Skip to content

Commit

Permalink
Add support for multiple git resolver configurations
Browse files Browse the repository at this point in the history
This will add support for providing multiple configurations
in git resolver configmap
Configurations can be provided using identifier and identifier
can be passed as a param in the taskrun/pipelinerun to resolver

Old format is supported to provide backward compatability and
docs added in details about how to use the feature.

Unit and e2e test added

Fixes #5487
  • Loading branch information
piyush-garg committed Sep 15, 2024
1 parent cd8a41e commit 52b64a2
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 54 deletions.
111 changes: 107 additions & 4 deletions docs/git-resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,6 @@ Note that not all `go-scm` implementations have been tested with the `git` resol
* BitBucket Server
* BitBucket Cloud

Fetching from multiple Git providers with different configuration is not
supported. You can use the [http resolver](./http-resolver.md) to fetch URL
from another provider with different credentials.

#### Task Resolution

```yaml
Expand Down Expand Up @@ -195,6 +191,113 @@ spec:
value: Ranni
```

### Specifying Configuration for Multiple Git Providers

You can specify configurations for multiple providers and even multiple configurations for same provider to use in
different tekton resources. You need to pass the identifier mentioned in configmap as an extra param with key
`token-identifier`. If no identifier passed, `default` value will be considered. You can specify default configuration
in configmap either by mentioning no identifier or by using default identifier `identifier.default`

### Sample Configmap

You can add multiple configuration to `git-resolver-configmap` like this. All keys mentioned above are supported.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: git-resolver-config
namespace: tekton-pipelines-resolvers
labels:
app.kubernetes.io/component: resolvers
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pipelines
data:
# configuration 1, default one to use if no identifier provided
fetch-timeout: "1m"
default-url: "https://github.com/tektoncd/catalog.git"
default-revision: "main"
scm-type: "github"
server-url: ""
api-token-secret-name: ""
api-token-secret-key: ""
api-token-secret-namespace: "default"
default-org: ""
# configuration 2, will be used if token-identifier params passed with value test1
identifier.test1.fetch-timeout: "5m"
identifier.test1.default-url: ""
identifier.test1.default-revision: "stable"
identifier.test1.scm-type: "github"
identifier.test1.server-url: "api.internal-github.com"
identifier.test1.api-token-secret-name: "test1-secret"
identifier.test1.api-token-secret-key: "token"
identifier.test1.api-token-secret-namespace: "test1"
identifier.test1.default-org: "tektoncd"
# configuration 3, will be used if token-identifier params passed with value test2
identifier.test2.fetch-timeout: "10m"
identifier.test2.default-url: ""
identifier.test2.default-revision: "stable"
identifier.test2.scm-type: "gitlab"
identifier.test2.server-url: "api.internal-gitlab.com"
identifier.test2.api-token-secret-name: "test2-secret"
identifier.test2.api-token-secret-key: "pat"
identifier.test2.api-token-secret-namespace: "test2"
identifier.test2.default-org: "tektoncd-infra"
```

#### Task Resolution

You can pass one more param `token-identifer` with value mentioned in configmap as identifier.

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: git-api-demo-tr
spec:
taskRef:
resolver: git
params:
- name: org
value: tektoncd
- name: repo
value: catalog
- name: revision
value: main
- name: pathInRepo
value: task/git-clone/0.6/git-clone.yaml
- name: token-identifier
value: test1
```

#### Pipeline resolution

```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: git-api-demo-pr
spec:
pipelineRef:
resolver: git
params:
- name: org
value: tektoncd
- name: repo
value: catalog
- name: revision
value: main
- name: pathInRepo
value: pipeline/simple/0.1/simple.yaml
- name: token-identifier
value: test2
params:
- name: name
value: Ranni
```

## `ResolutionRequest` Status
`ResolutionRequest.Status.RefSource` field captures the source where the remote resource came from. It includes the 3 subfields: `url`, `digest` and `entrypoint`.
- `url`
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/tektoncd/pipeline

go 1.22

toolchain go1.22.5

require (
Expand Down
2 changes: 1 addition & 1 deletion pkg/remoteresolution/resolver/framework/fakeresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,6 @@ func (r *FakeResolver) Resolve(_ context.Context, req *v1beta1.ResolutionRequest
var _ framework.TimedResolution = &FakeResolver{}

// GetResolutionTimeout returns the configured timeout for the reconciler, or the default time.Duration if not configured.
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration) time.Duration {
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration, params map[string]string) time.Duration {
return framework.GetResolutionTimeout(r.Timeout, defaultTimeout)
}
7 changes: 6 additions & 1 deletion pkg/remoteresolution/resolver/framework/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,14 @@ func (r *Reconciler) resolve(ctx context.Context, key string, rr *v1beta1.Resolu
errChan := make(chan error)
resourceChan := make(chan framework.ResolvedResource)

paramsMap := make(map[string]string)
for _, p := range rr.Spec.Params {
paramsMap[p.Name] = p.Value.StringVal
}

timeoutDuration := defaultMaximumResolutionDuration
if timed, ok := r.resolver.(framework.TimedResolution); ok {
timeoutDuration = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration)
timeoutDuration = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration, paramsMap)
}

// A new context is created for resolution so that timeouts can
Expand Down
12 changes: 9 additions & 3 deletions pkg/remoteresolution/resolver/git/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,15 @@ var _ resolutionframework.TimedResolution = &Resolver{}
// GetResolutionTimeout returns a time.Duration for the amount of time a
// single git fetch may take. This can be configured with the
// fetch-timeout field in the git-resolver-config configmap.
func (r *Resolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration) time.Duration {
conf := resolutionframework.GetResolverConfigFromContext(ctx)
if timeoutString, ok := conf[git.DefaultTimeoutKey]; ok {
func (r *Resolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration, params map[string]string) time.Duration {
gitResolverConfig := git.GetGitConfig(ctx)
var conf git.ScmInfo
if tokenIdentifier, ok := params[git.TokenIdentifierParam]; ok {
conf = gitResolverConfig.ScmTokens[tokenIdentifier]
} else {
conf = gitResolverConfig.ScmTokens["default"]
}
if timeoutString := conf.Timeout; timeoutString != "" {
timeout, err := time.ParseDuration(timeoutString)
if err == nil {
return timeout
Expand Down
79 changes: 72 additions & 7 deletions pkg/remoteresolution/resolver/git/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func TestValidateParams_Failure(t *testing.T) {
func TestGetResolutionTimeoutDefault(t *testing.T) {
resolver := Resolver{}
defaultTimeout := 30 * time.Minute
timeout := resolver.GetResolutionTimeout(context.Background(), defaultTimeout)
timeout := resolver.GetResolutionTimeout(context.Background(), defaultTimeout, map[string]string{})
if timeout != defaultTimeout {
t.Fatalf("expected default timeout to be returned")
}
Expand All @@ -233,12 +233,28 @@ func TestGetResolutionTimeoutCustom(t *testing.T) {
gitresolution.DefaultTimeoutKey: configTimeout.String(),
}
ctx := resolutionframework.InjectResolverConfigToContext(context.Background(), config)
timeout := resolver.GetResolutionTimeout(ctx, defaultTimeout)
timeout := resolver.GetResolutionTimeout(ctx, defaultTimeout, map[string]string{})
if timeout != configTimeout {
t.Fatalf("expected timeout from config to be returned")
}
}

func TestGetResolutionTimeoutCustomIdentifier(t *testing.T) {
resolver := Resolver{}
defaultTimeout := 30 * time.Minute
configTimeout := 5 * time.Second
identifierConfigTImeout := 10 * time.Second
config := map[string]string{
gitresolution.DefaultTimeoutKey: configTimeout.String(),
"identifier.foo." + gitresolution.DefaultTimeoutKey: identifierConfigTImeout.String(),
}
ctx := resolutionframework.InjectResolverConfigToContext(context.Background(), config)
timeout := resolver.GetResolutionTimeout(ctx, defaultTimeout, map[string]string{"token-identifier": "foo"})
if timeout != identifierConfigTImeout {
t.Fatalf("expected timeout from config to be returned")
}
}

func TestResolveNotEnabled(t *testing.T) {
resolver := Resolver{}

Expand Down Expand Up @@ -268,6 +284,7 @@ type params struct {
namespace string
serverURL string
scmType string
identifier string
}

func TestResolve(t *testing.T) {
Expand Down Expand Up @@ -344,6 +361,7 @@ func TestResolve(t *testing.T) {
expectedCommitSHA string
expectedStatus *v1beta1.ResolutionRequestStatus
expectedErr error
identifierKey string
}{{
name: "clone: default revision main",
args: &params{
Expand Down Expand Up @@ -439,6 +457,46 @@ func TestResolve(t *testing.T) {
apiToken: "some-token",
expectedCommitSHA: commitSHAsInSCMRepo[0],
expectedStatus: resolution.CreateResolutionRequestStatusWithData(mainTaskYAML),
}, {
name: "api: successful task from params api information with identifier",
args: &params{
revision: "main",
pathInRepo: "tasks/example-task.yaml",
org: testOrg,
repo: testRepo,
token: "token-secret",
tokenKey: "token",
namespace: "foo",
identifier: "test",
},
config: map[string]string{
"identifier.test." + gitresolution.ServerURLKey: "fake",
"identifier.test." + gitresolution.SCMTypeKey: "fake",
},
identifierKey: "identifier.test.",
apiToken: "some-token",
expectedCommitSHA: commitSHAsInSCMRepo[0],
expectedStatus: resolution.CreateResolutionRequestStatusWithData(mainTaskYAML),
}, {
name: "api: successful task with identifier",
args: &params{
revision: "main",
pathInRepo: "tasks/example-task.yaml",
org: testOrg,
repo: testRepo,
identifier: "test",
},
config: map[string]string{
"identifier.test." + gitresolution.ServerURLKey: "fake",
"identifier.test." + gitresolution.SCMTypeKey: "fake",
"identifier.test." + gitresolution.APISecretNameKey: "token-secret",
"identifier.test." + gitresolution.APISecretKeyKey: "token",
"identifier.test." + gitresolution.APISecretNamespaceKey: system.Namespace(),
},
identifierKey: "identifier.test.",
apiToken: "some-token",
expectedCommitSHA: commitSHAsInSCMRepo[0],
expectedStatus: resolution.CreateResolutionRequestStatusWithData(mainTaskYAML),
}, {
name: "api: successful pipeline",
args: &params{
Expand Down Expand Up @@ -591,9 +649,9 @@ func TestResolve(t *testing.T) {
if cfg == nil {
cfg = make(map[string]string)
}
cfg[gitresolution.DefaultTimeoutKey] = "1m"
if cfg[gitresolution.DefaultRevisionKey] == "" {
cfg[gitresolution.DefaultRevisionKey] = plumbing.Master.Short()
cfg[tc.identifierKey+gitresolution.DefaultTimeoutKey] = "1m"
if cfg[tc.identifierKey+gitresolution.DefaultRevisionKey] == "" {
cfg[tc.identifierKey+gitresolution.DefaultRevisionKey] = plumbing.Master.Short()
}

request := createRequest(tc.args)
Expand Down Expand Up @@ -654,8 +712,8 @@ func TestResolve(t *testing.T) {

frtesting.RunResolverReconcileTest(ctx, t, d, resolver, request, expectedStatus, tc.expectedErr, func(resolver framework.Resolver, testAssets test.Assets) {
var secretName, secretNameKey, secretNamespace string
if tc.config[gitresolution.APISecretNameKey] != "" && tc.config[gitresolution.APISecretNamespaceKey] != "" && tc.config[gitresolution.APISecretKeyKey] != "" && tc.apiToken != "" {
secretName, secretNameKey, secretNamespace = tc.config[gitresolution.APISecretNameKey], tc.config[gitresolution.APISecretKeyKey], tc.config[gitresolution.APISecretNamespaceKey]
if tc.config[tc.identifierKey+gitresolution.APISecretNameKey] != "" && tc.config[tc.identifierKey+gitresolution.APISecretNamespaceKey] != "" && tc.config[tc.identifierKey+gitresolution.APISecretKeyKey] != "" && tc.apiToken != "" {
secretName, secretNameKey, secretNamespace = tc.config[tc.identifierKey+gitresolution.APISecretNameKey], tc.config[tc.identifierKey+gitresolution.APISecretKeyKey], tc.config[tc.identifierKey+gitresolution.APISecretNamespaceKey]
}
if tc.args.token != "" && tc.args.namespace != "" && tc.args.tokenKey != "" {
secretName, secretNameKey, secretNamespace = tc.args.token, tc.args.tokenKey, tc.args.namespace
Expand Down Expand Up @@ -879,6 +937,13 @@ func createRequest(args *params) *v1beta1.ResolutionRequest {
}
}

if args.identifier != "" {
rr.Spec.Params = append(rr.Spec.Params, pipelinev1.Param{
Name: gitresolution.TokenIdentifierParam,
Value: *pipelinev1.NewStructuredValues(args.identifier),
})
}

return rr
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/resolution/resolver/framework/fakeresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func Resolve(params []pipelinev1.Param, forParam map[string]*FakeResolvedResourc
var _ TimedResolution = &FakeResolver{}

// GetResolutionTimeout returns the configured timeout for the reconciler, or the default time.Duration if not configured.
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration) time.Duration {
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration, params map[string]string) time.Duration {
return GetResolutionTimeout(r.Timeout, defaultTimeout)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/resolution/resolver/framework/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ type TimedResolution interface {
// object, which includes any request-scoped data like
// resolver config and the request's originating namespace,
// along with a default.
GetResolutionTimeout(ctx context.Context, timeout time.Duration) time.Duration
GetResolutionTimeout(ctx context.Context, timeout time.Duration, params map[string]string) time.Duration
}

// ResolvedResource returns the data and annotations of a successful
Expand Down
7 changes: 6 additions & 1 deletion pkg/resolution/resolver/framework/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,14 @@ func (r *Reconciler) resolve(ctx context.Context, key string, rr *v1beta1.Resolu
errChan := make(chan error)
resourceChan := make(chan ResolvedResource)

paramsMap := make(map[string]string)
for _, p := range rr.Spec.Params {
paramsMap[p.Name] = p.Value.StringVal
}

timeoutDuration := defaultMaximumResolutionDuration
if timed, ok := r.resolver.(TimedResolution); ok {
timeoutDuration = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration)
timeoutDuration = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration, paramsMap)
}

// A new context is created for resolution so that timeouts can
Expand Down
Loading

0 comments on commit 52b64a2

Please sign in to comment.