Skip to content

Commit

Permalink
[TEP-0142] Remote Resolution for StepAction
Browse files Browse the repository at this point in the history
This commit is part of tektoncd#7259. It adds the remote resolution for
StepAction.

Signed-off-by: Yongxuan Zhang [email protected]
  • Loading branch information
Yongxuanzhang committed Nov 7, 2023
1 parent 7e3897a commit 87bcb3c
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 26 deletions.
40 changes: 33 additions & 7 deletions docs/stepactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A `StepAction` is the reusable and scriptable unit of work that is performed by

A `Step` is not reusable, the work it performs is reusable and referenceable. `Steps` are in-lined in the `Task` definition and either perform work directly or perform a `StepAction`. A `StepAction` cannot be run stand-alone (unlike a `TaskRun` or a `PipelineRun`). It has to be referenced by a `Step`. Another way to ehink about this is that a `Step` is not composed of `StepActions` (unlike a `Task` being composed of `Steps` and `Sidecars`). Instead, a `Step` is an actionable component, meaning that it has the ability to refer to a `StepAction`. The author of the `StepAction` must be able to compose a `Step` using a `StepAction` and provide all the necessary context (or orchestration) to it.


## Configuring a `StepAction`

A `StepAction` definition supports the following fields:
Expand All @@ -29,7 +29,7 @@ A `StepAction` definition supports the following fields:
- [`metadata`][kubernetes-overview] - Specifies metadata that uniquely identifies the
`StepAction` resource object. For example, a `name`.
- [`spec`][kubernetes-overview] - Specifies the configuration information for this `StepAction` resource object.
- `image` - Specifies the image to use for the `Step`.
- `image` - Specifies the image to use for the `Step`.
- The container image must abide by the [container contract](./container-contract.md).
- Optional
- `command`
Expand All @@ -52,15 +52,15 @@ spec:
env:
- name: HOME
value: /home
image: ubuntu
image: ubuntu
command: ["ls"]
args: ["-lh"]
```
### Declaring Parameters
Like with `Tasks`, a `StepAction` must declare all the parameters that it used. The same rules for `Parameter` [name](./tasks.md/#parameter-name), [type](./tasks.md/#parameter-type) (including [object](./tasks.md/#object-type), [array](./tasks.md/#array-type) and [string](./tasks.md/#string-type)) apply as when declaring them in `Tasks`. A `StepAction` can also provide [default value](./tasks.md/#default-value) to a `Parameter`.
Like with `Tasks`, a `StepAction` must declare all the parameters that it used. The same rules for `Parameter` [name](./tasks.md/#parameter-name), [type](./tasks.md/#parameter-type) (including [object](./tasks.md/#object-type), [array](./tasks.md/#array-type) and [string](./tasks.md/#string-type)) apply as when declaring them in `Tasks`. A `StepAction` can also provide [default value](./tasks.md/#default-value) to a `Parameter`.

`Parameters` are passed to the `StepAction` from its corresponding `Step` referencing it.

```yaml
Expand Down Expand Up @@ -93,7 +93,7 @@ spec:

### Declaring Results

A `StepAction` also declares the results that it will emit.
A `StepAction` also declares the results that it will emit.

```yaml
apiVersion: tekton.dev/v1alpha1
Expand Down Expand Up @@ -233,7 +233,7 @@ spec:
onError: continue
```

### Passing Params to StepAction
### Passing Params to StepAction

A `StepAction` may require [params](#(declaring-parameters)). In this case, a `Task` needs to ensure that the `StepAction` has access to all the required `params`.
When referencing a `StepAction`, a `Step` can also provide it with `params`, just like how a `TaskRun` provides params to the underlying `Task`.
Expand All @@ -258,3 +258,29 @@ spec:
```

**Note:** If a `Step` declares `params` for an `inlined Step`, it will also lead to a validation error. This is because an `inlined Step` gets it's `params` from the `TaskRun`.

### Specifying Remote StepActions

A `ref` field may specify a `StepAction` in a remote location such as git.
Support for specific types of remote will depend on the `Resolvers` your
cluster's operator has installed. For more information including a tutorial, please check [resolution docs](resolution.md). The below example demonstrates referencing a `StepAction` in git:

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: step-action-run-
spec:
TaskSpec:
steps:
- name: action-runner
ref:
resolver: git
params:
- name: url
value: https://github.com/repo/repo.git
- name: revision
value: main
- name: pathInRepo
value: remote_step.yaml
```
18 changes: 18 additions & 0 deletions examples/v1/taskruns/alpha/stepaction-git-resolver.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# TODO(#7325): use StepAction from Catalog
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: step-action-run-
spec:
TaskSpec:
steps:
- name: action-runner
ref:
resolver: git
params:
- name: url
value: https://github.com/chitrangpatel/repo1M.git
- name: revision
value: main
- name: pathInRepo
value: basic_step.yaml
3 changes: 3 additions & 0 deletions pkg/apis/pipeline/v1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ const (
// TaskRunReasonResolvingTaskRef indicates that the TaskRun is waiting for
// its taskRef to be asynchronously resolved.
TaskRunReasonResolvingTaskRef = "ResolvingTaskRef"
// TaskRunReasonResolvingStepActionRef indicates that the TaskRun is waiting for
// its StepAction's Ref to be asynchronously resolved.
TaskRunReasonResolvingStepActionRef = "ResolvingStepActionRef"
// TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled
TaskRunReasonImagePullFailed TaskRunReason = "TaskRunImagePullFailed"
// TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB
Expand Down
8 changes: 8 additions & 0 deletions pkg/reconciler/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/google/uuid"
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -56,6 +57,13 @@ func DryRunValidate(ctx context.Context, namespace string, obj runtime.Object, t
if _, err := tekton.TektonV1beta1().Tasks(namespace).Create(ctx, dryRunObj, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
return handleDryRunCreateErr(err, obj.Name)
}
case *v1alpha1.StepAction:
dryRunObj := obj.DeepCopy()
dryRunObj.Name = dryRunObjName
dryRunObj.Namespace = namespace // Make sure the namespace is the same as the StepAction
if _, err := tekton.TektonV1alpha1().StepActions(namespace).Create(ctx, dryRunObj, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
return handleDryRunCreateErr(err, obj.Name)
}
default:
return fmt.Errorf("unsupported object GVK %s", obj.GetObjectKind().GroupVersionKind())
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/reconciler/apiserver/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/client/clientset/versioned/fake"
"github.com/tektoncd/pipeline/pkg/reconciler/apiserver"
Expand Down Expand Up @@ -34,6 +35,9 @@ func TestDryRunCreate_Valid_DifferentGVKs(t *testing.T) {
}, {
name: "v1beta1 pipeline",
obj: &v1beta1.Pipeline{},
}, {
name: "v1alpha1 stepaction",
obj: &v1alpha1.StepAction{},
}, {
name: "unsupported gvk",
obj: &v1beta1.ClusterTask{},
Expand Down Expand Up @@ -71,6 +75,10 @@ func TestDryRunCreate_Invalid_DifferentGVKs(t *testing.T) {
name: "v1beta1 pipeline",
obj: &v1beta1.Pipeline{},
wantErr: apiserver.ErrReferencedObjectValidationFailed,
}, {
name: "v1alpha1 stepaction",
obj: &v1alpha1.StepAction{},
wantErr: apiserver.ErrReferencedObjectValidationFailed,
}, {
name: "unsupported gvk",
obj: &v1beta1.ClusterTask{},
Expand All @@ -85,6 +93,9 @@ func TestDryRunCreate_Invalid_DifferentGVKs(t *testing.T) {
tektonclient.PrependReactor("create", "pipelines", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewBadRequest("bad request")
})
tektonclient.PrependReactor("create", "stepactions", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewBadRequest("bad request")
})
err := apiserver.DryRunValidate(context.Background(), "default", tc.obj, tektonclient)
if d := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); d != "" {
t.Errorf("wrong error: %s", d)
Expand Down
39 changes: 34 additions & 5 deletions pkg/reconciler/taskrun/resources/taskref.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func GetTaskKind(taskrun *v1.TaskRun) v1.TaskKind {
return kind
}

// GetTaskFuncFromTaskRun is a factory function that will use the given TaskRef as context to return a valid GetTask function. It
// also requires a kubeclient, tektonclient, namespace, and service account in case it needs to find that task in
// GetTaskFuncFromTaskRun is a factory function that will use the given TaskRef as context to return a valid GetTask function.
// It also requires a kubeclient, tektonclient, namespace, and service account in case it needs to find that task in
// cluster or authorize against an external repositroy. It will figure out whether it needs to look in the cluster or in
// a remote image to fetch the reference. It will also return the "kind" of the task being referenced.
// OCI bundle and remote resolution tasks will be verified by trusted resources if the feature is enabled
Expand All @@ -78,8 +78,8 @@ func GetTaskFuncFromTaskRun(ctx context.Context, k8s kubernetes.Interface, tekto
return GetTaskFunc(ctx, k8s, tekton, requester, taskrun, taskrun.Spec.TaskRef, taskrun.Name, taskrun.Namespace, taskrun.Spec.ServiceAccountName, verificationPolicies)
}

// GetTaskFunc is a factory function that will use the given TaskRef as context to return a valid GetTask function. It
// also requires a kubeclient, tektonclient, namespace, and service account in case it needs to find that task in
// GetTaskFunc is a factory function that will use the given TaskRef as context to return a valid GetTask function.
// It also requires a kubeclient, tektonclient, namespace, and service account in case it needs to find that task in
// cluster or authorize against an external repositroy. It will figure out whether it needs to look in the cluster or in
// a remote image to fetch the reference. It will also return the "kind" of the task being referenced.
// OCI bundle and remote resolution tasks will be verified by trusted resources if the feature is enabled
Expand Down Expand Up @@ -124,7 +124,21 @@ func GetTaskFunc(ctx context.Context, k8s kubernetes.Interface, tekton clientset
}

// GetStepActionFunc is a factory function that will use the given Ref as context to return a valid GetStepAction function.
func GetStepActionFunc(tekton clientset.Interface, namespace string) GetStepAction {
// It also requires a kubeclient, tektonclient, requester in case it needs to find that task in
// cluster or authorize against an external repository. It will figure out whether it needs to look in the cluster or in
// a remote location to fetch the reference.
func GetStepActionFunc(tekton clientset.Interface, k8s kubernetes.Interface, requester remoteresource.Requester, tr *v1.TaskRun, step *v1.Step) GetStepAction {
trName := tr.Name
namespace := tr.Namespace
if step.Ref != nil && step.Ref.Resolver != "" && requester != nil {
// Return an inline function that implements GetStepAction by calling Resolver.Get with the specified StepAction type and
// casting it to a StepAction.
return func(ctx context.Context, name string) (*v1alpha1.StepAction, *v1.RefSource, error) {
// TODO(#7259): support params replacements for resolver params
resolver := resolution.NewResolver(requester, tr, string(step.Ref.Resolver), trName, namespace, step.Ref.Params)
return resolveStepAction(ctx, resolver, name, namespace, k8s, tekton)
}
}
local := &LocalStepActionRefResolver{
Namespace: namespace,
Tektonclient: tekton,
Expand All @@ -151,6 +165,21 @@ func resolveTask(ctx context.Context, resolver remote.Resolver, name, namespace
return taskObj, refSource, vr, nil
}

func resolveStepAction(ctx context.Context, resolver remote.Resolver, name, namespace string, k8s kubernetes.Interface, tekton clientset.Interface) (*v1alpha1.StepAction, *v1.RefSource, error) {
obj, refSource, err := resolver.Get(ctx, "StepAction", name)
if err != nil {
return nil, nil, err
}
switch obj := obj.(type) { //nolint:gocritic
case *v1alpha1.StepAction:
if err := apiserver.DryRunValidate(ctx, namespace, obj, tekton); err != nil {
return nil, nil, err
}
return obj, refSource, nil
}
return nil, nil, errors.New("resource is not a StepAction")
}

// readRuntimeObjectAsTask tries to convert a generic runtime.Object
// into a *v1.Task type so that its meta and spec fields
// can be read. v1beta1 object will be converted to v1 and returned.
Expand Down
Loading

0 comments on commit 87bcb3c

Please sign in to comment.