-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support the release entity for the GitHub provider
Signed-off-by: Juan Antonio Osorio <ozz@stacklok.com>
- Loading branch information
Showing
5 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package properties | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
go_github "github.com/google/go-github/v63/github" | ||
|
||
"github.com/mindersec/minder/internal/entities/properties" | ||
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" | ||
v1 "github.com/mindersec/minder/pkg/providers/v1" | ||
) | ||
|
||
// Release Properties | ||
const ( | ||
// ReleasePropertyOwner represents the github owner | ||
ReleasePropertyOwner = "github/owner" | ||
// ReleasePropertyRepo represents the github repo | ||
ReleasePropertyRepo = "github/repo" | ||
// ReleasePropertyTag represents the github release tag name. | ||
ReleasePropertyTag = "github/tag" | ||
// ReleasePropertyBranch represents the github release branch | ||
ReleasePropertyBranch = "github/branch" | ||
) | ||
|
||
// ReleaseFetcher is a property fetcher for releases | ||
type ReleaseFetcher struct { | ||
propertyFetcherBase | ||
} | ||
|
||
// NewReleaseFetcher creates a new ReleaseFetcher | ||
func NewReleaseFetcher() *ReleaseFetcher { | ||
return &ReleaseFetcher{ | ||
propertyFetcherBase: propertyFetcherBase{ | ||
propertyOrigins: []propertyOrigin{ | ||
{ | ||
keys: []string{ | ||
// general entity | ||
properties.PropertyName, | ||
properties.PropertyUpstreamID, | ||
// general release | ||
ReleasePropertyTag, | ||
ReleasePropertyBranch, | ||
}, | ||
wrapper: getReleaseWrapper, | ||
}, | ||
}, | ||
operationalProperties: []string{}, | ||
}, | ||
} | ||
} | ||
|
||
// GetName returns the name of the release | ||
func (_ *ReleaseFetcher) GetName(props *properties.Properties) (string, error) { | ||
owner := props.GetProperty(ReleasePropertyOwner).GetString() | ||
repo, err := props.GetProperty(ReleasePropertyRepo).AsString() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get repo name: %w", err) | ||
} | ||
|
||
tag, err := props.GetProperty(ReleasePropertyTag).AsString() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get tag name: %w", err) | ||
} | ||
|
||
return getReleaseNameFromParams(owner, repo, tag), nil | ||
} | ||
|
||
func getReleaseNameFromParams(owner, repo, tag string) string { | ||
if owner == "" { | ||
return fmt.Sprintf("%s/%s", repo, tag) | ||
} | ||
|
||
return fmt.Sprintf("%s/%s/%s", owner, repo, tag) | ||
} | ||
|
||
func getReleaseWrapper( | ||
ctx context.Context, ghCli *go_github.Client, _ bool, getByProps *properties.Properties, | ||
) (map[string]any, error) { | ||
// TODO: Should I be parsing this as string or int64? | ||
// if string, then I should convert it to int64 | ||
upstreamID, err := getByProps.GetProperty(properties.PropertyUpstreamID).AsInt64() | ||
if err != nil { | ||
return nil, fmt.Errorf("upstream ID not found or invalid: %w", err) | ||
} | ||
|
||
owner, err := getByProps.GetProperty(ReleasePropertyOwner).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("owner not found or invalid: %w", err) | ||
} | ||
|
||
repo, err := getByProps.GetProperty(ReleasePropertyRepo).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("repo not found or invalid: %w", err) | ||
} | ||
|
||
var fetchErr error | ||
var release *go_github.RepositoryRelease | ||
var result *go_github.Response | ||
release, result, fetchErr = ghCli.Repositories.GetRelease(ctx, owner, repo, | ||
upstreamID) | ||
if fetchErr != nil { | ||
if result != nil && result.StatusCode == http.StatusNotFound { | ||
return nil, v1.ErrEntityNotFound | ||
} | ||
return nil, fmt.Errorf("failed to fetch release: %w", fetchErr) | ||
} | ||
|
||
return map[string]any{ | ||
properties.PropertyUpstreamID: properties.NumericalValueToUpstreamID(release.GetID()), | ||
properties.PropertyName: getReleaseNameFromParams(owner, repo, release.GetTagName()), | ||
ReleasePropertyOwner: owner, | ||
ReleasePropertyRepo: repo, | ||
ReleasePropertyTag: release.GetTagName(), | ||
ReleasePropertyBranch: release.GetTargetCommitish(), | ||
}, nil | ||
} | ||
|
||
// EntityInstanceV1FromReleaseProperties creates a new EntityInstance from the given properties | ||
func EntityInstanceV1FromReleaseProperties(props *properties.Properties) (*minderv1.EntityInstance, error) { | ||
_, err := props.GetProperty(properties.PropertyUpstreamID).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("upstream ID not found or invalid: %w", err) | ||
} | ||
|
||
tag, err := props.GetProperty(ReleasePropertyTag).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("tag not found or invalid: %w", err) | ||
} | ||
|
||
_, err = props.GetProperty(ReleasePropertyBranch).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("branch not found or invalid: %w", err) | ||
} | ||
|
||
owner := props.GetProperty(ReleasePropertyOwner).GetString() | ||
|
||
repo, err := props.GetProperty(ReleasePropertyRepo).AsString() | ||
if err != nil { | ||
return nil, fmt.Errorf("repo not found or invalid: %w", err) | ||
} | ||
|
||
name := getReleaseNameFromParams(owner, repo, tag) | ||
|
||
return &minderv1.EntityInstance{ | ||
Type: minderv1.Entity_ENTITY_RELEASE, | ||
Name: name, | ||
Properties: props.ToProtoStruct(), | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package webhook | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/mindersec/minder/internal/db" | ||
entityMessage "github.com/mindersec/minder/internal/entities/handlers/message" | ||
"github.com/mindersec/minder/internal/entities/properties" | ||
ghprop "github.com/mindersec/minder/internal/providers/github/properties" | ||
pb "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" | ||
"github.com/mindersec/minder/pkg/eventer/constants" | ||
) | ||
|
||
type releaseEvent struct { | ||
Action *string `json:"action,omitempty"` | ||
Release *release `json:"release,omitempty"` | ||
Repo *repo `json:"repository,omitempty"` | ||
} | ||
|
||
func (r *releaseEvent) GetAction() string { | ||
if r.Action != nil { | ||
return *r.Action | ||
} | ||
return "" | ||
} | ||
|
||
func (r *releaseEvent) GetRelease() *release { | ||
return r.Release | ||
} | ||
|
||
func (r *releaseEvent) GetRepo() *repo { | ||
return r.Repo | ||
} | ||
|
||
type release struct { | ||
ID *int64 `json:"id,omitempty"` | ||
TagName *string `json:"tag_name,omitempty"` | ||
Target *string `json:"target_commitish,omitempty"` | ||
} | ||
|
||
func (r *release) GetID() int64 { | ||
if r.ID != nil { | ||
return *r.ID | ||
} | ||
return 0 | ||
} | ||
|
||
func (r *release) GetTagName() string { | ||
if r.TagName != nil { | ||
return *r.TagName | ||
} | ||
return "" | ||
} | ||
|
||
func (r *release) GetTarget() string { | ||
if r.Target != nil { | ||
return *r.Target | ||
} | ||
return "" | ||
} | ||
|
||
func processReleaseEvent( | ||
ctx context.Context, | ||
payload []byte, | ||
) (*processingResult, error) { | ||
var event *releaseEvent | ||
if err := json.Unmarshal(payload, &event); err != nil { | ||
return nil, fmt.Errorf("failed to unmarshal release event: %w", err) | ||
} | ||
|
||
if event.GetAction() == "" { | ||
return nil, errors.New("release event action not found") | ||
} | ||
|
||
if event.GetRelease() == nil { | ||
return nil, errors.New("release event release not found") | ||
} | ||
|
||
if event.GetRepo() == nil { | ||
return nil, errors.New("release event repository not found") | ||
} | ||
|
||
if event.GetRelease().GetTagName() == "" { | ||
return nil, errors.New("release event tag name not found") | ||
} | ||
|
||
if event.GetRelease().GetTarget() == "" { | ||
return nil, errors.New("release event target not found") | ||
} | ||
|
||
return sendReleaseEvent(ctx, event) | ||
} | ||
|
||
func sendReleaseEvent( | ||
_ context.Context, | ||
event *releaseEvent, | ||
) (*processingResult, error) { | ||
lookByProps, err := properties.NewProperties(map[string]any{ | ||
properties.PropertyUpstreamID: properties.NumericalValueToUpstreamID(event.GetRelease().GetID()), | ||
ghprop.ReleasePropertyOwner: event.GetRepo().GetOwner(), | ||
ghprop.ReleasePropertyRepo: event.GetRepo().GetName(), | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating release properties: %w", err) | ||
} | ||
|
||
originatorProps, err := properties.NewProperties(map[string]any{ | ||
properties.PropertyUpstreamID: properties.NumericalValueToUpstreamID(event.GetRepo().GetID()), | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating repository properties for release origination: %w", err) | ||
} | ||
|
||
switch event.GetAction() { | ||
case "published": | ||
return &processingResult{ | ||
topic: constants.TopicQueueOriginatingEntityAdd, | ||
wrapper: entityMessage.NewEntityRefreshAndDoMessage(). | ||
WithEntity(pb.Entity_ENTITY_RELEASE, lookByProps). | ||
WithProviderImplementsHint(string(db.ProviderTypeGithub)). | ||
WithOriginator(pb.Entity_ENTITY_REPOSITORIES, originatorProps), | ||
}, nil | ||
case "unpublished", "deleted": | ||
return &processingResult{ | ||
topic: constants.TopicQueueOriginatingEntityDelete, | ||
wrapper: entityMessage.NewEntityRefreshAndDoMessage(). | ||
WithEntity(pb.Entity_ENTITY_RELEASE, lookByProps). | ||
WithProviderImplementsHint(string(db.ProviderTypeGithub)). | ||
WithOriginator(pb.Entity_ENTITY_REPOSITORIES, originatorProps), | ||
}, nil | ||
case "edited": | ||
return &processingResult{ | ||
topic: constants.TopicQueueRefreshEntityAndEvaluate, | ||
wrapper: entityMessage.NewEntityRefreshAndDoMessage(). | ||
WithEntity(pb.Entity_ENTITY_RELEASE, lookByProps). | ||
WithProviderImplementsHint(string(db.ProviderTypeGithub)). | ||
WithOriginator(pb.Entity_ENTITY_REPOSITORIES, originatorProps), | ||
}, nil | ||
} | ||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters