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 unique key and that key
can be passed as a param in the taskrun/pipelinerun to resolver

Old format is supported to provide backward compatibility 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 29, 2024
1 parent 575b35c commit d30340e
Show file tree
Hide file tree
Showing 14 changed files with 813 additions and 61 deletions.
112 changes: 108 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,114 @@ 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 first add details in configmap with your unique identifier key prefix.
To use them in tektonresouce, pass the unique key mentioned in configmap as an extra param to resolver with key
`configKey` and value your unique key. If no `configKey` param passed, `default` value will be considered. You can specify
default configuration in configmap either by mentioning no unique identifier or by using default 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 configKey provided or provided with value default
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 configKey param passed with value test1
test1.fetch-timeout: "5m"
test1.default-url: ""
test1.default-revision: "stable"
test1.scm-type: "github"
test1.server-url: "api.internal-github.com"
test1.api-token-secret-name: "test1-secret"
test1.api-token-secret-key: "token"
test1.api-token-secret-namespace: "test1"
test1.default-org: "tektoncd"
# configuration 3, will be used if configKey param passed with value test2
test2.fetch-timeout: "10m"
test2.default-url: ""
test2.default-revision: "stable"
test2.scm-type: "gitlab"
test2.server-url: "api.internal-gitlab.com"
test2.api-token-secret-name: "test2-secret"
test2.api-token-secret-key: "pat"
test2.api-token-secret-namespace: "test2"
test2.default-org: "tektoncd-infra"
```

#### Task Resolution

You can pass one more param `configKey` 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: configKey
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: configKey
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
4 changes: 2 additions & 2 deletions 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 {
return framework.GetResolutionTimeout(r.Timeout, defaultTimeout)
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration, params map[string]string) (time.Duration, error) {
return framework.GetResolutionTimeout(r.Timeout, defaultTimeout), nil
}
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
13 changes: 8 additions & 5 deletions pkg/remoteresolution/resolver/git/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,16 @@ 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, error) {
conf, err := git.GetScmConfigForParamConfigKey(ctx, params)
if err != nil {
return time.Duration(0), err
}
if timeoutString := conf.Timeout; timeoutString != "" {
timeout, err := time.ParseDuration(timeoutString)
if err == nil {
return timeout
return timeout, nil
}
}
return defaultTimeout
return defaultTimeout, nil
}
88 changes: 81 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,10 @@ func TestValidateParams_Failure(t *testing.T) {
func TestGetResolutionTimeoutDefault(t *testing.T) {
resolver := Resolver{}
defaultTimeout := 30 * time.Minute
timeout := resolver.GetResolutionTimeout(context.Background(), defaultTimeout)
timeout, err := resolver.GetResolutionTimeout(context.Background(), defaultTimeout, map[string]string{})
if err != nil {
t.Fatalf("couldn't get default-timeout: %v", err)
}
if timeout != defaultTimeout {
t.Fatalf("expected default timeout to be returned")
}
Expand All @@ -233,12 +236,34 @@ func TestGetResolutionTimeoutCustom(t *testing.T) {
gitresolution.DefaultTimeoutKey: configTimeout.String(),
}
ctx := resolutionframework.InjectResolverConfigToContext(context.Background(), config)
timeout := resolver.GetResolutionTimeout(ctx, defaultTimeout)
timeout, err := resolver.GetResolutionTimeout(ctx, defaultTimeout, map[string]string{})
if err != nil {
t.Fatalf("couldn't get default-timeout: %v", err)
}
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(),
"foo." + gitresolution.DefaultTimeoutKey: identifierConfigTImeout.String(),
}
ctx := resolutionframework.InjectResolverConfigToContext(context.Background(), config)
timeout, err := resolver.GetResolutionTimeout(ctx, defaultTimeout, map[string]string{"configKey": "foo"})
if err != nil {
t.Fatalf("couldn't get default-timeout: %v", err)
}
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 +293,7 @@ type params struct {
namespace string
serverURL string
scmType string
configKey string
}

func TestResolve(t *testing.T) {
Expand Down Expand Up @@ -344,6 +370,7 @@ func TestResolve(t *testing.T) {
expectedCommitSHA string
expectedStatus *v1beta1.ResolutionRequestStatus
expectedErr error
configIdentifer string
}{{
name: "clone: default revision main",
args: &params{
Expand Down Expand Up @@ -439,6 +466,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",
configKey: "test",
},
config: map[string]string{
"test." + gitresolution.ServerURLKey: "fake",
"test." + gitresolution.SCMTypeKey: "fake",
},
configIdentifer: "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,
configKey: "test",
},
config: map[string]string{
"test." + gitresolution.ServerURLKey: "fake",
"test." + gitresolution.SCMTypeKey: "fake",
"test." + gitresolution.APISecretNameKey: "token-secret",
"test." + gitresolution.APISecretKeyKey: "token",
"test." + gitresolution.APISecretNamespaceKey: system.Namespace(),
},
configIdentifer: "test.",
apiToken: "some-token",
expectedCommitSHA: commitSHAsInSCMRepo[0],
expectedStatus: resolution.CreateResolutionRequestStatusWithData(mainTaskYAML),
}, {
name: "api: successful pipeline",
args: &params{
Expand Down Expand Up @@ -591,9 +658,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.configIdentifer+gitresolution.DefaultTimeoutKey] = "1m"
if cfg[tc.configIdentifer+gitresolution.DefaultRevisionKey] == "" {
cfg[tc.configIdentifer+gitresolution.DefaultRevisionKey] = plumbing.Master.Short()
}

request := createRequest(tc.args)
Expand Down Expand Up @@ -654,8 +721,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.configIdentifer+gitresolution.APISecretNameKey] != "" && tc.config[tc.configIdentifer+gitresolution.APISecretNamespaceKey] != "" && tc.config[tc.configIdentifer+gitresolution.APISecretKeyKey] != "" && tc.apiToken != "" {
secretName, secretNameKey, secretNamespace = tc.config[tc.configIdentifer+gitresolution.APISecretNameKey], tc.config[tc.configIdentifer+gitresolution.APISecretKeyKey], tc.config[tc.configIdentifer+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 +946,13 @@ func createRequest(args *params) *v1beta1.ResolutionRequest {
}
}

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

return rr
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/resolution/resolver/framework/fakeresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ 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 {
return GetResolutionTimeout(r.Timeout, defaultTimeout)
func (r *FakeResolver) GetResolutionTimeout(ctx context.Context, defaultTimeout time.Duration, params map[string]string) (time.Duration, error) {
return GetResolutionTimeout(r.Timeout, defaultTimeout), nil
}

// GetResolutionTimeout returns the input timeout if set to something greater than 0 or the default time.Duration if not configured.
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, error)
}

// 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 d30340e

Please sign in to comment.