Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f406781
feature/3778
LUKIEYF Oct 21, 2025
1458ea2
[pre-commit.ci] auto fixes from pre-commit.com hooks [CI SKIP]
pre-commit-ci[bot] Oct 21, 2025
0d37662
Merge branch 'main' into faeture/3778
LUKIEYF Oct 22, 2025
52ca636
revamp, simplify the code in init the http.Transport and set the TLSC…
LUKIEYF Oct 22, 2025
42e02b6
markdownlint: example_usage.md complied
LUKIEYF Oct 23, 2025
710c6b5
Merge branch 'main' into faeture/3778
LUKIEYF Oct 23, 2025
fac07d5
Remove the example_usage.md and double check the docstring is proper
LUKIEYF Oct 27, 2025
39d39a8
Merge branch 'main' into faeture/3778
LUKIEYF Oct 28, 2025
656668b
bitbucket, forgejo, and gittea. add pratice in wrapped http header.
LUKIEYF Oct 30, 2025
cba3894
Merge branch 'faeture/3778' of github.com:LUKIEYF/woodpecker into fae…
LUKIEYF Oct 30, 2025
45c57c5
Merge branch 'main' into faeture/3778
LUKIEYF Oct 30, 2025
82ee0f2
Merge branch 'main' into faeture/3778
6543 Oct 31, 2025
8c90915
add 'useragent' to cspell allow list .json
LUKIEYF Nov 3, 2025
57f7ba0
Merge branch 'main' into faeture/3778
LUKIEYF Nov 3, 2025
a6034d8
1. adjust the import position
LUKIEYF Nov 4, 2025
479f981
Merge branch 'main' into faeture/3778
LUKIEYF Nov 4, 2025
e060e4b
Merge branch 'main' into faeture/3778
LUKIEYF Nov 5, 2025
a8d80cf
1. add the useragent files for external go file usage.
LUKIEYF Nov 5, 2025
b83e3d4
Merge branch 'faeture/3778' of github.com:LUKIEYF/woodpecker into fae…
LUKIEYF Nov 5, 2025
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
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
"unsanitize",
"Upsert",
"urfave",
"useragent",
"usecase",
"varchar",
"varz",
Expand Down
9 changes: 7 additions & 2 deletions cli/internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"golang.org/x/net/proxy"
"golang.org/x/oauth2"

"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)

Expand Down Expand Up @@ -72,23 +73,27 @@ func NewClient(ctx context.Context, c *cli.Command) (woodpecker.Client, error) {

trans, _ := client.Transport.(*oauth2.Transport)

var baseTransport http.RoundTripper
if len(socks) != 0 && !socksOff {
dialer, err := proxy.SOCKS5("tcp", socks, nil, proxy.Direct)
if err != nil {
return nil, err
}
trans.Base = &http.Transport{
baseTransport = &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
Dial: dialer.Dial,
}
} else {
trans.Base = &http.Transport{
baseTransport = &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
}
}

// Wrap the base transport with User-Agent support
trans.Base = httputil.NewUserAgentRoundTripper(baseTransport, "cli")

return woodpecker.NewClient(server, client), nil
}

Expand Down
5 changes: 4 additions & 1 deletion pipeline/backend/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/urfave/cli/v3"

backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)

Expand Down Expand Up @@ -92,7 +93,9 @@ func httpClientOfOpts(dockerCertPath string, verifyTLS bool) *http.Client {
}

return &http.Client{
Transport: &http.Transport{TLSClientConfig: tlsConf},
Transport: httputil.NewUserAgentRoundTripper(
&http.Transport{TLSClientConfig: tlsConf},
"backend-docker"),
CheckRedirect: client.CheckRedirect,
}
}
Expand Down
5 changes: 4 additions & 1 deletion server/forge/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v3/server/forge/common"
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)

Expand Down Expand Up @@ -449,7 +450,7 @@ func (c *config) newClient(ctx context.Context, u *model.User) *internal.Client

// helper function to return the bitbucket oauth2 client.
func (c *config) newClientToken(ctx context.Context, accessToken, refreshToken string) *internal.Client {
return internal.NewClientToken(
client := internal.NewClientToken(
ctx,
c.api,
accessToken,
Expand All @@ -459,6 +460,8 @@ func (c *config) newClientToken(ctx context.Context, accessToken, refreshToken s
RefreshToken: refreshToken,
},
)
client.Client = httputil.WrapClient(client.Client, "forge-bitbucket")
return client
}

// helper function to return the bitbucket oauth2 config.
Expand Down
2 changes: 2 additions & 0 deletions server/forge/bitbucketdatacenter/bitbucketdatacenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/store"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
)

const (
Expand Down Expand Up @@ -768,5 +769,6 @@ func (c *client) newClient(ctx context.Context, u *model.User) (*bb.Client, erro
AccessToken: u.AccessToken,
}
client := config.Client(ctx, t)
client = httputil.WrapClient(client, "forge-bitbucketdatacenter")
return bb.NewClient(c.urlAPI, client)
}
6 changes: 4 additions & 2 deletions server/forge/forgejo/forgejo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/store"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)

Expand Down Expand Up @@ -586,12 +587,13 @@ func (c *Forgejo) newClientToken(ctx context.Context, token string) (*forgejo.Cl
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
client, err := forgejo.NewClient(c.url, forgejo.SetToken(token), forgejo.SetHTTPClient(httpClient), forgejo.SetContext(ctx))
wrappedClient := httputil.WrapClient(httpClient, "forge-forgejo")
client, err := forgejo.NewClient(c.url, forgejo.SetToken(token), forgejo.SetHTTPClient(wrappedClient), forgejo.SetContext(ctx))
if err != nil &&
(errors.Is(err, &forgejo.ErrUnknownVersion{}) || strings.Contains(err.Error(), "Malformed version")) {
// we guess it's a dev forgejo version
log.Error().Err(err).Msgf("could not detect forgejo version, assume dev version %s", forgejoDevVersion)
client, err = forgejo.NewClient(c.url, forgejo.SetForgejoVersion(forgejoDevVersion), forgejo.SetToken(token), forgejo.SetHTTPClient(httpClient), forgejo.SetContext(ctx))
client, err = forgejo.NewClient(c.url, forgejo.SetForgejoVersion(forgejoDevVersion), forgejo.SetToken(token), forgejo.SetHTTPClient(wrappedClient), forgejo.SetContext(ctx))
}
return client, err
}
Expand Down
6 changes: 4 additions & 2 deletions server/forge/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/store"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)

Expand Down Expand Up @@ -593,12 +594,13 @@ func (c *Gitea) newClientToken(ctx context.Context, token string) (*gitea.Client
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
client, err := gitea.NewClient(c.url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient), gitea.SetContext(ctx))
wrappedClient := httputil.WrapClient(httpClient, "forge-gitea")
client, err := gitea.NewClient(c.url, gitea.SetToken(token), gitea.SetHTTPClient(wrappedClient), gitea.SetContext(ctx))
if err != nil &&
(errors.Is(err, &gitea.ErrUnknownVersion{}) || strings.Contains(err.Error(), "Malformed version")) {
// we guess it's a dev gitea version
log.Error().Err(err).Msgf("could not detect gitea version, assume dev version %s", giteaDevVersion)
client, err = gitea.NewClient(c.url, gitea.SetGiteaVersion(giteaDevVersion), gitea.SetToken(token), gitea.SetHTTPClient(httpClient), gitea.SetContext(ctx))
client, err = gitea.NewClient(c.url, gitea.SetGiteaVersion(giteaDevVersion), gitea.SetToken(token), gitea.SetHTTPClient(wrappedClient), gitea.SetContext(ctx))
}
return client, err
}
Expand Down
20 changes: 14 additions & 6 deletions server/forge/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/store"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)

Expand Down Expand Up @@ -469,15 +470,22 @@ func (c *client) newClientToken(ctx context.Context, token string) *github.Clien
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)

// Get the oauth2 transport to set custom base
tp, _ := tc.Transport.(*oauth2.Transport)

baseTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
}
if c.SkipVerify {
tp, _ := tc.Transport.(*oauth2.Transport)
tp.Base = &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
baseTransport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}

// Wrap the base transport with User-Agent support
tp.Base = httputil.NewUserAgentRoundTripper(baseTransport, "forge-github")

client := github.NewClient(tc)
client.BaseURL, _ = url.Parse(c.API)
return client
Expand Down
12 changes: 8 additions & 4 deletions server/forge/gitlab/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

gitlab "gitlab.com/gitlab-org/api/client-go"
"golang.org/x/oauth2"

"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
)

const (
Expand All @@ -33,10 +35,12 @@ func newClient(url, accessToken string, skipVerify bool) (*gitlab.Client, error)
return gitlab.NewAuthSourceClient(gitlab.OAuthTokenSource{
TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}),
}, gitlab.WithBaseURL(url), gitlab.WithHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
Proxy: http.ProxyFromEnvironment,
},
Transport: httputil.NewUserAgentRoundTripper(
&http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
Proxy: http.ProxyFromEnvironment,
},
"forge-gitlab"),
}))
}

Expand Down
13 changes: 10 additions & 3 deletions server/services/utils/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/yaronf/httpsign"

host_matcher "go.woodpecker-ci.org/woodpecker/v3/server/services/utils/hostmatcher"
"go.woodpecker-ci.org/woodpecker/v3/shared/httputil"
)

type Client struct {
Expand Down Expand Up @@ -58,12 +59,18 @@ func getHTTPClient(privateKey crypto.PrivateKey, allowedHostListValue string) (*
return nil, err
}

client := http.Client{
Timeout: timeout,
Transport: &http.Transport{
// Create base transport with custom User-Agent
baseTransport := httputil.NewUserAgentRoundTripper(
&http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
DialContext: host_matcher.NewDialContext("extensions", allowedHostMatcher),
},
"server-extensions",
)

client := http.Client{
Timeout: timeout,
Transport: baseTransport,
}

config := httpsign.NewClientConfig().SetSignatureName(pubKeyID).SetSigner(signer)
Expand Down
72 changes: 72 additions & 0 deletions shared/httputil/useragent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2024 Woodpecker 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 httputil

import (
"fmt"
"net/http"

"go.woodpecker-ci.org/woodpecker/v3/version"
)

// UserAgentRoundTripper is an http.RoundTripper that sets a custom User-Agent header
// on all outgoing requests.
type UserAgentRoundTripper struct {
base http.RoundTripper
userAgent string
}

// NewUserAgentRoundTripper creates a new RoundTripper that adds the Woodpecker User-Agent
// to all requests. If base is nil, http.DefaultTransport is used.
func NewUserAgentRoundTripper(base http.RoundTripper, component string) *UserAgentRoundTripper {
if base == nil {
base = http.DefaultTransport
}

userAgent := fmt.Sprintf("Woodpecker/%s", version.String())
if component != "" {
userAgent = fmt.Sprintf("%s (%s)", userAgent, component)
}

return &UserAgentRoundTripper{
base: base,
userAgent: userAgent,
}
}

// RoundTrip implements the http.RoundTripper interface.
func (rt *UserAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Clone the request to avoid modifying the original
reqClone := req.Clone(req.Context())

// Set the User-Agent header if not already set
if reqClone.Header.Get("User-Agent") == "" {
reqClone.Header.Set("User-Agent", rt.userAgent)
}

// Execute the request using the base transport
return rt.base.RoundTrip(reqClone)
}

// WrapClient wraps an existing http.Client with the UserAgentRoundTripper.
// If client is nil, a new client with default settings is created.
func WrapClient(client *http.Client, component string) *http.Client {
if client == nil {
client = &http.Client{}
}

client.Transport = NewUserAgentRoundTripper(client.Transport, component)
return client
}
Loading