Skip to content

Commit d63e8c6

Browse files
committed
feat(scm): implement commit status provider
Signed-off-by: Ushira Dineth <[email protected]>
1 parent e00400f commit d63e8c6

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package bitbucket
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
"time"
9+
10+
"github.com/ktrysmt/go-bitbucket"
11+
v1 "k8s.io/api/core/v1"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
"sigs.k8s.io/controller-runtime/pkg/log"
14+
15+
"github.com/argoproj-labs/gitops-promoter/api/v1alpha1"
16+
"github.com/argoproj-labs/gitops-promoter/internal/metrics"
17+
"github.com/argoproj-labs/gitops-promoter/internal/scms"
18+
"github.com/argoproj-labs/gitops-promoter/internal/utils"
19+
)
20+
21+
// CommitStatus implements the scms.CommitStatusProvider interface for Bitbucket.
22+
type CommitStatus struct {
23+
client *bitbucket.Client
24+
k8sClient client.Client
25+
}
26+
27+
var _ scms.CommitStatusProvider = &CommitStatus{}
28+
29+
// NewBitbucketCommitStatusProvider creates a new instance of CommitStatus for Bitbucket.
30+
func NewBitbucketCommitStatusProvider(k8sClient client.Client, secret v1.Secret, domain string) (*CommitStatus, error) {
31+
client, err := GetClient(secret)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
return &CommitStatus{client: client, k8sClient: k8sClient}, nil
37+
}
38+
39+
// Set sets the commit status for a given commit SHA in the specified repository.
40+
func (cs *CommitStatus) Set(ctx context.Context, commitStatus *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error) {
41+
logger := log.FromContext(ctx)
42+
logger.Info("Setting Commit Phase")
43+
44+
repo, err := utils.GetGitRepositoryFromObjectKey(ctx, cs.k8sClient, client.ObjectKey{
45+
Namespace: commitStatus.Namespace,
46+
Name: commitStatus.Spec.RepositoryReference.Name,
47+
})
48+
if err != nil {
49+
return nil, fmt.Errorf("failed to get repo: %w", err)
50+
}
51+
52+
commitOptions := &bitbucket.CommitsOptions{
53+
Owner: repo.Spec.Bitbucket.Workspace,
54+
RepoSlug: repo.Spec.Bitbucket.Repository,
55+
Revision: commitStatus.Spec.Sha,
56+
}
57+
58+
commitStatusOptions := &bitbucket.CommitStatusOptions{
59+
State: phaseToBuildState(commitStatus.Spec.Phase),
60+
Key: commitStatus.Spec.Name,
61+
Url: commitStatus.Spec.Url,
62+
Description: commitStatus.Spec.Description,
63+
}
64+
65+
start := time.Now()
66+
result, err := cs.client.Repositories.Commits.CreateCommitStatus(
67+
commitOptions,
68+
commitStatusOptions,
69+
)
70+
71+
// Parse error message to determine status code
72+
statusCode := http.StatusCreated
73+
if err != nil {
74+
statusCode = http.StatusInternalServerError
75+
if bbErr, ok := err.(*bitbucket.UnexpectedResponseStatusError); ok {
76+
errMsg := bbErr.Error()
77+
switch {
78+
case strings.HasPrefix(errMsg, "401"):
79+
statusCode = http.StatusUnauthorized
80+
case strings.HasPrefix(errMsg, "404"):
81+
statusCode = http.StatusNotFound
82+
}
83+
}
84+
}
85+
86+
metrics.RecordSCMCall(repo, metrics.SCMAPICommitStatus, metrics.SCMOperationCreate, statusCode, time.Since(start), nil)
87+
88+
if err != nil {
89+
return nil, fmt.Errorf("failed to create status: %w", err)
90+
}
91+
92+
logger.V(4).Info("bitbucket response status", "status", statusCode)
93+
94+
// Parse the response
95+
resultMap, ok := result.(map[string]interface{})
96+
if !ok {
97+
return nil, fmt.Errorf("unexpected response type from Bitbucket API: %T", result)
98+
}
99+
100+
// Extract state
101+
state, _ := resultMap["state"].(string)
102+
103+
commitStatus.Status.Phase = buildStateToPhase(state)
104+
commitStatus.Status.Sha = commitStatus.Spec.Sha
105+
106+
// Bitbucket doesn't return an ID for commit statuses, use key as identifier
107+
commitStatus.Status.Id = commitStatus.Spec.Name
108+
109+
return commitStatus, nil
110+
}

internal/scms/bitbucket/utils.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package bitbucket
2+
3+
import (
4+
"github.com/argoproj-labs/gitops-promoter/api/v1alpha1"
5+
)
6+
7+
// phaseToBuildState converts a CommitStatusPhase to a Bitbucket build state.
8+
// Bitbucket states: SUCCESSFUL, FAILED, INPROGRESS, STOPPED
9+
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/#api-repositories-workspace-repo-slug-commit-commit-statuses-build-post-request-body
10+
func phaseToBuildState(phase v1alpha1.CommitStatusPhase) string {
11+
switch phase {
12+
case v1alpha1.CommitPhaseSuccess:
13+
return "SUCCESSFUL"
14+
case v1alpha1.CommitPhasePending:
15+
return "INPROGRESS"
16+
default:
17+
return "FAILED"
18+
}
19+
}
20+
21+
// buildStateToPhase converts a Bitbucket build state to a CommitStatusPhase.
22+
// Bitbucket states: SUCCESSFUL, FAILED, INPROGRESS, STOPPED
23+
// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/#api-repositories-workspace-repo-slug-commit-commit-statuses-build-post-request-body
24+
func buildStateToPhase(buildState string) v1alpha1.CommitStatusPhase {
25+
switch buildState {
26+
case "SUCCESSFUL":
27+
return v1alpha1.CommitPhaseSuccess
28+
case "INPROGRESS":
29+
return v1alpha1.CommitPhasePending
30+
default:
31+
return v1alpha1.CommitPhaseFailure
32+
}
33+
}

0 commit comments

Comments
 (0)