Skip to content

Commit

Permalink
feat(STONEINTG-998): support group snapshot
Browse files Browse the repository at this point in the history
* Create group snapshot for the component snapshots with opened PR and  belonging to the same
  pr group once a component snasphot is created for PR
* Set group to snapshot type to group snapshot
* Set event-type to pull-request to group snapshot
* Set pr-group annotation and pr-group-sha label from component snasphot to group snapshot

Signed-off-by: Hongwei Liu <hongliu@redhat.com>
  • Loading branch information
hongweiliu17 committed Aug 16, 2024
1 parent 4edfc44 commit 999affe
Show file tree
Hide file tree
Showing 12 changed files with 529 additions and 14 deletions.
25 changes: 25 additions & 0 deletions git/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ type RepositoriesService interface {
ListStatuses(ctx context.Context, owner, repo, ref string, opts *ghapi.ListOptions) ([]*ghapi.RepoStatus, *ghapi.Response, error)
}

// PullRequestsService defines the methods used in the github PullRequests service.
type PullRequestsService interface {
Get(ctx context.Context, owner string, repo string, prID int) (*ghapi.PullRequest, *ghapi.Response, error)
}

// ClientInterface defines the methods that should be implemented by a GitHub client
type ClientInterface interface {
CreateAppInstallationToken(ctx context.Context, appID int64, installationID int64, privateKey []byte) (string, error)
Expand All @@ -107,6 +112,7 @@ type ClientInterface interface {
CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *CommitStatusAdapter) (bool, error)
GetExistingCommentID(comments []*ghapi.IssueComment, snapshotName, scenarioName string) *int64
EditComment(ctx context.Context, owner string, repo string, commentID int64, body string) (int64, error)
GetPullRequest(ctx context.Context, owner string, repo string, prID int) (*ghapi.PullRequest, error)
}

// Client is an abstraction around the API client.
Expand All @@ -117,6 +123,7 @@ type Client struct {
checks ChecksService
issues IssuesService
repos RepositoriesService
pulls PullRequestsService
}

// GetAppsService returns either the default or custom Apps service.
Expand Down Expand Up @@ -151,6 +158,14 @@ func (c *Client) GetRepositoriesService() RepositoriesService {
return c.repos
}

// GetPullRequestsService returns either the default or custom PullRequest service.
func (c *Client) GetPullRequestsService() PullRequestsService {
if c.pulls == nil {
return c.gh.PullRequests
}
return c.pulls
}

// ClientOption is used to extend Client with optional parameters.
type ClientOption = func(c *Client)

Expand Down Expand Up @@ -502,3 +517,13 @@ func (c *Client) CreateCommitStatus(ctx context.Context, owner string, repo stri

return *status.ID, nil
}

// GetPullRequest returns pull request according to the owner, repo and pull request number
func (c *Client) GetPullRequest(ctx context.Context, owner string, repo string, prID int) (*ghapi.PullRequest, error) {
pr, _, err := c.GetPullRequestsService().Get(ctx, owner, repo, prID)
if err != nil {
return nil, fmt.Errorf("failed to get pull request for GitHub owner/repo/pull %s/%s/%d: %w", owner, repo, prID, err)
}

return pr, err
}
102 changes: 101 additions & 1 deletion gitops/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"github.com/konflux-ci/integration-service/api/v1beta2"
"reflect"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -34,6 +35,7 @@ import (
applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
Expand Down Expand Up @@ -82,6 +84,9 @@ const (
// PRGroupHashLabel contains the pr group name in sha format
PRGroupHashLabel = "test.appstudio.openshift.io/pr-group-sha"

// PRGroupCreationAnnotation contains the info of groupsnapshot creation
PRGroupCreationAnnotation = "test.appstudio.openshift.io/create-groupsnapshot-status"

// BuildPipelineRunStartTime contains the start time of build pipelineRun
BuildPipelineRunStartTime = "test.appstudio.openshift.io/pipelinerunstarttime"

Expand All @@ -91,6 +96,9 @@ const (
// BuildPipelineRunFinishTimeLabel contains the build PipelineRun finish time of the Snapshot.
BuildPipelineRunFinishTimeLabel = "test.appstudio.openshift.io/pipelinerunfinishtime"

// GroupSnapshotInfoAnnotation contains the component snapshot info included in group snapshot
GroupSnapshotInfoAnnotation = "test.appstudio.openshift.io/group-test-info"

// BuildPipelineRunNameLabel contains the build PipelineRun name
BuildPipelineRunNameLabel = AppstudioLabelPrefix + "/build-pipelinerun"

Expand Down Expand Up @@ -221,6 +229,16 @@ var (
SnapshotComponentLabel = tekton.ComponentNameLabel
)

// ComponentSnapshotInfo contains data about the component snapshots' info in group snapshot
type ComponentSnapshotInfo struct {
// Component name
Component string `json:"component"`
// The build PLR name building the container image triggered by pull request
BuildPipelineRun string `json:"buildPipelineRun"`
// The built component snapshot from build PLR
Snapshot string `json:"snapshot"`
}

// IsSnapshotMarkedAsPassed returns true if snapshot is marked as passed
func IsSnapshotMarkedAsPassed(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsSnapshotStatusConditionSet(snapshot, AppStudioTestSucceededCondition, metav1.ConditionTrue, "")
Expand Down Expand Up @@ -749,7 +767,7 @@ func PrepareSnapshot(ctx context.Context, adapterClient client.Client, applicati
return snapshot, nil
}

// FindMatchingSnapshot tries to find the expected Snapshot with the same set of images.
// FindMatchingSnapshot tries to finds the expected Snapshot with the same set of images.
func FindMatchingSnapshot(application *applicationapiv1alpha1.Application, allSnapshots *[]applicationapiv1alpha1.Snapshot, expectedSnapshot *applicationapiv1alpha1.Snapshot) *applicationapiv1alpha1.Snapshot {
for _, foundSnapshot := range *allSnapshots {
foundSnapshot := foundSnapshot
Expand Down Expand Up @@ -956,3 +974,85 @@ func FilterIntegrationTestScenariosWithContext(scenarios *[]v1beta2.IntegrationT
}
return &filteredScenarioList
}

// HasPRGroupProcessed check if the pr group has been handled by snapshot adapter
// to avoid duplicate check, if yes, won't handle the snapshot again
func HasPRGroupProcessed(snapshot *applicationapiv1alpha1.Snapshot) bool {
return metadata.HasAnnotation(snapshot, PRGroupCreationAnnotation)
}

// GetPRGroupHashFromSnapshot gets the value of label test.appstudio.openshift.io/pr-group-sha from component snapshot
func GetPRGroupHashFromSnapshot(snapshot *applicationapiv1alpha1.Snapshot) string {
if metadata.HasLabel(snapshot, PRGroupHashLabel) {
return snapshot.Annotations[PRGroupHashLabel]
}
return ""
}

// GetPRGroupFromSnapshot gets the value of annotation test.appstudio.openshift.io/pr-group from component snapshot
func GetPRGroupFromSnapshot(snapshot *applicationapiv1alpha1.Snapshot) string {
if metadata.HasAnnotation(snapshot, PRGroupAnnotation) {
return snapshot.Annotations[PRGroupAnnotation]
}
return ""
}

// FindMatchingSnapshotComponent find the snapshot component from the given snapshot according to the name of the given component name
func FindMatchingSnapshotComponent(snapshot *applicationapiv1alpha1.Snapshot, component applicationapiv1alpha1.Component) applicationapiv1alpha1.SnapshotComponent {
for _, snapshotComponent := range snapshot.Spec.Components {
if snapshotComponent.Name == component.Name {
return snapshotComponent
}
}
return applicationapiv1alpha1.SnapshotComponent{}

}

// SortSnapshots sorts the snapshots according to the snapshot annotation BuildPipelineRunStartTime
func SortSnapshots(snapshots []applicationapiv1alpha1.Snapshot) []applicationapiv1alpha1.Snapshot {
sort.Slice(snapshots, func(i, j int) bool {
// sorting snapshots according to the annotation BuildPipelineRunStartTime which
// represents the start time of build PLR
time_i, _ := strconv.Atoi(snapshots[i].Annotations[BuildPipelineRunStartTime])
time_j, _ := strconv.Atoi(snapshots[j].Annotations[BuildPipelineRunStartTime])

return time_j > time_i
})
return snapshots
}

// AnnotateSnapshot sets annotation for a snapshot in defined context, return error if meeting it
func AnnotateSnapshot(ctx context.Context, snapshot *applicationapiv1alpha1.Snapshot, key, value string, cl client.Client) error {
patch := client.MergeFrom(snapshot.DeepCopy())

_ = metadata.SetAnnotation(&snapshot.ObjectMeta, key, value)

err := cl.Patch(ctx, snapshot, patch)
if err != nil {
return err
}
return nil
}

// NotifyComponentSnapshotsInGroupSnapshot annotate the msg to the given component snapshots in componentSnapshotInfos
func NotifyComponentSnapshotsInGroupSnapshot(ctx context.Context, cl client.Client, componentSnapshotInfos []ComponentSnapshotInfo, namespace, msg string) error {
log := log.FromContext(ctx)
for _, componentSnapshotInfo := range componentSnapshotInfos {
snapshot := &applicationapiv1alpha1.Snapshot{}
err := cl.Get(ctx, types.NamespacedName{
Namespace: namespace,
Name: componentSnapshotInfo.Snapshot,
}, snapshot)
if err != nil {
log.Error(err, fmt.Sprintf("error while getting snapshot %s from namespace: %s", componentSnapshotInfo.Snapshot, namespace))
return err
}

err = AnnotateSnapshot(ctx, snapshot, PRGroupCreationAnnotation, msg, cl)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to annotate group snapshot creation status to component snapshot %s/%s", namespace, componentSnapshotInfo.Snapshot))
return err
}
}
return nil
}
10 changes: 5 additions & 5 deletions internal/controller/buildpipeline/buildpipeline_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,9 @@ func (a *Adapter) updatePipelineRunWithCustomizedError(canRemoveFinalizer *bool,
// addPRGroupToBuildPLRMetadata will add pr-group info gotten from souce-branch to annotation
// and also the string in sha format to metadata label
func (a *Adapter) addPRGroupToBuildPLRMetadata(pipelineRun *tektonv1.PipelineRun) error {
prGroupName := tekton.GetPRGroupNameFromBuildPLR(pipelineRun)
if prGroupName != "" {
prGroupHashName := tekton.GenerateSHA(prGroupName)
prGroup := tekton.GetPRGroupFromBuildPLR(pipelineRun)
if prGroup != "" {
prGroupHash := tekton.GenerateSHA(prGroup)

return retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
Expand All @@ -383,8 +383,8 @@ func (a *Adapter) addPRGroupToBuildPLRMetadata(pipelineRun *tektonv1.PipelineRun

patch := client.MergeFrom(pipelineRun.DeepCopy())

_ = metadata.SetAnnotation(&pipelineRun.ObjectMeta, gitops.PRGroupAnnotation, prGroupName)
_ = metadata.SetLabel(&pipelineRun.ObjectMeta, gitops.PRGroupHashLabel, prGroupHashName)
_ = metadata.SetAnnotation(&pipelineRun.ObjectMeta, gitops.PRGroupAnnotation, prGroup)
_ = metadata.SetLabel(&pipelineRun.ObjectMeta, gitops.PRGroupHashLabel, prGroupHash)

return a.client.Patch(a.context, pipelineRun, patch)
})
Expand Down
Loading

0 comments on commit 999affe

Please sign in to comment.