diff --git a/cmd/server/flags.go b/cmd/server/flags.go
index 4567367b045..900e5e4347d 100644
--- a/cmd/server/flags.go
+++ b/cmd/server/flags.go
@@ -284,11 +284,21 @@ var flags = append([]cli.Flag{
Name: "config-extension-exclusive",
Usage: "whether global configuration service endpoint should be exclusive (skip forge)",
},
+ &cli.BoolFlag{
+ Sources: cli.EnvVars("WOODPECKER_CONFIG_EXTENSION_NETRC"),
+ Name: "config-extension-netrc",
+ Usage: "whether global configuration extension should receive netrc data",
+ },
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_REGISTRY_EXTENSION_ENDPOINT"),
Name: "registry-extension-endpoint",
Usage: "url used for calling registry service endpoint",
},
+ &cli.BoolFlag{
+ Sources: cli.EnvVars("WOODPECKER_REGISTRY_EXTENSION_NETRC"),
+ Name: "registry-extension-netrc",
+ Usage: "whether global registry extension should receive netrc data",
+ },
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_SECRET_EXTENSION_ENDPOINT"),
Name: "secret-extension-endpoint",
diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go
index 5bc9d825223..5eb6c0999ae 100644
--- a/cmd/server/openapi/docs.go
+++ b/cmd/server/openapi/docs.go
@@ -5250,6 +5250,9 @@ const docTemplate = `{
"config_extension_exclusive": {
"type": "boolean"
},
+ "config_extension_netrc": {
+ "type": "boolean"
+ },
"config_file": {
"type": "string"
},
@@ -5296,6 +5299,9 @@ const docTemplate = `{
"registry_extension_endpoint": {
"type": "string"
},
+ "registry_extension_netrc": {
+ "type": "boolean"
+ },
"require_approval": {
"$ref": "#/definitions/model.ApprovalMode"
},
@@ -5355,6 +5361,9 @@ const docTemplate = `{
"config_extension_exclusive": {
"type": "boolean"
},
+ "config_extension_netrc": {
+ "type": "boolean"
+ },
"config_file": {
"type": "string"
},
@@ -5404,6 +5413,9 @@ const docTemplate = `{
"registry_extension_endpoint": {
"type": "string"
},
+ "registry_extension_netrc": {
+ "type": "boolean"
+ },
"require_approval": {
"$ref": "#/definitions/model.ApprovalMode"
},
@@ -5451,6 +5463,9 @@ const docTemplate = `{
"config_extension_exclusive": {
"type": "boolean"
},
+ "config_extension_netrc": {
+ "type": "boolean"
+ },
"config_file": {
"type": "string"
},
@@ -5463,6 +5478,9 @@ const docTemplate = `{
"registry_extension_endpoint": {
"type": "string"
},
+ "registry_extension_netrc": {
+ "type": "boolean"
+ },
"require_approval": {
"type": "string"
},
diff --git a/docs/docs/20-usage/72-extensions/40-configuration-extension.md b/docs/docs/20-usage/72-extensions/40-configuration-extension.md
index 13125743bfa..f263a0693c8 100644
--- a/docs/docs/20-usage/72-extensions/40-configuration-extension.md
+++ b/docs/docs/20-usage/72-extensions/40-configuration-extension.md
@@ -39,12 +39,16 @@ You can enable the exclusive setting (both globally and on a per-repo level). Th
The extension receives an HTTP POST request with the following JSON payload:
+:::info
+The `netrc` field is only included in the request when the global `WOODPECKER_CONFIG_EXTENSION_NETRC` is set to `true` (default: `false`) or the per-repo "Send netrc credentials" is checked.
+:::
+
```ts
class Request {
repo: Repo;
pipeline: Pipeline;
- netrc: Netrc;
- configuration: {
+ netrc?: Netrc; // only included when netrc sending is enabled (see above)
+ configuration?: {
// list of configurations. Not send if there was none.
name: string; // filename of the configuration file
data: string; // content of the configuration file
@@ -131,7 +135,12 @@ Example request:
"name": ".woodpecker.yaml",
"data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from Repo (.woodpecker.yaml)\"\n"
}
- ]
+ ],
+ "netrc": {
+ "machine": "myforge.com",
+ "login": "myUser",
+ "password": "forge-access-token"
+ }
}
```
diff --git a/docs/docs/20-usage/72-extensions/50-registry-extension.md b/docs/docs/20-usage/72-extensions/50-registry-extension.md
index dd9d0831b6a..c4f46032786 100644
--- a/docs/docs/20-usage/72-extensions/50-registry-extension.md
+++ b/docs/docs/20-usage/72-extensions/50-registry-extension.md
@@ -33,10 +33,15 @@ When a pipeline is triggered, Woodpecker will fetch the credentials from your se
The extension receives an HTTP POST request with the following JSON payload:
+:::info
+The `netrc` field is only included in the request when the global `WOODPECKER_REGISTRY_EXTENSION_NETRC` is set to `true` (default: `false`) or the per-repo "Send netrc credentials" is checked.
+:::
+
```ts
class Request {
repo: Repo;
pipeline: Pipeline;
+ netrc?: Netrc; // only included when netrc sending is enabled (see above)
}
```
@@ -44,6 +49,11 @@ Checkout the following models for more information:
- [repo model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/repo.go)
- [pipeline model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/pipeline.go)
+- [netrc model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/netrc.go)
+
+:::tip
+The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge (Github or Gitlab, ...) API to get more information about the repository.
+:::
Example request:
@@ -111,6 +121,11 @@ Example request:
"title": "",
"updated_at": 0,
"verified": false
+ },
+ "netrc": {
+ "machine": "myforge.com",
+ "login": "myUser",
+ "password": "forge-access-token"
}
}
```
diff --git a/docs/docs/30-administration/10-configuration/10-server.md b/docs/docs/30-administration/10-configuration/10-server.md
index c18a2836e2a..3318f7c95f3 100644
--- a/docs/docs/30-administration/10-configuration/10-server.md
+++ b/docs/docs/30-administration/10-configuration/10-server.md
@@ -993,6 +993,19 @@ If you enable this, all repos will exclusively use the global config service end
---
+### CONFIG_EXTENSION_NETRC
+
+- Name: `WOODPECKER_CONFIG_EXTENSION_NETRC`
+- Default: false
+
+Send `netrc` to the config extension endpoint.
+
+:::warning
+The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge API to get more information about the repository.
+:::
+
+---
+
### SECRET_EXTENSION_ENDPOINT
- Name: `WOODPECKER_SECRET_EXTENSION_ENDPOINT`
@@ -1024,6 +1037,19 @@ Specify a registry extension endpoint, see [Registry Extension](../../20-usage/7
---
+### REGISTRY_EXTENSION_NETRC
+
+- Name: `WOODPECKER_REGISTRY_EXTENSION_NETRC`
+- Default: false
+
+Send `netrc` to the registry extension endpoint.
+
+:::warning
+The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge API to get more information about the repository.
+:::
+
+---
+
### EXTENSIONS_ALLOWED_HOSTS
- Name: `WOODPECKER_EXTENSIONS_ALLOWED_HOSTS`
diff --git a/server/api/hook_test.go b/server/api/hook_test.go
index a6c2070eb5a..7185cec5dc4 100644
--- a/server/api/hook_test.go
+++ b/server/api/hook_test.go
@@ -97,9 +97,9 @@ func TestHook(t *testing.T) {
_forge.On("Netrc", mock.Anything, mock.Anything).Return(&model.Netrc{}, nil)
_store.On("GetPipelineLastBefore", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
_manager.On("SecretServiceFromRepo", repo).Return(_secretService)
- _secretService.On("SecretListPipeline", mock.Anything, repo, mock.Anything, mock.Anything).Return(nil, nil)
+ _secretService.On("SecretListPipeline", mock.Anything, repo, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
_manager.On("RegistryServiceFromRepo", repo).Return(_registryService)
- _registryService.On("RegistryListPipeline", mock.Anything, repo, mock.Anything).Return(nil, nil)
+ _registryService.On("RegistryListPipeline", mock.Anything, repo, mock.Anything, mock.Anything).Return(nil, nil)
_manager.On("EnvironmentService").Return(nil)
_store.On("DeletePipeline", mock.Anything).Return(nil)
diff --git a/server/api/pipeline_test.go b/server/api/pipeline_test.go
index 1164fd2184f..2c14d480a79 100644
--- a/server/api/pipeline_test.go
+++ b/server/api/pipeline_test.go
@@ -304,8 +304,8 @@ func TestCreatePipeline(t *testing.T) {
}, nil).Maybe()
mockForge.On("Status", mock.Anything, fakeUser, fakeRepo, mock.Anything, mock.Anything).Return(nil).Maybe()
- mockSecretService.On("SecretListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything).Return([]*model.Secret{}, nil).Maybe()
- mockRegistryService.On("RegistryListPipeline", mock.Anything, fakeRepo, mock.Anything).Return([]*model.Registry{}, nil).Maybe()
+ mockSecretService.On("SecretListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Secret{}, nil).Maybe()
+ mockRegistryService.On("RegistryListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything).Return([]*model.Registry{}, nil).Maybe()
mockManager := manager_mocks.NewMockManager(t)
mockManager.On("ForgeFromRepo", fakeRepo).Return(mockForge, nil)
@@ -377,8 +377,8 @@ func TestCreatePipeline(t *testing.T) {
}, nil).Maybe()
mockForge.On("Status", mock.Anything, fakeUser, fakeRepo, mock.Anything, mock.Anything).Return(nil).Maybe()
- mockSecretService.On("SecretListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything).Return([]*model.Secret{}, nil).Maybe()
- mockRegistryService.On("RegistryListPipeline", mock.Anything, fakeRepo, mock.Anything).Return([]*model.Registry{}, nil).Maybe()
+ mockSecretService.On("SecretListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Secret{}, nil).Maybe()
+ mockRegistryService.On("RegistryListPipeline", mock.Anything, fakeRepo, mock.Anything, mock.Anything).Return([]*model.Registry{}, nil).Maybe()
mockManager := manager_mocks.NewMockManager(t)
mockManager.On("ForgeFromRepo", fakeRepo).Return(mockForge, nil)
diff --git a/server/api/repo.go b/server/api/repo.go
index d1cf3d2e75d..3a6b89e23eb 100644
--- a/server/api/repo.go
+++ b/server/api/repo.go
@@ -296,9 +296,15 @@ func PatchRepo(c *gin.Context) {
if in.ConfigExtensionExclusive != nil {
repo.ConfigExtensionExclusive = *in.ConfigExtensionExclusive
}
+ if in.ConfigExtensionNetrc != nil {
+ repo.ConfigExtensionNetrc = *in.ConfigExtensionNetrc
+ }
if in.RegistryExtensionEndpoint != nil {
repo.RegistryExtensionEndpoint = *in.RegistryExtensionEndpoint
}
+ if in.RegistryExtensionNetrc != nil {
+ repo.RegistryExtensionNetrc = *in.RegistryExtensionNetrc
+ }
if in.SecretExtensionEndpoint != nil {
repo.SecretExtensionEndpoint = *in.SecretExtensionEndpoint
}
diff --git a/server/model/repo.go b/server/model/repo.go
index 8aec1d95a54..577979b6695 100644
--- a/server/model/repo.go
+++ b/server/model/repo.go
@@ -74,7 +74,9 @@ type Repo struct {
NetrcTrustedPlugins []string `json:"netrc_trusted" xorm:"json 'netrc_trusted'"`
ConfigExtensionEndpoint string `json:"config_extension_endpoint" xorm:"varchar(500) 'config_extension_endpoint'"`
ConfigExtensionExclusive bool `json:"config_extension_exclusive" xorm:"DEFAULT FALSE 'config_extension_exclusive'"`
+ ConfigExtensionNetrc bool `json:"config_extension_netrc" xorm:"DEFAULT FALSE 'config_extension_netrc'"`
RegistryExtensionEndpoint string `json:"registry_extension_endpoint" xorm:"varchar(500) 'registry_extension_endpoint'"`
+ RegistryExtensionNetrc bool `json:"registry_extension_netrc" xorm:"DEFAULT FALSE 'registry_extension_netrc'"`
SecretExtensionEndpoint string `json:"secret_extension_endpoint" xorm:"varchar(500) 'secret_extension_endpoint'"`
SecretExtensionNetrc bool `json:"secret_extension_netrc" xorm:"DEFAULT FALSE 'secret_extension_netrc'"`
} // @name Repo
@@ -149,7 +151,9 @@ type RepoPatch struct {
Trusted *TrustedConfigurationPatch `json:"trusted"`
ConfigExtensionEndpoint *string `json:"config_extension_endpoint,omitempty"`
ConfigExtensionExclusive *bool `json:"config_extension_exclusive"`
+ ConfigExtensionNetrc *bool `json:"config_extension_netrc"`
RegistryExtensionEndpoint *string `json:"registry_extension_endpoint,omitempty"`
+ RegistryExtensionNetrc *bool `json:"registry_extension_netrc"`
SecretExtensionEndpoint *string `json:"secret_extension_endpoint,omitempty"`
SecretExtensionNetrc *bool `json:"secret_extension_netrc,omitempty"`
} // @name RepoPatch
diff --git a/server/pipeline/items.go b/server/pipeline/items.go
index 57f54ddfc5f..bb33c672336 100644
--- a/server/pipeline/items.go
+++ b/server/pipeline/items.go
@@ -67,7 +67,7 @@ func parsePipeline(ctx context.Context, forge forge.Forge, store store.Store, cu
}
registryService := server.Config.Services.Manager.RegistryServiceFromRepo(repo)
- regs, err := registryService.RegistryListPipeline(ctx, repo, currentPipeline)
+ regs, err := registryService.RegistryListPipeline(ctx, repo, currentPipeline, netrc)
if err != nil {
log.Error().Err(err).Msgf("error getting registry credentials for %s#%d", repo.FullName, currentPipeline.Number)
}
diff --git a/server/pipeline/items_test.go b/server/pipeline/items_test.go
index 8cef54803a4..645ced5d1ce 100644
--- a/server/pipeline/items_test.go
+++ b/server/pipeline/items_test.go
@@ -132,7 +132,7 @@ steps:
server.Config.Services.Manager = mockManager
secretService := secret_service_mocks.NewMockService(t)
- secretService.On("SecretListPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Secret{
+ secretService.On("SecretListPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Secret{
{
Name: "hello",
Value: "secret world",
@@ -141,7 +141,7 @@ steps:
mockManager.On("SecretServiceFromRepo", mock.Anything).Return(secretService, nil)
registryService := registry_service_mocks.NewMockService(t)
- registryService.On("RegistryListPipeline", mock.Anything, mock.Anything, mock.Anything).Return([]*model.Registry{
+ registryService.On("RegistryListPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*model.Registry{
{
Address: "docker.io",
Username: "user",
diff --git a/server/services/config/combined_test.go b/server/services/config/combined_test.go
index e4a71596c99..3c274bf92d5 100644
--- a/server/services/config/combined_test.go
+++ b/server/services/config/combined_test.go
@@ -191,7 +191,7 @@ func TestFetchFromConfigService(t *testing.T) {
client, err := utils.NewHTTPClient(privEd25519Key, "loopback")
require.NoError(t, err)
- httpFetcher := config.NewHTTP(ts.URL+"/", client)
+ httpFetcher := config.NewHTTP(ts.URL+"/", client, true)
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
diff --git a/server/services/config/http.go b/server/services/config/http.go
index ca50e7bc66c..28c76fddba2 100644
--- a/server/services/config/http.go
+++ b/server/services/config/http.go
@@ -28,8 +28,9 @@ import (
)
type httpService struct {
- endpoint string
- client *utils.Client
+ endpoint string
+ client *utils.Client
+ includeNetrc bool
}
// configData same as forge.FileMeta but with json tags and string data.
@@ -49,16 +50,11 @@ type responseStructure struct {
Configs []*configData `json:"configs"`
}
-func NewHTTP(endpoint string, client *utils.Client) Service {
- return &httpService{endpoint, client}
+func NewHTTP(endpoint string, client *utils.Client, includeNetrc bool) Service {
+ return &httpService{endpoint, client, includeNetrc}
}
func (h *httpService) Fetch(ctx context.Context, forge forge.Forge, user *model.User, repo *model.Repo, pipeline *model.Pipeline, oldConfigData []*types.FileMeta, _ bool) ([]*types.FileMeta, error) {
- netrc, err := forge.Netrc(user, repo)
- if err != nil {
- return nil, fmt.Errorf("could not get Netrc data from forge: %w", err)
- }
-
configuration := make([]*configData, len(oldConfigData))
for i, oldConfig := range oldConfigData {
configuration[i] = &configData{Name: oldConfig.Name, Data: string(oldConfig.Data)}
@@ -68,10 +64,17 @@ func (h *httpService) Fetch(ctx context.Context, forge forge.Forge, user *model.
body := requestStructure{
Repo: repo,
Pipeline: pipeline,
- Netrc: netrc,
Configuration: configuration,
}
+ if h.includeNetrc {
+ netrc, err := forge.Netrc(user, repo)
+ if err != nil {
+ return nil, fmt.Errorf("could not get Netrc data from forge: %w", err)
+ }
+ body.Netrc = netrc
+ }
+
status, err := h.client.Send(ctx, http.MethodPost, h.endpoint, body, response)
if err != nil && status != http.StatusNoContent {
return nil, fmt.Errorf("failed to fetch config via http (status: %d): %w", status, err)
diff --git a/server/services/manager.go b/server/services/manager.go
index 50a91d61c4b..411fa6611b3 100644
--- a/server/services/manager.go
+++ b/server/services/manager.go
@@ -88,7 +88,7 @@ func NewManager(c *cli.Command, store store.Store, setupForge SetupForge) (Manag
signaturePublicKey: signaturePublicKey,
store: store,
secret: setupSecretService(store, c.String("secret-extension-endpoint"), client, c.Bool("secret-extension-netrc")),
- registry: setupRegistryService(store, c.String("docker-config"), c.String("registry-extension-endpoint"), client),
+ registry: setupRegistryService(store, c.String("docker-config"), c.String("registry-extension-endpoint"), c.Bool("registry-extension-netrc"), client),
config: configService,
environment: environment.Parse(c.StringSlice("environment")),
forgeCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[int64, forge.Forge]()),
@@ -115,7 +115,7 @@ func (m *manager) SecretService() secret.Service {
func (m *manager) RegistryServiceFromRepo(repo *model.Repo) registry.Service {
if repo.RegistryExtensionEndpoint != "" {
- return registry.NewWithExtension(m.registry, registry.NewHTTP(strings.TrimRight(repo.RegistryExtensionEndpoint, "/"), m.client))
+ return registry.NewWithExtension(m.registry, registry.NewHTTP(strings.TrimRight(repo.RegistryExtensionEndpoint, "/"), m.client, repo.RegistryExtensionNetrc))
}
return m.RegistryService()
}
@@ -127,9 +127,9 @@ func (m *manager) RegistryService() registry.Service {
func (m *manager) ConfigServiceFromRepo(repo *model.Repo) config.Service {
if repo.ConfigExtensionEndpoint != "" {
if repo.ConfigExtensionExclusive {
- return config.NewHTTP(strings.TrimRight(repo.ConfigExtensionEndpoint, "/"), m.client)
+ return config.NewHTTP(strings.TrimRight(repo.ConfigExtensionEndpoint, "/"), m.client, repo.ConfigExtensionNetrc)
}
- return config.NewCombined(m.config, config.NewHTTP(strings.TrimRight(repo.ConfigExtensionEndpoint, "/"), m.client))
+ return config.NewCombined(m.config, config.NewHTTP(strings.TrimRight(repo.ConfigExtensionEndpoint, "/"), m.client, repo.ConfigExtensionNetrc))
}
return m.config
diff --git a/server/services/registry/combined.go b/server/services/registry/combined.go
index d0d5836d352..d28d104a761 100644
--- a/server/services/registry/combined.go
+++ b/server/services/registry/combined.go
@@ -43,8 +43,8 @@ func (c *combined) RegistryList(repo *model.Repo, p *model.ListOptions) ([]*mode
return c.dbRegistry.RegistryList(repo, p)
}
-func (c *combined) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error) {
- dbRegistries, err := c.dbRegistry.RegistryListPipeline(ctx, repo, pipeline)
+func (c *combined) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc) ([]*model.Registry, error) {
+ dbRegistries, err := c.dbRegistry.RegistryListPipeline(ctx, repo, pipeline, netrc)
if err != nil {
return nil, err
}
diff --git a/server/services/registry/combined_test.go b/server/services/registry/combined_test.go
index 2ff8938708b..72010fd31ec 100644
--- a/server/services/registry/combined_test.go
+++ b/server/services/registry/combined_test.go
@@ -85,6 +85,7 @@ func TestCombinedRegistryListPipeline(t *testing.T) {
t.Context(),
&model.Repo{ID: 1, Name: tt.repoName},
&model.Pipeline{},
+ nil,
)
if tt.expectedError {
require.Error(t, err, "expected an error")
diff --git a/server/services/registry/db.go b/server/services/registry/db.go
index f06842429aa..a55e9572ae4 100644
--- a/server/services/registry/db.go
+++ b/server/services/registry/db.go
@@ -38,7 +38,7 @@ func (d *db) RegistryList(repo *model.Repo, p *model.ListOptions) ([]*model.Regi
return d.store.RegistryList(repo, false, p)
}
-func (d *db) RegistryListPipeline(_ context.Context, repo *model.Repo, _ *model.Pipeline) ([]*model.Registry, error) {
+func (d *db) RegistryListPipeline(_ context.Context, repo *model.Repo, _ *model.Pipeline, _ *model.Netrc) ([]*model.Registry, error) {
r, err := d.store.RegistryList(repo, true, &model.ListOptions{All: true})
if err != nil {
return nil, err
diff --git a/server/services/registry/http.go b/server/services/registry/http.go
index a1b062d8da0..bd36dc0e5df 100644
--- a/server/services/registry/http.go
+++ b/server/services/registry/http.go
@@ -24,13 +24,15 @@ import (
)
type httpExtension struct {
- endpoint string
- client *utils.Client
+ endpoint string
+ client *utils.Client
+ includeNetrc bool
}
type requestStructure struct {
Repo *model.Repo `json:"repo"`
Pipeline *model.Pipeline `json:"pipeline"`
+ Netrc *model.Netrc `json:"netrc,omitempty"`
}
type responseStructure struct {
@@ -44,17 +46,20 @@ type registryData struct {
}
// NewHTTP returns a new HTTP registry extension client.
-func NewHTTP(endpoint string, client *utils.Client) *httpExtension {
- return &httpExtension{endpoint, client}
+func NewHTTP(endpoint string, client *utils.Client, includeNetrc bool) *httpExtension {
+ return &httpExtension{endpoint, client, includeNetrc}
}
// RegistryListPipeline fetches registry credentials from an external HTTP extension.
-func (h *httpExtension) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error) {
+func (h *httpExtension) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc) ([]*model.Registry, error) {
response := new(responseStructure)
body := requestStructure{
Repo: repo,
Pipeline: pipeline,
}
+ if h.includeNetrc {
+ body.Netrc = netrc
+ }
status, err := h.client.Send(ctx, http.MethodPost, h.endpoint, body, response)
if err != nil && status != http.StatusNoContent {
diff --git a/server/services/registry/mocks/mock_Service.go b/server/services/registry/mocks/mock_Service.go
index 109460c3ab4..3339b79e60c 100644
--- a/server/services/registry/mocks/mock_Service.go
+++ b/server/services/registry/mocks/mock_Service.go
@@ -873,8 +873,8 @@ func (_c *MockService_RegistryList_Call) RunAndReturn(run func(repo *model.Repo,
}
// RegistryListPipeline provides a mock function for the type MockService
-func (_mock *MockService) RegistryListPipeline(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error) {
- ret := _mock.Called(context1, repo, pipeline)
+func (_mock *MockService) RegistryListPipeline(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc) ([]*model.Registry, error) {
+ ret := _mock.Called(context1, repo, pipeline, netrc)
if len(ret) == 0 {
panic("no return value specified for RegistryListPipeline")
@@ -882,18 +882,18 @@ func (_mock *MockService) RegistryListPipeline(context1 context.Context, repo *m
var r0 []*model.Registry
var r1 error
- if returnFunc, ok := ret.Get(0).(func(context.Context, *model.Repo, *model.Pipeline) ([]*model.Registry, error)); ok {
- return returnFunc(context1, repo, pipeline)
+ if returnFunc, ok := ret.Get(0).(func(context.Context, *model.Repo, *model.Pipeline, *model.Netrc) ([]*model.Registry, error)); ok {
+ return returnFunc(context1, repo, pipeline, netrc)
}
- if returnFunc, ok := ret.Get(0).(func(context.Context, *model.Repo, *model.Pipeline) []*model.Registry); ok {
- r0 = returnFunc(context1, repo, pipeline)
+ if returnFunc, ok := ret.Get(0).(func(context.Context, *model.Repo, *model.Pipeline, *model.Netrc) []*model.Registry); ok {
+ r0 = returnFunc(context1, repo, pipeline, netrc)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Registry)
}
}
- if returnFunc, ok := ret.Get(1).(func(context.Context, *model.Repo, *model.Pipeline) error); ok {
- r1 = returnFunc(context1, repo, pipeline)
+ if returnFunc, ok := ret.Get(1).(func(context.Context, *model.Repo, *model.Pipeline, *model.Netrc) error); ok {
+ r1 = returnFunc(context1, repo, pipeline, netrc)
} else {
r1 = ret.Error(1)
}
@@ -909,11 +909,12 @@ type MockService_RegistryListPipeline_Call struct {
// - context1 context.Context
// - repo *model.Repo
// - pipeline *model.Pipeline
-func (_e *MockService_Expecter) RegistryListPipeline(context1 interface{}, repo interface{}, pipeline interface{}) *MockService_RegistryListPipeline_Call {
- return &MockService_RegistryListPipeline_Call{Call: _e.mock.On("RegistryListPipeline", context1, repo, pipeline)}
+// - netrc *model.Netrc
+func (_e *MockService_Expecter) RegistryListPipeline(context1 interface{}, repo interface{}, pipeline interface{}, netrc interface{}) *MockService_RegistryListPipeline_Call {
+ return &MockService_RegistryListPipeline_Call{Call: _e.mock.On("RegistryListPipeline", context1, repo, pipeline, netrc)}
}
-func (_c *MockService_RegistryListPipeline_Call) Run(run func(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline)) *MockService_RegistryListPipeline_Call {
+func (_c *MockService_RegistryListPipeline_Call) Run(run func(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc)) *MockService_RegistryListPipeline_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
@@ -927,10 +928,15 @@ func (_c *MockService_RegistryListPipeline_Call) Run(run func(context1 context.C
if args[2] != nil {
arg2 = args[2].(*model.Pipeline)
}
+ var arg3 *model.Netrc
+ if args[3] != nil {
+ arg3 = args[3].(*model.Netrc)
+ }
run(
arg0,
arg1,
arg2,
+ arg3,
)
})
return _c
@@ -941,7 +947,7 @@ func (_c *MockService_RegistryListPipeline_Call) Return(registrys []*model.Regis
return _c
}
-func (_c *MockService_RegistryListPipeline_Call) RunAndReturn(run func(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error)) *MockService_RegistryListPipeline_Call {
+func (_c *MockService_RegistryListPipeline_Call) RunAndReturn(run func(context1 context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc) ([]*model.Registry, error)) *MockService_RegistryListPipeline_Call {
_c.Call.Return(run)
return _c
}
diff --git a/server/services/registry/service.go b/server/services/registry/service.go
index 5e45d272c7e..6da947446d3 100644
--- a/server/services/registry/service.go
+++ b/server/services/registry/service.go
@@ -22,7 +22,7 @@ import (
// Service defines a service for managing registries.
type Service interface {
- RegistryListPipeline(context.Context, *model.Repo, *model.Pipeline) ([]*model.Registry, error)
+ RegistryListPipeline(context.Context, *model.Repo, *model.Pipeline, *model.Netrc) ([]*model.Registry, error)
// Repository registries
RegistryFind(*model.Repo, string) (*model.Registry, error)
RegistryList(*model.Repo, *model.ListOptions) ([]*model.Registry, error)
diff --git a/server/services/registry/with_extension.go b/server/services/registry/with_extension.go
index 0e233c4accb..3b3b8343241 100644
--- a/server/services/registry/with_extension.go
+++ b/server/services/registry/with_extension.go
@@ -34,15 +34,15 @@ func NewWithExtension(base Service, extension *httpExtension) Service {
return &withExtension{base, extension}
}
-func (w *withExtension) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error) {
+func (w *withExtension) RegistryListPipeline(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, netrc *model.Netrc) ([]*model.Registry, error) {
// Get registries from base service
- baseRegistries, err := w.base.RegistryListPipeline(ctx, repo, pipeline)
+ baseRegistries, err := w.base.RegistryListPipeline(ctx, repo, pipeline, netrc)
if err != nil {
return nil, err
}
// Get registries from HTTP extension
- extensionRegistries, err := w.extension.RegistryListPipeline(ctx, repo, pipeline)
+ extensionRegistries, err := w.extension.RegistryListPipeline(ctx, repo, pipeline, netrc)
if err != nil {
// Log the error but don't fail - use base registries only
log.Warn().Err(err).Msg("failed to fetch registries from extension")
diff --git a/server/services/registry/with_extension_test.go b/server/services/registry/with_extension_test.go
index 4b7d571e826..f0ea500f36a 100644
--- a/server/services/registry/with_extension_test.go
+++ b/server/services/registry/with_extension_test.go
@@ -145,7 +145,7 @@ func TestWithExtensionRegistryListPipeline(t *testing.T) {
client, err := utils.NewHTTPClient(privEd25519Key, "loopback")
require.NoError(t, err)
- httpExtension := NewHTTP(ts.URL, client)
+ httpExtension := NewHTTP(ts.URL, client, true)
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
@@ -158,6 +158,7 @@ func TestWithExtensionRegistryListPipeline(t *testing.T) {
t.Context(),
&model.Repo{ID: 1, Name: tt.repoName},
&model.Pipeline{},
+ nil,
)
if tt.expectedError {
require.Error(t, err, "expected an error")
diff --git a/server/services/secret/combined_test.go b/server/services/secret/combined_test.go
index b522773e31d..a2a4ee05e74 100644
--- a/server/services/secret/combined_test.go
+++ b/server/services/secret/combined_test.go
@@ -146,7 +146,7 @@ func TestCombinedSecretListPipeline(t *testing.T) {
client, err := utils.NewHTTPClient(privEd25519Key, "loopback")
require.NoError(t, err)
- httpExtension := secret.NewHTTP(ts.URL, client, false)
+ httpExtension := secret.NewHTTP(ts.URL, client, true)
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
diff --git a/server/services/setup.go b/server/services/setup.go
index 5a2d40b2f14..d11395f2ceb 100644
--- a/server/services/setup.go
+++ b/server/services/setup.go
@@ -35,7 +35,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v3/server/store/types"
)
-func setupRegistryService(store store.Store, dockerConfig, endpoint string, client *utils.Client) registry.Service {
+func setupRegistryService(store store.Store, dockerConfig, endpoint string, includeNetrc bool, client *utils.Client) registry.Service {
var service registry.Service
if dockerConfig != "" {
service = registry.NewCombined(
@@ -48,7 +48,7 @@ func setupRegistryService(store store.Store, dockerConfig, endpoint string, clie
// Wrap with global HTTP extension if configured
if endpoint != "" {
- service = registry.NewWithExtension(service, registry.NewHTTP(endpoint, client))
+ service = registry.NewWithExtension(service, registry.NewHTTP(endpoint, client, includeNetrc))
}
return service
@@ -79,7 +79,7 @@ func setupConfigService(c *cli.Command, client *utils.Client) (config.Service, e
configFetcher := config.NewForge(timeout, retries)
if endpoint := c.String("config-extension-endpoint"); endpoint != "" {
- httpFetcher := config.NewHTTP(endpoint, client)
+ httpFetcher := config.NewHTTP(endpoint, client, c.Bool("config-extension-netrc"))
if c.Bool("config-extension-exclusive") {
return httpFetcher, nil
}
diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json
index 021e51b124f..df93e2052fe 100644
--- a/web/src/assets/locales/en.json
+++ b/web/src/assets/locales/en.json
@@ -543,8 +543,8 @@
"config_extension_exclusive": "Exclusive",
"config_extension_exclusive_desc": "If enabled, will skip all other ways of configuration fetching, including the forge.",
"secret_extension_endpoint": "Secret extension endpoint",
- "secret_extension_netrc": "Include netrc credentials",
- "secret_extension_netrc_desc": "Send forge netrc credentials to the secret extension.",
+ "extension_netrc": "Include netrc credentials",
+ "extension_netrc_desc": "Send forge netrc credentials to the extension.",
"extensions_signatures_public_key": "Public key for signatures",
"extensions_signatures_public_key_description": "This public key should be used by your extensions to verify webhook calls from Woodpecker.",
"extensions_configuration_saved": "Extensions configuration saved",
diff --git a/web/src/lib/api/types/repo.ts b/web/src/lib/api/types/repo.ts
index a52f0968cbf..79aa0d4a09b 100644
--- a/web/src/lib/api/types/repo.ts
+++ b/web/src/lib/api/types/repo.ts
@@ -85,9 +85,15 @@ export interface Repo {
config_extension_exclusive: boolean;
+ // Whether to include netrc credentials in config extension requests
+ config_extension_netrc: boolean;
+
// Endpoint for registry extensions
registry_extension_endpoint: string;
+ // Whether to include netrc credentials in registry extension requests
+ registry_extension_netrc: boolean;
+
// Endpoint for secret extensions
secret_extension_endpoint: string;
@@ -128,7 +134,9 @@ export type ExtensionSettings = Pick<
Repo,
| 'config_extension_endpoint'
| 'config_extension_exclusive'
+ | 'config_extension_netrc'
| 'registry_extension_endpoint'
+ | 'registry_extension_netrc'
| 'secret_extension_endpoint'
| 'secret_extension_netrc'
>;
diff --git a/web/src/views/repo/settings/Extensions.vue b/web/src/views/repo/settings/Extensions.vue
index 5539e48fe4a..2431f5ee7dd 100644
--- a/web/src/views/repo/settings/Extensions.vue
+++ b/web/src/views/repo/settings/Extensions.vue
@@ -16,6 +16,12 @@
:label="$t('config_extension_exclusive')"
:description="$t('config_extension_exclusive_desc')"
/>
+
+
@@ -23,6 +29,13 @@
v-model="extensions.registry_extension_endpoint"
:placeholder="$t('extension_endpoint_placeholder')"
/>
+
+
@@ -31,8 +44,8 @@
@@ -74,7 +87,9 @@ onMounted(async () => {
const extensions = ref({
config_extension_endpoint: repo.value.config_extension_endpoint,
config_extension_exclusive: repo.value.config_extension_exclusive,
+ config_extension_netrc: repo.value.config_extension_netrc,
registry_extension_endpoint: repo.value.registry_extension_endpoint,
+ registry_extension_netrc: repo.value.registry_extension_netrc,
secret_extension_endpoint: repo.value.secret_extension_endpoint,
secret_extension_netrc: repo.value.secret_extension_netrc,
});