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 d302029
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 1 deletion.
3 changes: 2 additions & 1 deletion 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 All @@ -51,7 +52,7 @@ const (
// ProviderSpec defines the desired state of the Provider.
type ProviderSpec struct {
// Type specifies which Provider implementation to use.
// +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
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ spec:
- generic-hmac
- github
- gitlab
- gitea
- bitbucket
- azuredevops
- googlechat
Expand Down
14 changes: 14 additions & 0 deletions docs/spec/v1beta2/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ The supported providers for [Git commit status updates](#git-commit-status-updat
| [Bitbucket](#bitbucket) | `bitbucket` |
| [GitHub](#github) | `github` |
| [GitLab](#gitlab) | `gitlab` |
| [Gitea](#gitea) | `gitea` |

#### Alerting

Expand Down Expand Up @@ -1291,6 +1292,19 @@ You can create the secret with `kubectl` like this:
kubectl create secret generic gitlab-token --from-literal=token=<GITLAB-TOKEN>
```

#### Gitea

When `.spec.type` is set to `gitea`, the referenced secret must contain a key called `token` with the value set to a
[Gitea token](https://docs.gitea.io/en-us/api-usage/#generating-and-listing-api-tokens).

The token owner must have permissions to update the commit status for the Gitea repository specified in `.spec.address`.

You can create the secret with `kubectl` like this:

```shell
kubectl create secret generic gitea-token --from-literal=token=<GITEA-TOKEN>
```

#### BitBucket

When `.spec.type` is set to `bitbucket`, the referenced secret must contain a key called `token` with the value
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 d302029

Please sign in to comment.