Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TEP-0142] Remote Resolution for StepAction #7321

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
chitrangpatel marked this conversation as resolved.
Show resolved Hide resolved
- 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
Loading