Generate unique commit status updates

Use the Provider cluster assigned UID to compose a unique commit status
ID to avoid name collisions when multiple clusters write to the same
repository.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2022-10-27 17:48:46 +03:00 committed by Hidde Beydals
parent 974a77da00
commit 9f2d0e1a6c
15 changed files with 106 additions and 92 deletions

View File

@ -206,7 +206,7 @@ func (r *ProviderReconciler) validate(ctx context.Context, provider *apiv1.Provi
}
}
factory := notifier.NewFactory(address, proxy, username, provider.Spec.Channel, token, headers, certPool, password)
factory := notifier.NewFactory(address, proxy, username, provider.Spec.Channel, token, headers, certPool, password, string(provider.UID))
if _, err := factory.Notifier(provider.Spec.Type); err != nil {
return fmt.Errorf("failed to initialize provider, error: %w", err)
}

View File

@ -35,13 +35,14 @@ const genre string = "fluxcd"
// AzureDevOps is a Azure DevOps notifier.
type AzureDevOps struct {
Project string
Repo string
Client git.Client
Project string
Repo string
ProviderUID string
Client git.Client
}
// NewAzureDevOps creates and returns a new AzureDevOps notifier.
func NewAzureDevOps(addr string, token string, certPool *x509.CertPool) (*AzureDevOps, error) {
func NewAzureDevOps(providerUID string, addr string, token string, certPool *x509.CertPool) (*AzureDevOps, error) {
if len(token) == 0 {
return nil, errors.New("azure devops token cannot be empty")
}
@ -71,9 +72,10 @@ func NewAzureDevOps(addr string, token string, certPool *x509.CertPool) (*AzureD
Client: *client,
}
return &AzureDevOps{
Project: proj,
Repo: repo,
Client: gitClient,
Project: proj,
Repo: repo,
ProviderUID: providerUID,
Client: gitClient,
}, nil
}
@ -99,7 +101,8 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
// Check if the exact status is already set
g := genre
name, desc := formatNameAndDescription(event)
_, desc := formatNameAndDescription(event)
id := generateCommitStatusID(a.ProviderUID, event)
createArgs := git.CreateCommitStatusArgs{
Project: &a.Project,
RepositoryId: &a.Repo,
@ -109,7 +112,7 @@ func (a AzureDevOps) Post(ctx context.Context, event eventv1.Event) error {
State: &state,
Context: &git.GitStatusContext{
Genre: &g,
Name: &name,
Name: &id,
},
},
}

View File

@ -33,13 +33,12 @@ import (
const apiLocations = `{"count":0,"value":[{"area":"","id":"428dd4fb-fda5-4722-af02-9313b80305da","routeTemplate":"","resourceName":"","maxVersion":"6.0","minVersion":"5.0","releasedVersion":"6.0"}]}`
func Fuzz_AzureDevOps(f *testing.F) {
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "error", "", []byte{}, []byte(`{"count":1,"value":[{"state":"error","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":1,"value":[{"state":"info","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":0,"value":[]}`))
f.Add("alakazam", "org/proj/_git/repo", "", "", "Progressing", []byte{}, []byte{})
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "error", "", []byte{}, []byte(`{"count":1,"value":[{"state":"error","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":1,"value":[{"state":"info","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":0,"value":[]}`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "alakazam", "org/proj/_git/repo", "", "", "Progressing", []byte{}, []byte{})
f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity, reason string, seed, response []byte) {
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "_apis") {
w.Write([]byte(apiLocations))
@ -55,7 +54,7 @@ func Fuzz_AzureDevOps(f *testing.F) {
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
azureDevOps, err := NewAzureDevOps(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
azureDevOps, err := NewAzureDevOps(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

View File

@ -24,19 +24,19 @@ import (
)
func TestNewAzureDevOpsBasic(t *testing.T) {
a, err := NewAzureDevOps("https://dev.azure.com/foo/bar/_git/baz", "foo", nil)
a, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/_git/baz", "foo", nil)
assert.Nil(t, err)
assert.Equal(t, a.Project, "bar")
assert.Equal(t, a.Repo, "baz")
}
func TestNewAzureDevOpsInvalidUrl(t *testing.T) {
_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "foo", nil)
_, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/baz", "foo", nil)
assert.NotNil(t, err)
}
func TestNewAzureDevOpsMissingToken(t *testing.T) {
_, err := NewAzureDevOps("https://dev.azure.com/foo/bar/baz", "", nil)
_, err := NewAzureDevOps("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://dev.azure.com/foo/bar/baz", "", nil)
assert.NotNil(t, err)
}

View File

@ -31,12 +31,10 @@ import (
)
func Fuzz_Bitbucket(f *testing.F) {
f.Add("user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
f.Add("user:pass", "org/repo", "revision/dsa123a", "error", []byte{}, []byte(`{}`))
f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity string, seed, response []byte) {
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "user:pass", "org/repo", "revision/dsa123a", "error", []byte{}, []byte(`{}`))
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(io.Discard, r.Body)
w.Write(response)
@ -47,7 +45,7 @@ func Fuzz_Bitbucket(f *testing.F) {
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
bitbucket, err := NewBitbucket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
bitbucket, err := NewBitbucket(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

View File

@ -24,26 +24,36 @@ import (
)
type Factory struct {
URL string
ProxyURL string
Username string
Channel string
Token string
Headers map[string]string
CertPool *x509.CertPool
Password string
URL string
ProxyURL string
Username string
Channel string
Token string
Headers map[string]string
CertPool *x509.CertPool
Password string
ProviderUID string
}
func NewFactory(url string, proxy string, username string, channel string, token string, headers map[string]string, certPool *x509.CertPool, password string) *Factory {
func NewFactory(url string,
proxy string,
username string,
channel string,
token string,
headers map[string]string,
certPool *x509.CertPool,
password string,
providerUID string) *Factory {
return &Factory{
URL: url,
ProxyURL: proxy,
Channel: channel,
Username: username,
Token: token,
Headers: headers,
CertPool: certPool,
Password: password,
URL: url,
ProxyURL: proxy,
Channel: channel,
Username: username,
Token: token,
Headers: headers,
CertPool: certPool,
Password: password,
ProviderUID: providerUID,
}
}
@ -68,15 +78,15 @@ func (f Factory) Notifier(provider string) (Interface, error) {
case apiv1.MSTeamsProvider:
n, err = NewMSTeams(f.URL, f.ProxyURL, f.CertPool)
case apiv1.GitHubProvider:
n, err = NewGitHub(f.URL, f.Token, f.CertPool)
n, err = NewGitHub(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.GitHubDispatchProvider:
n, err = NewGitHubDispatch(f.URL, f.Token, f.CertPool)
case apiv1.GitLabProvider:
n, err = NewGitLab(f.URL, f.Token, f.CertPool)
n, err = NewGitLab(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.BitbucketProvider:
n, err = NewBitbucket(f.URL, f.Token, f.CertPool)
case apiv1.AzureDevOpsProvider:
n, err = NewAzureDevOps(f.URL, f.Token, f.CertPool)
n, err = NewAzureDevOps(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.GoogleChatProvider:
n, err = NewGoogleChat(f.URL, f.ProxyURL)
case apiv1.WebexProvider:

View File

@ -34,12 +34,13 @@ import (
)
type GitHub struct {
Owner string
Repo string
Client *github.Client
Owner string
Repo string
ProviderUID string
Client *github.Client
}
func NewGitHub(addr string, token string, certPool *x509.CertPool) (*GitHub, error) {
func NewGitHub(providerUID string, addr string, token string, certPool *x509.CertPool) (*GitHub, error) {
if len(token) == 0 {
return nil, errors.New("github token cannot be empty")
}
@ -81,9 +82,10 @@ func NewGitHub(addr string, token string, certPool *x509.CertPool) (*GitHub, err
}
return &GitHub{
Owner: comp[0],
Repo: comp[1],
Client: client,
Owner: comp[0],
Repo: comp[1],
ProviderUID: providerUID,
Client: client,
}, nil
}
@ -106,11 +108,12 @@ func (g *GitHub) Post(ctx context.Context, event eventv1.Event) error {
if err != nil {
return err
}
name, desc := formatNameAndDescription(event)
_, desc := formatNameAndDescription(event)
id := generateCommitStatusID(g.ProviderUID, event)
status := &github.RepoStatus{
State: &state,
Context: &name,
Context: &id,
Description: &desc,
}

View File

@ -30,15 +30,14 @@ import (
)
func Fuzz_GitHub(f *testing.F) {
f.Add("token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"success","description":""}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/"}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"success","description":""}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/","state":"failure","description":""}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"context":"/"}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity, reason string, seed, response []byte) {
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(response)
io.Copy(io.Discard, r.Body)
@ -49,7 +48,7 @@ func Fuzz_GitHub(f *testing.F) {
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
github, err := NewGitHub(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
github, err := NewGitHub(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

View File

@ -24,7 +24,7 @@ import (
)
func TestNewGitHubBasic(t *testing.T) {
g, err := NewGitHub("https://github.com/foo/bar", "foobar", nil)
g, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "foobar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Owner, "foo")
assert.Equal(t, g.Repo, "bar")
@ -32,7 +32,7 @@ func TestNewGitHubBasic(t *testing.T) {
}
func TestNewEmterpriseGitHubBasic(t *testing.T) {
g, err := NewGitHub("https://foobar.com/foo/bar", "foobar", nil)
g, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://foobar.com/foo/bar", "foobar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Owner, "foo")
assert.Equal(t, g.Repo, "bar")
@ -40,12 +40,12 @@ func TestNewEmterpriseGitHubBasic(t *testing.T) {
}
func TestNewGitHubInvalidUrl(t *testing.T) {
_, err := NewGitHub("https://github.com/foo/bar/baz", "foobar", nil)
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar/baz", "foobar", nil)
assert.NotNil(t, err)
}
func TestNewGitHubEmptyToken(t *testing.T) {
_, err := NewGitHub("https://github.com/foo/bar", "", nil)
_, err := NewGitHub("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://github.com/foo/bar", "", nil)
assert.NotNil(t, err)
}

View File

@ -31,11 +31,12 @@ import (
)
type GitLab struct {
Id string
Client *gitlab.Client
Id string
ProviderUID string
Client *gitlab.Client
}
func NewGitLab(addr string, token string, certPool *x509.CertPool) (*GitLab, error) {
func NewGitLab(providerUID string, addr string, token string, certPool *x509.CertPool) (*GitLab, error) {
if len(token) == 0 {
return nil, errors.New("gitlab token cannot be empty")
}
@ -61,8 +62,9 @@ func NewGitLab(addr string, token string, certPool *x509.CertPool) (*GitLab, err
}
gitlab := &GitLab{
Id: id,
Client: client,
Id: id,
ProviderUID: providerUID,
Client: client,
}
return gitlab, nil
@ -88,9 +90,10 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
return err
}
name, desc := formatNameAndDescription(event)
_, desc := formatNameAndDescription(event)
id := generateCommitStatusID(g.ProviderUID, event)
status := &gitlab.CommitStatus{
Name: name,
Name: id,
SHA: rev,
Status: string(state),
Description: desc,
@ -106,7 +109,7 @@ func (g *GitLab) Post(ctx context.Context, event eventv1.Event) error {
}
setOpt := &gitlab.SetCommitStatusOptions{
Name: &name,
Name: &id,
Description: &desc,
State: state,
}

View File

@ -30,13 +30,12 @@ import (
)
func Fuzz_GitLab(f *testing.F) {
f.Add("token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
f.Add("token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
f.Add("token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "error", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[{"sha":"abce1","status":"failed","name":"/","description":""}]`))
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "Progressing", []byte{}, []byte{})
f.Add("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "token", "org/repo", "revision/abce1", "info", "", []byte{}, []byte(`[]`))
f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity, reason string, seed, response []byte) {
f.Fuzz(func(t *testing.T, uuid, token, urlSuffix, revision, severity, reason string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(response)
io.Copy(io.Discard, r.Body)
@ -47,7 +46,7 @@ func Fuzz_GitLab(f *testing.F) {
var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)
gitLab, err := NewGitLab(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
gitLab, err := NewGitLab(uuid, fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

View File

@ -23,25 +23,25 @@ import (
)
func TestNewGitLabBasic(t *testing.T) {
g, err := NewGitLab("https://gitlab.com/foo/bar", "foobar", nil)
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar", "foobar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Id, "foo/bar")
}
func TestNewGitLabSubgroups(t *testing.T) {
g, err := NewGitLab("https://gitlab.com/foo/bar/baz", "foobar", nil)
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar/baz", "foobar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Id, "foo/bar/baz")
}
func TestNewGitLabSelfHosted(t *testing.T) {
g, err := NewGitLab("https://example.com/foo/bar", "foo:bar", nil)
g, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com/foo/bar", "foo:bar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Id, "foo/bar")
assert.Equal(t, g.Client.BaseURL().Host, "example.com")
}
func TestNewGitLabEmptyToken(t *testing.T) {
_, err := NewGitLab("https://gitlab.com/foo/bar", "", nil)
_, err := NewGitLab("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://gitlab.com/foo/bar", "", nil)
assert.NotNil(t, err)
}

View File

@ -109,11 +109,11 @@ func splitCamelcase(src string) (entries []string) {
func parseRevision(rev string) (string, error) {
comp := strings.Split(rev, "/")
if len(comp) < 2 {
return "", fmt.Errorf("Revision string format incorrect: %v", rev)
return "", fmt.Errorf("revision string format incorrect: %v", rev)
}
sha := comp[len(comp)-1]
if sha == "" {
return "", fmt.Errorf("Commit Sha cannot be empty: %v", rev)
return "", fmt.Errorf("commit SHA cannot be empty: %v", rev)
}
return sha, nil
}

View File

@ -54,13 +54,13 @@ func TestUtil_ParseRevisionNestedBranch(t *testing.T) {
func TestUtil_ParseRevisionOneComponents(t *testing.T) {
revString := "master"
_, err := parseRevision(revString)
require.EqualError(t, err, "Revision string format incorrect: master")
require.EqualError(t, err, "revision string format incorrect: master")
}
func TestUtil_ParseRevisionTooFewComponents(t *testing.T) {
revString := "master/"
_, err := parseRevision(revString)
require.EqualError(t, err, "Commit Sha cannot be empty: master/")
require.EqualError(t, err, "commit SHA cannot be empty: master/")
}
func TestUtil_ParseGitHttps(t *testing.T) {

View File

@ -243,7 +243,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
continue
}
factory := notifier.NewFactory(webhook, proxy, username, provider.Spec.Channel, token, headers, certPool, password)
factory := notifier.NewFactory(webhook, proxy, username, provider.Spec.Channel, token, headers, certPool, password, string(provider.UID))
sender, err := factory.Notifier(provider.Spec.Type)
if err != nil {
s.logger.Error(err, "failed to initialize provider",