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 30, 2024
1 parent b463b83 commit 33d1398
Show file tree
Hide file tree
Showing 14 changed files with 828 additions and 63 deletions.
115 changes: 111 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,117 @@ spec:
value: Ranni
```

### Specifying Configuration for Multiple Git Providers

It is possible to 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 tekton resources, 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 is passed, `default` will be used. You can specify
default configuration in configmap either by mentioning no unique identifier or by using default identifier `default`

**Note**: `configKey` should not contain `.` while specifying configurations in configmap

### Example 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

A specific configurations from the configMap can be selected by passing the parameter `configKey` with the value
matching one of the configuration keys used in the configMaps.

```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
}
11 changes: 10 additions & 1 deletion pkg/remoteresolution/resolver/framework/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,18 @@ 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)
var err error
timeoutDuration, err = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration, paramsMap)
if err != nil {
return err
}
}

// A new context is created for resolution so that timeouts can
Expand Down
16 changes: 10 additions & 6 deletions pkg/remoteresolution/resolver/git/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,17 @@ 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
if err != nil {
return time.Duration(0), err
}
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
11 changes: 10 additions & 1 deletion pkg/resolution/resolver/framework/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,18 @@ 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)
var err error
timeoutDuration, err = timed.GetResolutionTimeout(ctx, defaultMaximumResolutionDuration, paramsMap)
if err != nil {
return err
}
}

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

0 comments on commit 33d1398

Please sign in to comment.