diff --git a/tests/client/setup.go b/tests/client/setup.go new file mode 100644 index 000000000..9e067f1d5 --- /dev/null +++ b/tests/client/setup.go @@ -0,0 +1,21 @@ +package client + +import ( + "testing" + + tektonv1client "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1" + "k8s.io/client-go/tools/clientcmd" +) + +func TektonClient(t *testing.T) *tektonv1client.TektonV1Client { + t.Helper() + + rules := clientcmd.NewDefaultClientConfigLoadingRules() + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}) + config, err := clientConfig.ClientConfig() + if err != nil { + t.Fatalf("Error creating client config: %v", err) + } + + return tektonv1client.NewForConfigOrDie(config) +} diff --git a/tests/e2e_gcs_test.go b/tests/e2e_gcs_test.go index 60b538f66..8f11fa18f 100644 --- a/tests/e2e_gcs_test.go +++ b/tests/e2e_gcs_test.go @@ -5,95 +5,17 @@ package tests import ( "context" - "fmt" - "log" "os" "testing" - "time" + "github.com/openshift-pipelines/tekton-caches/tests/client" + "github.com/openshift-pipelines/tekton-caches/tests/resources" tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - tektonv1client "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/yaml" - - corev1 "k8s.io/api/core/v1" - "knative.dev/pkg/apis" ) -// ConditionAccessorFn is a condition function used polling functions. -type ConditionAccessorFn func(ca apis.ConditionAccessor) (bool, error) - -const ( - defaultNamespace = "default" - waitInterval = 10 * time.Minute - tickerDuration = 10 * time.Second -) - -var deletePolicy = metav1.DeletePropagationForeground - -func tektonClient(t *testing.T) *tektonv1client.TektonV1Client { - t.Helper() - - rules := clientcmd.NewDefaultClientConfigLoadingRules() - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}) - config, err := clientConfig.ClientConfig() - if err != nil { - t.Fatalf("Error creating client config: %v", err) - } - - return tektonv1client.NewForConfigOrDie(config) -} - -// Succeed provides a poll condition function that checks if the ConditionAccessor -// resource has successfully completed or not. -func Succeed(name string) ConditionAccessorFn { - return func(ca apis.ConditionAccessor) (bool, error) { - c := ca.GetCondition(apis.ConditionSucceeded) - if c != nil { - if c.Status == corev1.ConditionTrue { - return true, nil - } else if c.Status == corev1.ConditionFalse { - return true, fmt.Errorf("%q failed", name) - } - } - return false, nil - } -} - -func waitForPipelineRun(ctx context.Context, tc *tektonv1client.TektonV1Client, pr *tektonv1.PipelineRun, cond ConditionAccessorFn, duration time.Duration) error { - ticker := time.NewTicker(tickerDuration) - defer ticker.Stop() - stop := make(chan bool) - done := func() { - stop <- true - } - go func() { - time.Sleep(duration) - done() - }() - for { - select { - case <-stop: - return fmt.Errorf("timeout waiting for PipelineRun %s to complete", pr.Name) - case <-ticker.C: - pr, err := tc.PipelineRuns(defaultNamespace).Get(ctx, pr.Name, metav1.GetOptions{}) - if err != nil { - return err - } - log.Printf("PipelineRun %s is %v", pr.Name, pr.Status.GetCondition(apis.ConditionSucceeded)) - if val, err := cond(&pr.Status); val && err != nil { - go done() - return fmt.Errorf("PipelineRun %s failed: %s", pr.Name, err.Error()) - } else if !val { - continue - } - return nil - } - } -} - func TestCacheGCS(t *testing.T) { ctx := context.Background() pr := new(tektonv1.PipelineRun) @@ -105,17 +27,17 @@ func TestCacheGCS(t *testing.T) { t.Fatalf("Error unmarshalling: %v", err) } - tc := tektonClient(t) + tc := client.TektonClient(t) - _ = tc.PipelineRuns(defaultNamespace).Delete(ctx, pr.GetName(), metav1.DeleteOptions{ - PropagationPolicy: &deletePolicy, + _ = tc.PipelineRuns(resources.DefaultNamespace).Delete(ctx, pr.GetName(), metav1.DeleteOptions{ + PropagationPolicy: &resources.DeletePolicy, }) - if _, err = tc.PipelineRuns(defaultNamespace).Create(ctx, pr, metav1.CreateOptions{}); err != nil { + if _, err = tc.PipelineRuns(resources.DefaultNamespace).Create(ctx, pr, metav1.CreateOptions{}); err != nil { t.Fatalf("Error creating PipelineRun: %v", err) } - if err := waitForPipelineRun(ctx, tc, pr, Succeed(pr.Name), waitInterval); err != nil { + if err := resources.WaitForPipelineRun(ctx, tc, pr, resources.Succeed(pr.Name), resources.WaitInterval); err != nil { t.Fatalf("Error waiting for PipelineRun to complete: %v", err) } } diff --git a/tests/oci_test.go b/tests/oci_test.go index 1f234d55d..04b9a2c5a 100644 --- a/tests/oci_test.go +++ b/tests/oci_test.go @@ -1,6 +1,3 @@ -//go:build e2e -// +build e2e - package tests import ( @@ -12,9 +9,14 @@ import ( "github.com/openshift-pipelines/tekton-caches/internal/fetch" "github.com/openshift-pipelines/tekton-caches/internal/hash" "github.com/openshift-pipelines/tekton-caches/internal/upload" + "github.com/openshift-pipelines/tekton-caches/tests/client" + "github.com/openshift-pipelines/tekton-caches/tests/resources" + tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "gotest.tools/v3/assert" "gotest.tools/v3/env" "gotest.tools/v3/fs" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" ) const ( @@ -41,3 +43,68 @@ func TestOCIUpload(t *testing.T) { assert.NilError(t, fetch.Fetch(ctx, hash, regTarget, tmpdir.Path(), false)) assert.NilError(t, fetch.Fetch(ctx, "unknown", regTarget, tmpdir.Path(), false)) // should not error on unknown hash } + +func TestCacheOCI(t *testing.T) { + ctx := context.Background() + p := new(tektonv1.Pipeline) + pr := new(tektonv1.PipelineRun) + + // Get the pipeline yaml + pipeline, err := os.ReadFile("test-pipelineruns/test-pipeline-oci.yaml") + if err != nil { + t.Fatalf("Error reading file: %v", err) + } + if err := yaml.UnmarshalStrict(pipeline, p); err != nil { + t.Fatalf("Error unmarshalling: %v", err) + } + + // Get the pipelineRun yaml + pipelineRun, err := os.ReadFile("test-pipelineruns/test-pipelinerun-oci.yaml") + if err != nil { + t.Fatalf("Error reading file: %v", err) + } + if err := yaml.UnmarshalStrict(pipelineRun, pr); err != nil { + t.Fatalf("Error unmarshalling: %v", err) + } + + tc := client.TektonClient(t) + + // Install the pipeline example + if _, err := tc.Pipelines(resources.DefaultNamespace).Create(ctx, p, metav1.CreateOptions{}); err != nil { + t.Fatalf("Error creating Pipeline: %v", err) + } + + _ = tc.PipelineRuns(resources.DefaultNamespace).Delete(ctx, pr.GetName(), metav1.DeleteOptions{ + PropagationPolicy: &resources.DeletePolicy, + }) + + // Install the pipelineRun example + if _, err = tc.PipelineRuns(resources.DefaultNamespace).Create(ctx, pr, metav1.CreateOptions{}); err != nil { + t.Fatalf("Error creating PipelineRun: %v", err) + } + + if err := resources.WaitForPipelineRun(ctx, tc, pr, resources.Succeed(pr.Name), resources.WaitInterval); err != nil { + t.Fatalf("Error waiting for PipelineRun to complete: %v", err) + } + + // Get the taskrun + tr, err := tc.TaskRuns(resources.DefaultNamespace).Get(ctx, "pipelinerun-oci-test-build-task", metav1.GetOptions{}) + if err != nil { + t.Fatalf("Error creating PipelineRun: %v", err) + } + + // Assert the result which was produced by stepAction and stored as result in taskrun + assert.Equal(t, tr.Status.Results[0].Value.StringVal, "true") + + // Delete the pipelinerun + err = tc.PipelineRuns(resources.DefaultNamespace).Delete(ctx, pr.GetName(), metav1.DeleteOptions{}) + if err != nil { + t.Fatalf("Error deleting pipelinerun") + } + + // Delete the pipeline + err = tc.Pipelines(resources.DefaultNamespace).Delete(ctx, p.GetName(), metav1.DeleteOptions{}) + if err != nil { + t.Fatalf("Error deleting pipelinerun") + } +} diff --git a/tests/resources/pipelines.go b/tests/resources/pipelines.go new file mode 100644 index 000000000..1064969d8 --- /dev/null +++ b/tests/resources/pipelines.go @@ -0,0 +1,74 @@ +package resources + +import ( + "context" + "fmt" + "log" + "time" + + tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + tektonv1client "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/typed/pipeline/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +// ConditionAccessorFn is a condition function used polling functions. +type ConditionAccessorFn func(ca apis.ConditionAccessor) (bool, error) + +const ( + DefaultNamespace = "default" + WaitInterval = 10 * time.Minute + TickerDuration = 10 * time.Second +) + +var DeletePolicy = metav1.DeletePropagationForeground + +// Succeed provides a poll condition function that checks if the ConditionAccessor +// resource has successfully completed or not. +func Succeed(name string) ConditionAccessorFn { + return func(ca apis.ConditionAccessor) (bool, error) { + c := ca.GetCondition(apis.ConditionSucceeded) + if c != nil { + if c.Status == corev1.ConditionTrue { + return true, nil + } else if c.Status == corev1.ConditionFalse { + return true, fmt.Errorf("%q failed", name) + } + } + return false, nil + } +} + +func WaitForPipelineRun(ctx context.Context, tc *tektonv1client.TektonV1Client, pr *tektonv1.PipelineRun, cond ConditionAccessorFn, duration time.Duration) error { + ticker := time.NewTicker(TickerDuration) + defer ticker.Stop() + stop := make(chan bool) + done := func() { + stop <- true + } + go func() { + time.Sleep(duration) + done() + }() + for { + select { + case <-stop: + return fmt.Errorf("timeout waiting for PipelineRun %s to complete", pr.Name) + case <-ticker.C: + pr, err := tc.PipelineRuns(DefaultNamespace).Get(ctx, pr.Name, metav1.GetOptions{}) + if err != nil { + return err + } + log.Printf("PipelineRun %s is %v", pr.Name, pr.Status.GetCondition(apis.ConditionSucceeded)) + if val, err := cond(&pr.Status); val && err != nil { + go done() + return fmt.Errorf("PipelineRun %s failed: %s", pr.Name, err.Error()) + } else if !val { + continue + } + return nil + } + } +} diff --git a/tests/test-pipelineruns/test-pipeline-oci.yaml b/tests/test-pipelineruns/test-pipeline-oci.yaml new file mode 100644 index 000000000..95f7270bf --- /dev/null +++ b/tests/test-pipelineruns/test-pipeline-oci.yaml @@ -0,0 +1,151 @@ +--- +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: pipeline +spec: + params: + - name: repo_url + type: string + - name: revision + type: string + - name: registry + type: string + - name: buildCommand + type: string + default: go build -v . + - name: cachePatterns + type: array + default: [ "**go.mod", "**go.sum" ] + - name: image + type: string + default: golang:latest + - name: force-cache-upload + type: string + default: "false" + workspaces: + - name: source + - name: cred + tasks: + - displayName: Build go application + name: build-task + workspaces: + - name: source + workspace: source + taskSpec: + results: + - name: test-fetched + workspaces: + - name: source + - name: cred + params: + - name: buildCommand + default: $(params.buildCommand) + - name: cachePatterns + default: $(params.cachePatterns) + - name: image + default: $(params.image) + steps: + - name: create-repo + image: $(params.image) + script: | + mkdir -p $(workspaces.source.path)/repo + chmod 777 $(workspaces.source.path)/repo + - name: fetch-repo + ref: + resolver: http + params: + - name: url + value: https://raw.githubusercontent.com/tektoncd/catalog/main/stepaction/git-clone/0.1/git-clone.yaml + params: + - name: output-path + value: $(workspaces.source.path)/repo + - name: url + value: $(params.repo_url) + - name: revision + value: $(params.revision) + - name: cache-fetch + ref: + name: cache-fetch + params: + - name: patterns + value: $(params.cachePatterns) + - name: source + value: $(params.registry)/cache-go:{{hash}} + - name: cachePath + value: $(workspaces.source.path)/cache + - name: workingdir + value: $(workspaces.source.path)/repo + - name: awsCredentialFile + value: $(workspaces.cred.path)/credentials + - name: awsConfigFile + value: $(workspaces.cred.path)/config + - name: googleCredentialsPath + value: $(workspaces.cred.path)/creds.json + + - name: run-go-build + workingDir: $(workspaces.source.path)/repo + image: $(params.image) + env: + - name: GOCACHE + value: $(workspaces.source.path)/cache/gocache + - name: GOMODCACHE + value: $(workspaces.source.path)/cache/gomodcache + script: | + set -x + git config --global --add safe.directory $(workspaces.source.path)/repo + $(params.buildCommand) + echo "Cache size is $(du -sh $(workspaces.source.path)/cache)" + - name: cache-upload + ref: + name: cache-upload + params: + - name: patterns + value: $(params.cachePatterns) + - name: target + value: $(params.registry)/cache-go:{{hash}} + - name: cachePath + value: $(workspaces.source.path)/cache + - name: workingdir + value: $(workspaces.source.path)/repo + - name: dockerConfig + value: $(workspaces.cred.path) + - name: awsCredentialFile + value: $(workspaces.cred.path)/credentials + - name: awsConfigFile + value: $(workspaces.cred.path)/config + - name: force-cache-upload + value: $(params.force-cache-upload) + - name: googleCredentialsPath + value: $(workspaces.cred.path)/creds.json + + - name: cache-fetch-2 + ref: + name: cache-fetch + params: + - name: patterns + value: $(params.cachePatterns) + - name: source + value: $(params.registry)/cache-go:{{hash}} + - name: cachePath + value: $(workspaces.source.path)/cache + - name: workingdir + value: $(workspaces.source.path)/repo + - name: awsCredentialFile + value: $(workspaces.cred.path)/credentials + - name: awsConfigFile + value: $(workspaces.cred.path)/config + - name: googleCredentialsPath + value: $(workspaces.cred.path)/creds.json + + - name: verify + image: bash:latest + env: + - name: test + value: $(steps.cache-fetch-2.results.fetched) + script: | + #!/usr/bin/env bash + res=${test} + echo ${res} + echo -n ${res} > $(results.test-fetched.path) + diff --git a/tests/test-pipelineruns/test-pipelinerun-oci.yaml b/tests/test-pipelineruns/test-pipelinerun-oci.yaml new file mode 100644 index 000000000..f4656493c --- /dev/null +++ b/tests/test-pipelineruns/test-pipelinerun-oci.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + name: pipelinerun-oci-test +spec: + pipelineRef: + name: pipeline + params: + - name: repo_url + value: https://github.com/chmouel/go-helloworld + - name: revision + value: main + # This uses S3 bucket to upload Caches + - name: registry + value: oci://ttl.sh + - name: buildCommand + value: go build -v ./ + - name: image + value: golang:1.21 + workspaces: + - name: cred + emptyDir: {} + - name: source + emptyDir: {}