Skip to content

Commit 106b201

Browse files
committed
feat: Allow custom user agent for Forgejo/Gitea
Added the ability to specify a custom User-Agent header for API requests to Gitea/Forgejo instances. This is useful for instances protected by AI scraping protection proxies that may block requests lacking a recognized User-Agent. The User-Agent can be configured per repository in the `Repository` CRD under `spec.settings.gitea.user_agent`. If not specified, it defaults to "pipelines-as-code/<version>". This functionality was implemented by adding a new field to the `GiteaSettings` struct and updating the `gitea.go` provider to utilize this new setting when initializing the Gitea client. Documentation and tests were also updated to reflect this change. Jira: https://issues.redhat.com/browse/SRVKP-10579 AI-assisted-by: Google Gemini Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>
1 parent 4d950e7 commit 106b201

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

config/300-repositories.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,17 @@ spec:
448448
- roles
449449
- secret_ref
450450
type: object
451+
forgejo:
452+
description: Forgejo contains Forgejo/Gitea-specific settings.
453+
properties:
454+
user_agent:
455+
description: |-
456+
UserAgent sets the User-Agent header on API requests to the Gitea/Forgejo instance.
457+
This is useful when the instance is behind an AI scraping protection proxy (e.g. Anubis)
458+
that blocks requests without a recognized User-Agent.
459+
Defaults to "pipelines-as-code/<version>" when left empty.
460+
type: string
461+
type: object
451462
github:
452463
properties:
453464
comment_strategy:

docs/content/docs/guide/repositorycrd.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,23 @@ spec:
192192
comment_strategy: "disable_all"
193193
```
194194

195+
### Forgejo/Gitea
196+
197+
You can configure a custom `User-Agent` header for API requests to
198+
Forgejo/Gitea instances. This is useful when the instance is behind an AI
199+
scraping protection proxy (such as [Anubis](https://anubis.techaro.lol/)) that
200+
blocks requests without a recognized `User-Agent`.
201+
202+
By default, Pipelines-as-Code sends `pipelines-as-code/<version>` as the
203+
`User-Agent`. You can override it per repository:
204+
205+
```yaml
206+
spec:
207+
settings:
208+
forgejo:
209+
user_agent: "my-custom-agent"
210+
```
211+
195212
## Concurrency
196213

197214
`concurrency_limit` allows you to define the maximum number of PipelineRuns running at any time for a Repository.

pkg/apis/pipelinesascode/v1alpha1/types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ type Settings struct {
161161

162162
Github *GithubSettings `json:"github,omitempty"`
163163

164+
// Forgejo contains Forgejo/Gitea-specific settings.
165+
// +optional
166+
Forgejo *ForgejoSettings `json:"forgejo,omitempty"`
167+
164168
// AIAnalysis contains AI/LLM analysis configuration for automated CI/CD pipeline analysis.
165169
// +optional
166170
AIAnalysis *AIAnalysisConfig `json:"ai,omitempty"`
@@ -186,6 +190,15 @@ type GithubSettings struct {
186190
CommentStrategy string `json:"comment_strategy,omitempty"`
187191
}
188192

193+
type ForgejoSettings struct {
194+
// UserAgent sets the User-Agent header on API requests to the Gitea/Forgejo instance.
195+
// This is useful when the instance is behind an AI scraping protection proxy (e.g. Anubis)
196+
// that blocks requests without a recognized User-Agent.
197+
// Defaults to "pipelines-as-code/<version>" when left empty.
198+
// +optional
199+
UserAgent string `json:"user_agent,omitempty"`
200+
}
201+
189202
func (s *Settings) Merge(newSettings *Settings) {
190203
if newSettings.PipelineRunProvenance != "" && s.PipelineRunProvenance == "" {
191204
s.PipelineRunProvenance = newSettings.PipelineRunProvenance
@@ -196,6 +209,9 @@ func (s *Settings) Merge(newSettings *Settings) {
196209
if newSettings.GithubAppTokenScopeRepos != nil && s.GithubAppTokenScopeRepos == nil {
197210
s.GithubAppTokenScopeRepos = newSettings.GithubAppTokenScopeRepos
198211
}
212+
if newSettings.Forgejo != nil && s.Forgejo == nil {
213+
s.Forgejo = newSettings.Forgejo
214+
}
199215
if newSettings.AIAnalysis != nil && s.AIAnalysis == nil {
200216
s.AIAnalysis = newSettings.AIAnalysis
201217
}

pkg/apis/pipelinesascode/v1alpha1/types_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,56 @@ func TestMergeSpecs(t *testing.T) {
117117
ConcurrencyLimit: &two,
118118
},
119119
},
120+
{
121+
name: "forgejo settings from global",
122+
local: &RepositorySpec{
123+
Settings: &Settings{},
124+
GitProvider: &GitProvider{},
125+
},
126+
global: RepositorySpec{
127+
Settings: &Settings{
128+
Forgejo: &ForgejoSettings{
129+
UserAgent: "my-custom-agent",
130+
},
131+
},
132+
GitProvider: &GitProvider{},
133+
},
134+
expected: &RepositorySpec{
135+
Settings: &Settings{
136+
Forgejo: &ForgejoSettings{
137+
UserAgent: "my-custom-agent",
138+
},
139+
},
140+
GitProvider: &GitProvider{},
141+
},
142+
},
143+
{
144+
name: "local forgejo settings take precedence",
145+
local: &RepositorySpec{
146+
Settings: &Settings{
147+
Forgejo: &ForgejoSettings{
148+
UserAgent: "local-agent",
149+
},
150+
},
151+
GitProvider: &GitProvider{},
152+
},
153+
global: RepositorySpec{
154+
Settings: &Settings{
155+
Forgejo: &ForgejoSettings{
156+
UserAgent: "global-agent",
157+
},
158+
},
159+
GitProvider: &GitProvider{},
160+
},
161+
expected: &RepositorySpec{
162+
Settings: &Settings{
163+
Forgejo: &ForgejoSettings{
164+
UserAgent: "local-agent",
165+
},
166+
},
167+
GitProvider: &GitProvider{},
168+
},
169+
},
120170
{
121171
name: "different git providers",
122172
local: &RepositorySpec{

pkg/provider/gitea/gitea.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
2525
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
2626
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
27+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/versiondata"
2728
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider"
2829
providerMetrics "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/providermetrics"
2930
providerstatus "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/status"
@@ -200,14 +201,18 @@ func (v *Provider) GetConfig() *info.ProviderConfig {
200201
func (v *Provider) SetClient(_ context.Context, run *params.Run, runevent *info.Event, repo *v1alpha1.Repository, emitter *events.EventEmitter) error {
201202
var err error
202203
apiURL := runevent.Provider.URL
204+
userAgent := "pipelines-as-code/" + strings.TrimSpace(versiondata.Version)
205+
if repo != nil && repo.Spec.Settings != nil && repo.Spec.Settings.Forgejo != nil && repo.Spec.Settings.Forgejo.UserAgent != "" {
206+
userAgent = repo.Spec.Settings.Forgejo.UserAgent
207+
}
203208
// password is not exposed to CRD, it's only used from the e2e tests
204209
if v.Password != "" && runevent.Provider.User != "" {
205-
v.giteaClient, err = forgejo.NewClient(apiURL, forgejo.SetBasicAuth(runevent.Provider.User, v.Password))
210+
v.giteaClient, err = forgejo.NewClient(apiURL, forgejo.SetBasicAuth(runevent.Provider.User, v.Password), forgejo.SetUserAgent(userAgent))
206211
} else {
207212
if runevent.Provider.Token == "" {
208213
return fmt.Errorf("no git_provider.secret has been set in the repo crd")
209214
}
210-
v.giteaClient, err = forgejo.NewClient(apiURL, forgejo.SetToken(runevent.Provider.Token))
215+
v.giteaClient, err = forgejo.NewClient(apiURL, forgejo.SetToken(runevent.Provider.Token), forgejo.SetUserAgent(userAgent))
211216
}
212217
if err != nil {
213218
return err

0 commit comments

Comments
 (0)