Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/provider/bitbucketcloud/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,10 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusopts
detailsURL = statusopts.DetailsURL
}

key := provider.GetBBCloudStatusKey(statusopts, v.pacInfo)

cso := &bitbucket.CommitStatusOptions{
Key: provider.GetCheckName(statusopts, v.pacInfo),
Key: key,
Url: detailsURL,
State: string(state),
Description: statusopts.Title,
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/bitbucketcloud/test/bbcloudtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func MuxCreateCommitstatus(t *testing.T, mux *http.ServeMux, event *info.Event,
err := json.Unmarshal(bit, cso)
assert.NilError(t, err)
pacOpts := &info.PacOpts{Settings: settings.Settings{ApplicationName: applicationName}}
assert.Equal(t, provider.GetCheckName(expStatus, pacOpts), cso.Key)
assert.Equal(t, provider.GetBBCloudStatusKey(expStatus, pacOpts), cso.Key)

if expStatus.DetailsURL != "" {
assert.Equal(t, expStatus.DetailsURL, cso.Url)
Expand Down
13 changes: 13 additions & 0 deletions pkg/provider/bitbucketcloud/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ type Comments struct {
Values []Comment
}

type Status struct {
Key string `json:"key"`
Type string `json:"type"`
State string `json:"state"`
Name string `json:"name"`
RefName string `json:"refname"`
Commit Commit `json:"commit"`
URL string `json:"url"`
Repository Repository `json:"repository"`
Description string `json:"description"`
Links Links `json:"links"`
}

type CommitStatusState string

// See: https://api.bitbucket.org/swagger.json attribute:
Expand Down
38 changes: 38 additions & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package provider

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"regexp"
Expand Down Expand Up @@ -221,6 +223,42 @@ func GetCheckName(status providerstatus.StatusOpts, pacopts *info.PacOpts) strin
return status.OriginalPipelineRunName
}

// GetBBCloudStatusKey returns a unique key for Bitbucket Cloud build commit status.
Comment thread
theakshaypant marked this conversation as resolved.
// Bitbucket Cloud limits the key to 40 characters. When both ApplicationName and
// OriginalPipelineRunName are set and their combined form ("{appName} / {prName}")
// fits within 40 chars, that combined form is used. Otherwise, the
// OriginalPipelineRunName alone is used. If the name exceeds 40 chars, it is
// truncated to 33 chars with a 6-char hash suffix for uniqueness. If only
// ApplicationName is set, it is returned truncated to 40 chars.
func GetBBCloudStatusKey(status providerstatus.StatusOpts, pacopts *info.PacOpts) string {
if pacopts.ApplicationName != "" {
if status.OriginalPipelineRunName == "" {
if len(pacopts.ApplicationName) > 40 {
return pacopts.ApplicationName[:40]
}
return pacopts.ApplicationName
}
key := fmt.Sprintf("%s / %s", pacopts.ApplicationName, status.OriginalPipelineRunName)
// if application name and pipeline run name combined are less than 40 characters, return the key
// otherwise, skip it here and let the only pipeline run being returned.
if len(key) <= 40 {
return key
}
}

if len(status.OriginalPipelineRunName) > 40 {
hash := getNameHash(status.OriginalPipelineRunName)
return fmt.Sprintf("%s-%s", status.OriginalPipelineRunName[:33], hash)
}

return status.OriginalPipelineRunName
}

func getNameHash(input string) string {
hash := sha256.Sum256([]byte(input))
return hex.EncodeToString(hash[:])[:6]
}

func IsZeroSHA(sha string) bool {
return sha == "0000000000000000000000000000000000000000"
}
Expand Down
90 changes: 90 additions & 0 deletions pkg/provider/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"strings"
"testing"

"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
Expand Down Expand Up @@ -867,3 +868,92 @@ func TestGetGitOpsCommentPrefix(t *testing.T) {
})
}
}

func TestGetBBCloudStatusKey(t *testing.T) {
tests := []struct {
name string
status status.StatusOpts
pacopts *info.PacOpts
expected string
}{
{
name: "application name only no pipeline run name",
status: status.StatusOpts{OriginalPipelineRunName: ""},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: "MyApp"},
},
expected: "MyApp",
},
{
name: "application name longer than 40 truncated",
status: status.StatusOpts{OriginalPipelineRunName: ""},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: strings.Repeat("x", 50)},
},
expected: strings.Repeat("x", 40),
},
{
name: "app and pr name combined fit in 40 chars",
status: status.StatusOpts{OriginalPipelineRunName: "my-pr"},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: "MyApp"},
},
expected: "MyApp / my-pr",
},
{
name: "app and pr name combined exceed 40 chars falls back to pr name only",
status: status.StatusOpts{OriginalPipelineRunName: "this-is-a-very-long-pipeline-run-name"},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: "MyApplication"},
},
expected: "this-is-a-very-long-pipeline-run-name",
},
{
name: "no application name short pipeline run name",
status: status.StatusOpts{OriginalPipelineRunName: "my-pr"},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: ""},
},
expected: "my-pr",
},
{
name: "no application name pipeline run name exactly 40",
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("a", 40)},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: ""},
},
expected: strings.Repeat("a", 40),
},
{
name: "no application name pipeline run name longer than 40 truncated with hash",
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("c", 50)},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: ""},
},
expected: strings.Repeat("c", 33) + "-5de6bf",
},
{
name: "no application name no pipeline run name",
status: status.StatusOpts{OriginalPipelineRunName: ""},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: ""},
},
expected: "",
},
{
name: "long pr name with app produces stable hash",
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("b", 50)},
pacopts: &info.PacOpts{
Settings: settings.Settings{ApplicationName: "App"},
},
expected: strings.Repeat("b", 33) + "-c01a9b",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetBBCloudStatusKey(tt.status, tt.pacopts)
assert.Equal(t, tt.expected, got)
assert.Assert(t, len(got) <= 40, "key length %d exceeds 40 char limit: %s", len(got), got)
})
}
}
62 changes: 62 additions & 0 deletions test/bitbucket_cloud_pullrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ package test
import (
"context"
"fmt"
"strings"
"testing"
"time"

"github.com/ktrysmt/go-bitbucket"
"github.com/mitchellh/mapstructure"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketcloud/types"
tbb "github.com/openshift-pipelines/pipelines-as-code/test/pkg/bitbucketcloud"
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
Expand Down Expand Up @@ -143,6 +146,65 @@ func TestBitbucketCloudCELExpressionOnPush(t *testing.T) {
twait.Succeeded(ctx, t, runcnx, opts, sopt)
}

func TestBitbucketCloudPRBuildStatusReported(t *testing.T) {
targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
ctx := context.Background()

runcnx, opts, bprovider, err := tbb.Setup(ctx)
if err != nil {
t.Skip(err.Error())
return
}
bcrepo := tbb.CreateCRD(ctx, t, bprovider, runcnx, opts, targetNS)
targetRefName := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test")
title := "TestPullRequest - " + targetRefName

entries, err := payload.GetEntries(
map[string]string{".tekton/my-long-pipelinerun-name-exceeding-forty-chars.yaml": "testdata/pipelinerun.yaml"},
targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{})
assert.NilError(t, err)

pr, repobranch := tbb.MakePR(t, bprovider, runcnx, bcrepo, opts, title, targetRefName, entries)
defer tbb.TearDown(ctx, t, runcnx, bprovider, opts, pr.ID, targetRefName, targetNS, false)

hash, ok := repobranch.Target["hash"].(string)
assert.Assert(t, ok)

sopt := twait.SuccessOpt{
TargetNS: targetNS,
OnEvent: triggertype.PullRequest.String(),
NumberofPRMatch: 1,
SHA: hash,
Title: title,
MinNumberStatus: 1,
}
twait.Succeeded(ctx, t, runcnx, opts, sopt)

resp, err := bprovider.Client().Repositories.Commits.GetCommitStatuses(&bitbucket.CommitsOptions{
Owner: opts.Organization,
RepoSlug: opts.Repo,
Revision: hash,
})
assert.NilError(t, err)

statusesMap, ok := resp.(map[string]any)
assert.Equal(t, ok, true, "cannot convert Bitbucket commit statuses response to map[string]any")

statuses := []*types.Status{}

err = mapstructure.Decode(statusesMap["values"], &statuses)
assert.NilError(t, err, fmt.Sprintf("cannot decode Bitbucket commit statuses from response payload: %v", err))

foundStatus := false
for _, status := range statuses {
if strings.Contains(status.Key, "my-long-pipelinerun-name") {
foundStatus = true
break
}
}
assert.Equal(t, foundStatus, true, "should have found the status for the pipeline run")
}

// Local Variables:
// compile-command: "go test -tags=e2e -v -run TestBitbucketCloudPullRequest$ ."
// End:
Loading