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
3 changes: 2 additions & 1 deletion api/v1beta3/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
GitLabProvider string = "gitlab"
GitLabMergeRequestCommentProvider string = "gitlabmergerequestcomment"
GiteaProvider string = "gitea"
GiteaPullRequestCommentProvider string = "giteapullrequestcomment"
BitbucketServerProvider string = "bitbucketserver"
BitbucketProvider string = "bitbucket"
AzureDevOpsProvider string = "azuredevops"
Expand All @@ -62,7 +63,7 @@ const (
// +kubebuilder:validation:XValidation:rule="self.type == 'github' || self.type == 'gitlab' || self.type == 'gitea' || self.type == 'bitbucketserver' || self.type == 'bitbucket' || self.type == 'azuredevops' || !has(self.commitStatusExpr)", message="spec.commitStatusExpr is only supported for the 'github', 'gitlab', 'gitea', 'bitbucketserver', 'bitbucket', 'azuredevops' provider types"
type ProviderSpec struct {
// Type specifies which Provider implementation to use.
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;githubpullrequestcomment;gitlabmergerequestcomment;pagerduty;datadog;nats;zulip;otel
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;giteapullrequestcomment;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;githubpullrequestcomment;gitlabmergerequestcomment;pagerduty;datadog;nats;zulip;otel
// +required
Type string `json:"type"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ spec:
- github
- gitlab
- gitea
- giteapullrequestcomment
- bitbucketserver
- bitbucket
- azuredevops
Expand Down
49 changes: 49 additions & 0 deletions docs/spec/v1beta3/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ The providers supporting change request (pull request / merge request) comments
| Provider | Type |
|----------------------------------------------------------------------------|--------------------------------|
| [GitHub Pull Request Comment](#github-pull-request-comment) | `githubpullrequestcomment` |
| [Gitea Pull Request Comment](#gitea-pull-request-comment) | `giteapullrequestcomment` |
| [GitLab Merge Request Comment](#gitlab-merge-request-comment) | `gitlabmergerequestcomment` |

#### Alerting
Expand Down Expand Up @@ -2147,6 +2148,54 @@ Metadata:
* `revision`: branch@sha1:hex
```

#### Gitea Pull Request Comment

When `.spec.type` is set to `giteapullrequestcomment`, the controller will post
a comment on the Gitea pull request specified in the event metadata.

This provider is designed to work with Flux objects that contain the
`event.toolkit.fluxcd.io/change_request` annotation, which specifies
the pull request number. Flux objects without this annotation are
ignored.

Each Flux object will have at most one status comment per provider on the pull request,
which is updated whenever a new event is received.

##### Authentication

The provider requires a [Gitea token](https://docs.gitea.io/en-us/api-usage/#generating-and-listing-api-tokens)
with at least the `write:issue` permission to read and write pull request comments.

##### Gitea Pull Request Comment Example

```yaml
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: gitea-pr-comment
namespace: flux-system
spec:
type: giteapullrequestcomment
address: https://gitea.example.com/my-org/my-repo
secretRef:
name: gitea-token
---
apiVersion: v1
kind: Secret
metadata:
name: gitea-token
namespace: flux-system
stringData:
token: <personal-access-token>
```

For self-hosted Gitea instances, update the `address` field to point to your Gitea instance.

##### Comment Format

The provider posts comments in the same format as the [GitHub Pull Request Comment](#comment-format) provider.

#### GitLab Merge Request Comment

When `.spec.type` is set to `gitlabmergerequestcomment`, the controller will post
Expand Down
10 changes: 6 additions & 4 deletions internal/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ var (
apiv1.GitLabProvider: gitLabNotifierFunc,
apiv1.GitLabMergeRequestCommentProvider: gitLabMergeRequestCommentNotifierFunc,
apiv1.GiteaProvider: giteaNotifierFunc,
apiv1.GiteaPullRequestCommentProvider: giteaPullRequestCommentNotifierFunc,
apiv1.BitbucketServerProvider: bitbucketServerNotifierFunc,
apiv1.BitbucketProvider: bitbucketNotifierFunc,
apiv1.AzureDevOpsProvider: azureDevOpsNotifierFunc,
Expand Down Expand Up @@ -353,10 +354,11 @@ func gitLabMergeRequestCommentNotifierFunc(opts notifierOptions) (Interface, err
}

func giteaNotifierFunc(opts notifierOptions) (Interface, error) {
if opts.Token == "" && opts.Password != "" {
opts.Token = opts.Password
}
return NewGitea(opts.CommitStatus, opts.URL, opts.ProxyURL, opts.Token, opts.TLSConfig)
return NewGitea(opts.CommitStatus, opts.GiteaClientOptions()...)
}

func giteaPullRequestCommentNotifierFunc(opts notifierOptions) (Interface, error) {
return NewGiteaPullRequestComment(opts.ProviderUID, opts.GiteaClientOptions()...)
}

func bitbucketServerNotifierFunc(opts notifierOptions) (Interface, error) {
Expand Down
54 changes: 7 additions & 47 deletions internal/notifier/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@ package notifier

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

"code.gitea.io/sdk/gitea"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
Expand All @@ -33,8 +29,6 @@ import (
)

type Gitea struct {
BaseURL string
Token string
Owner string
Repo string
CommitStatus string
Expand All @@ -44,55 +38,21 @@ type Gitea struct {

var _ Interface = &Gitea{}

func NewGitea(commitStatus string, addr string, proxyURL string, token string, tlsConfig *tls.Config) (*Gitea, error) {
if len(token) == 0 {
return nil, errors.New("gitea token cannot be empty")
}

// this should never happen
func NewGitea(commitStatus string, opts ...GiteaClientOption) (*Gitea, error) {
if commitStatus == "" {
return nil, errors.New("commit status cannot be empty")
}

host, id, err := parseGitAddress(addr)
if err != nil {
return nil, fmt.Errorf("failed parsing Git URL: %w", err)
}

if _, err := url.Parse(host); err != nil {
return nil, fmt.Errorf("failed parsing host: %w", err)
}

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

tr := &http.Transport{}
if tlsConfig != nil {
tr.TLSClientConfig = tlsConfig
}

if proxyURL != "" {
parsedProxyURL, err := url.Parse(proxyURL)
if err != nil {
return nil, errors.New("invalid proxy URL")
}
tr.Proxy = http.ProxyURL(parsedProxyURL)
}

client, err := gitea.NewClient(host, gitea.SetToken(token), gitea.SetHTTPClient(&http.Client{Transport: tr}))
clientInfo, err := NewGiteaClient(opts...)
if err != nil {
return nil, fmt.Errorf("failed creating Gitea client: %w", err)
return nil, err
}

return &Gitea{
BaseURL: host,
Token: token,
Owner: idComponents[0],
Repo: idComponents[1],
Owner: clientInfo.Owner,
Repo: clientInfo.Repo,
CommitStatus: commitStatus,
Client: client,
Client: clientInfo.Client,
Debug: os.Getenv("NOTIFIER_GITEA_DEBUG") == "true",
}, nil
}
Expand Down Expand Up @@ -141,7 +101,7 @@ func (g *Gitea) Post(ctx context.Context, event eventv1.Event) error {

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

st, rsp, err := g.Client.CreateStatus(g.Owner, g.Repo, rev, status)
Expand Down
159 changes: 159 additions & 0 deletions internal/notifier/gitea_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
Copyright 2026 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 (
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/url"
"strings"

"code.gitea.io/sdk/gitea"
)

// GiteaClient holds the Gitea client and repository information.
type GiteaClient struct {
Owner string
Repo string
Username string
Client *gitea.Client
}

// giteaClientOptions holds the configuration for creating a Gitea client.
type giteaClientOptions struct {
address string
token string
proxyURL string
tlsConfig *tls.Config
fetchUserLogin bool
}

// GiteaClientOption is a functional option for configuring Gitea client creation.
type GiteaClientOption func(*giteaClientOptions)

// WithGiteaAddress sets the Gitea repository address.
func WithGiteaAddress(addr string) GiteaClientOption {
return func(o *giteaClientOptions) {
o.address = addr
}
}

// WithGiteaToken sets the authentication token.
func WithGiteaToken(token string) GiteaClientOption {
return func(o *giteaClientOptions) {
o.token = token
}
}

// WithGiteaProxyURL sets the proxy URL.
func WithGiteaProxyURL(proxyURL string) GiteaClientOption {
return func(o *giteaClientOptions) {
o.proxyURL = proxyURL
}
}

// WithGiteaTLSConfig sets the TLS configuration.
func WithGiteaTLSConfig(cfg *tls.Config) GiteaClientOption {
return func(o *giteaClientOptions) {
o.tlsConfig = cfg
}
}

// WithGiteaFetchUserLogin enables fetching the authenticated user's login.
// This is needed for providers that need to identify their own comments.
func WithGiteaFetchUserLogin() GiteaClientOption {
return func(o *giteaClientOptions) {
o.fetchUserLogin = true
}
}

// NewGiteaClient creates a new GiteaClient with the provided options.
func NewGiteaClient(opts ...GiteaClientOption) (*GiteaClient, error) {
var o giteaClientOptions
for _, opt := range opts {
opt(&o)
}

if o.token == "" {
return nil, errors.New("gitea token cannot be empty")
}

host, id, err := parseGitAddress(o.address)
if err != nil {
return nil, err
}

if _, err := url.Parse(host); err != nil {
return nil, fmt.Errorf("failed parsing host: %w", err)
}

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

tr := &http.Transport{}
if o.tlsConfig != nil {
tr.TLSClientConfig = o.tlsConfig
}

if o.proxyURL != "" {
parsedProxyURL, err := url.Parse(o.proxyURL)
if err != nil {
return nil, errors.New("invalid proxy URL")
}
tr.Proxy = http.ProxyURL(parsedProxyURL)
}

client, err := gitea.NewClient(host, gitea.SetToken(o.token), gitea.SetHTTPClient(&http.Client{Transport: tr}))
if err != nil {
return nil, fmt.Errorf("failed creating Gitea client: %w", err)
}

var username string
if o.fetchUserLogin {
user, _, err := client.GetMyUserInfo()
if err != nil {
return nil, fmt.Errorf("failed to get authenticated user info: %w", err)
}
username = user.UserName
}

return &GiteaClient{
Owner: idComponents[0],
Repo: idComponents[1],
Username: username,
Client: client,
}, nil
}

// GiteaClientOptions returns the Gitea client options derived from notifierOptions.
// This handles the token/password fallback logic and converts factory options to Gitea client options.
func (o *notifierOptions) GiteaClientOptions() []GiteaClientOption {
token := o.Token
if token == "" && o.Password != "" {
token = o.Password
}
return []GiteaClientOption{
WithGiteaAddress(o.URL),
WithGiteaToken(token),
WithGiteaProxyURL(o.ProxyURL),
WithGiteaTLSConfig(o.TLSConfig),
}
}
Loading