Skip to content

Commit

Permalink
feat: add gitea notifier
Browse files Browse the repository at this point in the history
Signed-off-by: ttyS3 <ttys3.rust@gmail.com>
  • Loading branch information
ttys3 committed Dec 13, 2022
1 parent 5dc6124 commit c6be6f8
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 3 deletions.
2 changes: 1 addition & 1 deletion api/v1beta1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
// ProviderSpec defines the desired state of Provider
type ProviderSpec struct {
// Type of provider
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;
// +required
Type string `json:"type"`

Expand Down
1 change: 1 addition & 0 deletions api/v1beta2/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
GitHubDispatchProvider string = "githubdispatch"
GitHubProvider string = "github"
GitLabProvider string = "gitlab"
GiteaProvider string = "gitea"
BitbucketProvider string = "bitbucket"
AzureDevOpsProvider string = "azuredevops"
GoogleChatProvider string = "googlechat"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ spec:
- generic-hmac
- github
- gitlab
- gitea
- bitbucket
- azuredevops
- googlechat
Expand Down
5 changes: 3 additions & 2 deletions docs/spec/v1beta1/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Spec:
```go
type ProviderSpec struct {
// Type of provider
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;githubdispatch
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;githubdispatch
// +required
Type string `json:"type"`

Expand Down Expand Up @@ -72,11 +72,12 @@ Notification providers:
Git commit status providers:

| Provider | Type |
| ------------ | ----------- |
|--------------|-------------|
| Azure DevOps | azuredevops |
| Bitbucket | bitbucket |
| GitHub | github |
| GitLab | gitlab |
| Gitea | gitea |

Status:

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.18
replace github.com/fluxcd/notification-controller/api => ./api

require (
code.gitea.io/sdk/gitea v0.15.1
github.com/AdaLogics/go-fuzz-headers v0.0.0-20221103172237-443f56ff4ba8
github.com/Azure/azure-amqp-common-go/v3 v3.2.3
github.com/Azure/azure-event-hubs-go/v3 v3.3.20
Expand Down Expand Up @@ -99,6 +100,7 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20221103172237-443f56ff4ba8 h1:d+pBUmsteW5tM87xmVXHZ4+LibHRFn40SPAoZJOg2ak=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20221103172237-443f56ff4ba8/go.mod h1:i9fr2JpcEcY/IHEvzCM3qXUZYOQHgR89dt4es1CgMhc=
Expand Down Expand Up @@ -344,6 +347,9 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
Expand Down Expand Up @@ -839,6 +845,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
Expand Down
2 changes: 2 additions & 0 deletions internal/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func (f Factory) Notifier(provider string) (Interface, error) {
n, err = NewGitHubDispatch(f.URL, f.Token, f.CertPool)
case apiv1.GitLabProvider:
n, err = NewGitLab(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.GiteaProvider:
n, err = NewGitea(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.BitbucketProvider:
n, err = NewBitbucket(f.ProviderUID, f.URL, f.Token, f.CertPool)
case apiv1.AzureDevOpsProvider:
Expand Down
192 changes: 192 additions & 0 deletions internal/notifier/gitea.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notifier

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strings"

"code.gitea.io/sdk/gitea"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
ctrl "sigs.k8s.io/controller-runtime"
)

type Gitea struct {
BaseURL string
Token string
Owner string
Repo string
ProviderUID string
Client *gitea.Client
Debug bool
}

var _ Interface = &Gitea{}

func NewGitea(providerUID string, addr string, token string, certPool *x509.CertPool) (*Gitea, error) {
if len(token) == 0 {
return nil, errors.New("gitea token cannot be empty")
}

host, id, err := parseGitAddress(addr)
if err != nil {
return nil, err
}

if _, err := url.Parse(host); err != nil {
return nil, err
}

idComponents := strings.Split(id, "/")
if len(idComponents) != 2 {
return nil, fmt.Errorf("invalid repository id %q", id)
}

client, err := gitea.NewClient(host, gitea.SetToken(token))
if err != nil {
return nil, err
}

if certPool != nil {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
},
}
client.SetHTTPClient(&http.Client{Transport: tr})
}

return &Gitea{
BaseURL: host,
Token: token,
Owner: idComponents[0],
Repo: idComponents[1],
ProviderUID: providerUID,
Client: client,
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
}, err
}

func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {
revString, ok := event.Metadata[eventv1.MetaRevisionKey]
if !ok {
return errors.New("missing revision metadata")
}
rev, err := parseRevision(revString)
if err != nil {
return err
}
state, err := toGiteaState(event)
if err != nil {
return err
}

_, desc := formatNameAndDescription(event)
id := generateCommitStatusID(g.ProviderUID, event)

status := gitea.CreateStatusOption{
State: state,
TargetURL: "",
Description: desc,
Context: id,
}

listStatusesOpts := gitea.ListStatusesOption{
ListOptions: gitea.ListOptions{
Page: 0,
PageSize: 50,
},
}
statuses, _, err := g.Client.ListStatuses(g.Owner, g.Repo, rev, listStatusesOpts)
if err != nil {
return fmt.Errorf("could not list commit statuses: %w", err)
}
if duplicateGiteaStatus(statuses, &status) {
if g.Debug {
ctrl.Log.Info("gitea skip posting duplicate status",
"owner", g.Owner, "repo", g.Repo, "commit_hash", rev, "status", status)
}
return nil
}

if g.Debug {
ctrl.Log.Info("gitea create commit begin",
"base_url", g.BaseURL, "token", g.Token, "event", event, "status", status)
}

st, rsp, err := g.Client.CreateStatus(g.Owner, g.Repo, rev, status)
if err != nil {
if g.Debug {
ctrl.Log.Error(err, "gitea create commit failed", "status", status)
}
return err
}

if g.Debug {
ctrl.Log.Info("gitea create commit ok", "response", rsp, "response_status", st)
}

return nil
}

func toGiteaState(event eventv1.Event) (gitea.StatusState, error) {
// progressing events
if event.HasReason(meta.ProgressingReason) {
// pending
return gitea.StatusPending, nil
}
switch event.Severity {
case eventv1.EventSeverityInfo:
return gitea.StatusSuccess, nil
case eventv1.EventSeverityError:
return gitea.StatusFailure, nil
default:
return gitea.StatusError, errors.New("can't convert to gitea state")
}
}

// duplicateGiteaStatus return true if the latest status
// with a matching context has the same state and description
func duplicateGiteaStatus(statuses []*gitea.Status, status *gitea.CreateStatusOption) bool {
if status == nil || statuses == nil {
return false
}

for _, s := range statuses {
if s.Context == "" || s.State == "" || s.Description == "" {
continue
}

if s.Context == status.Context {
if s.State == status.State && s.Description == status.Description {
return true
}

return false
}
}

return false
}
71 changes: 71 additions & 0 deletions internal/notifier/gitea_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notifier

import (
"context"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestNewGiteaBasic(t *testing.T) {
g, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://try.gitea.io/foo/bar", "foobar", nil)
assert.Nil(t, err)
assert.Equal(t, g.Owner, "foo")
assert.Equal(t, g.Repo, "bar")
assert.Equal(t, g.BaseURL, "https://try.gitea.io")
}

func TestNewGiteaInvalidUrl(t *testing.T) {
_, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://try.gitea.io/foo/bar/baz", "foobar", nil)
assert.NotNil(t, err)
}

func TestNewGiteaEmptyToken(t *testing.T) {
_, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://try.gitea.io/foo/bar", "", nil)
assert.NotNil(t, err)
}

func TestGitea_Post(t *testing.T) {
g, err := NewGitea("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://try.gitea.io/foo/bar", "foobar", nil)
assert.Nil(t, err)

event := eventv1.Event{
InvolvedObject: corev1.ObjectReference{
Kind: "Kustomization",
Namespace: "flux-system",
Name: "podinfo-repo",
},
Severity: "info",
Timestamp: metav1.Time{
Time: time.Now(),
},
Metadata: map[string]string{
eventv1.MetaRevisionKey: "main/1234567890",
},
Message: "Service/podinfo/podinfo configured",
Reason: "",
}
err = g.Post(context.Background(), event)
assert.NotNil(t, err)
assert.ErrorContains(t, err, "404 Not Found")
}

0 comments on commit c6be6f8

Please sign in to comment.