Skip to content

Commit fe862a9

Browse files
committed
feat(*): Add support for a spintainer executor
This executor runs SpinApps directly in a docker container rather than via the shim. You can use the default docker images published by the Spin project or you can craft your own images to run custom triggers, Spin versions, plugins, etc. Signed-off-by: Caleb Schoepp <[email protected]>
1 parent 30def7d commit fe862a9

17 files changed

+310
-51
lines changed

api/v1alpha1/spinappexecutor_types.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,15 @@ type SpinAppExecutorSpec struct {
3636

3737
type ExecutorDeploymentConfig struct {
3838
// RuntimeClassName is the runtime class name that should be used by pods created
39-
// as part of a deployment.
40-
RuntimeClassName string `json:"runtimeClassName"`
39+
// as part of a deployment. This should only be defined when SpintainerImage is not defined.
40+
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
41+
42+
// SpinImage points to an image that will run Spin in a container to execute
43+
// your SpinApp. This is an alternative to using the shim to execute your
44+
// SpinApp. This should only be defined when RuntimeClassName is not
45+
// defined. When specified, application images must be available without
46+
// authentication.
47+
SpinImage *string `json:"spinImage,omitempty"`
4148

4249
// CACertSecret specifies the name of the secret containing the CA
4350
// certificates to be mounted to the deployment.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/core.spinoperator.dev_spinappexecutors.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,16 @@ spec:
8484
runtimeClassName:
8585
description: |-
8686
RuntimeClassName is the runtime class name that should be used by pods created
87-
as part of a deployment.
87+
as part of a deployment. This should only be defined when SpintainerImage is not defined.
88+
type: string
89+
spinImage:
90+
description: |-
91+
SpinImage points to an image that will run Spin in a container to execute
92+
your SpinApp. This is an alternative to using the shim to execute your
93+
SpinApp. This should only be defined when RuntimeClassName is not
94+
defined. When specified, application images must be available without
95+
authentication.
8896
type: string
89-
required:
90-
- runtimeClassName
9197
type: object
9298
required:
9399
- createDeployment
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: core.spinoperator.dev/v1alpha1
2+
kind: SpinAppExecutor
3+
metadata:
4+
name: spintainer
5+
spec:
6+
createDeployment: true
7+
deploymentConfig:
8+
installDefaultCACerts: true
9+
spinImage: ghcr.io/fermyon/spin:v2.7.0

config/samples/spintainer.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: core.spinoperator.dev/v1alpha1
2+
kind: SpinApp
3+
metadata:
4+
name: spintainer-spinapp
5+
spec:
6+
image: "ghcr.io/spinkube/spin-operator/hello-world:20240708-130250-gfefd2b1"
7+
replicas: 1
8+
executor: spintainer

e2e/crd_installed_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestCRDInstalled(t *testing.T) {
1414
Assess("spinapp crd installed", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
1515
client := cfg.Client()
1616
if err := apiextensionsV1.AddToScheme(client.Resources().GetScheme()); err != nil {
17-
t.Fatalf("failed to register the v1 API extension types with Kuberenets scheme: %s", err)
17+
t.Fatalf("failed to register the v1 API extension types with Kubernetes scheme: %s", err)
1818
}
1919
name := "spinapps.core.spinoperator.dev"
2020
var crd apiextensionsV1.CustomResourceDefinition
@@ -31,7 +31,7 @@ func TestCRDInstalled(t *testing.T) {
3131
Assess("spinappexecutor crd installed", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
3232
client := cfg.Client()
3333
if err := apiextensionsV1.AddToScheme(client.Resources().GetScheme()); err != nil {
34-
t.Fatalf("failed to register the v1 API extension types with Kuberenets scheme: %s", err)
34+
t.Fatalf("failed to register the v1 API extension types with Kubernetes scheme: %s", err)
3535
}
3636

3737
name := "spinappexecutors.core.spinoperator.dev"

e2e/default_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestDefaultSetup(t *testing.T) {
3030
Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
3131
client = cfg.Client()
3232

33-
testSpinApp := newSpinAppCR(testSpinAppName, helloWorldImage)
33+
testSpinApp := newSpinAppCR(testSpinAppName, helloWorldImage, "containerd-shim-spin")
3434
if err := client.Resources().Create(ctx, testSpinApp); err != nil {
3535
t.Fatalf("Failed to create spinapp: %s", err)
3636
}
@@ -69,7 +69,7 @@ func TestDefaultSetup(t *testing.T) {
6969
testEnv.Test(t, defaultTest)
7070
}
7171

72-
func newSpinAppCR(name, image string) *spinapps_v1alpha1.SpinApp {
72+
func newSpinAppCR(name, image, executor string) *spinapps_v1alpha1.SpinApp {
7373
return &spinapps_v1alpha1.SpinApp{
7474
ObjectMeta: metav1.ObjectMeta{
7575
Name: name,
@@ -78,7 +78,7 @@ func newSpinAppCR(name, image string) *spinapps_v1alpha1.SpinApp {
7878
Spec: spinapps_v1alpha1.SpinAppSpec{
7979
Replicas: 1,
8080
Image: image,
81-
Executor: "containerd-shim-spin",
81+
Executor: executor,
8282
},
8383
}
8484
}

e2e/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func newContainerdShimExecutor(namespace string) *spinapps_v1alpha1.SpinAppExecu
144144
Spec: spinapps_v1alpha1.SpinAppExecutorSpec{
145145
CreateDeployment: true,
146146
DeploymentConfig: &spinapps_v1alpha1.ExecutorDeploymentConfig{
147-
RuntimeClassName: runtimeClassName,
147+
RuntimeClassName: &runtimeClassName,
148148
InstallDefaultCACerts: true,
149149
CACertSecret: testCACertSecret,
150150
},

e2e/spintainer_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
v1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"sigs.k8s.io/e2e-framework/klient"
11+
"sigs.k8s.io/e2e-framework/klient/k8s"
12+
"sigs.k8s.io/e2e-framework/klient/wait"
13+
"sigs.k8s.io/e2e-framework/klient/wait/conditions"
14+
"sigs.k8s.io/e2e-framework/pkg/envconf"
15+
"sigs.k8s.io/e2e-framework/pkg/features"
16+
17+
spinapps_v1alpha1 "github.com/spinkube/spin-operator/api/v1alpha1"
18+
"github.com/spinkube/spin-operator/internal/generics"
19+
)
20+
21+
// TestSpintainer is a test that checks that the minimal setup works
22+
// with the spintainer executor
23+
func TestSpintainer(t *testing.T) {
24+
var client klient.Client
25+
26+
helloWorldImage := "ghcr.io/spinkube/spin-operator/hello-world:20240708-130250-gfefd2b1"
27+
testSpinAppName := "test-spintainer-app"
28+
29+
defaultTest := features.New("default and most minimal setup").
30+
Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
31+
32+
client = cfg.Client()
33+
34+
if err := spinapps_v1alpha1.AddToScheme(client.Resources(testNamespace).GetScheme()); err != nil {
35+
t.Fatalf("failed to register the spinapps_v1alpha1 types with Kubernetes scheme: %s", err)
36+
}
37+
38+
return ctx
39+
}).
40+
Assess("spin app custom resource is created", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
41+
testSpinApp := newSpinAppCR(testSpinAppName, helloWorldImage, "spintainer")
42+
43+
if err := client.Resources().Create(ctx, newSpintainerExecutor(testNamespace)); err != nil {
44+
t.Fatalf("Failed to create spinappexecutor: %s", err)
45+
}
46+
47+
if err := client.Resources().Create(ctx, testSpinApp); err != nil {
48+
t.Fatalf("Failed to create spinapp: %s", err)
49+
}
50+
// wait for spinapp to be created
51+
if err := wait.For(
52+
conditions.New(client.Resources()).ResourceMatch(testSpinApp, func(object k8s.Object) bool {
53+
return true
54+
}),
55+
wait.WithTimeout(3*time.Minute),
56+
wait.WithInterval(30*time.Second),
57+
); err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
return ctx
62+
}).
63+
Assess("spin app deployment and service are available", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
64+
// wait for deployment to be ready
65+
if err := wait.For(
66+
conditions.New(client.Resources()).DeploymentAvailable(testSpinAppName, testNamespace),
67+
wait.WithTimeout(3*time.Minute),
68+
wait.WithInterval(30*time.Second),
69+
); err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
svc := &v1.ServiceList{
74+
Items: []v1.Service{
75+
{ObjectMeta: metav1.ObjectMeta{Name: testSpinAppName, Namespace: testNamespace}},
76+
},
77+
}
78+
79+
if err := wait.For(
80+
conditions.New(client.Resources()).ResourcesFound(svc),
81+
wait.WithTimeout(3*time.Minute),
82+
wait.WithInterval(30*time.Second),
83+
); err != nil {
84+
t.Fatal(err)
85+
}
86+
return ctx
87+
}).
88+
Feature()
89+
testEnv.Test(t, defaultTest)
90+
}
91+
92+
func newSpintainerExecutor(namespace string) *spinapps_v1alpha1.SpinAppExecutor {
93+
var testSpinAppExecutor = &spinapps_v1alpha1.SpinAppExecutor{
94+
ObjectMeta: metav1.ObjectMeta{
95+
Name: "spintainer",
96+
Namespace: namespace,
97+
},
98+
Spec: spinapps_v1alpha1.SpinAppExecutorSpec{
99+
CreateDeployment: true,
100+
DeploymentConfig: &spinapps_v1alpha1.ExecutorDeploymentConfig{
101+
SpinImage: generics.Ptr("ghcr.io/fermyon/spin:v2.7.0"),
102+
},
103+
},
104+
}
105+
106+
return testSpinAppExecutor
107+
}

internal/controller/deployment.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func constructRuntimeConfigSecretMount(_ctx context.Context, secretName string)
2020
VolumeSource: corev1.VolumeSource{
2121
Secret: &corev1.SecretVolumeSource{
2222
SecretName: secretName,
23-
Optional: ptr(true),
23+
Optional: generics.Ptr(true),
2424
Items: []corev1.KeyToPath{
2525
{
2626
Key: "runtime-config.toml",
@@ -46,7 +46,7 @@ func constructCASecretMount(_ context.Context, caSecretName string) (corev1.Volu
4646
VolumeSource: corev1.VolumeSource{
4747
Secret: &corev1.SecretVolumeSource{
4848
SecretName: caSecretName,
49-
Optional: ptr(true),
49+
Optional: generics.Ptr(true),
5050
Items: []corev1.KeyToPath{{
5151
Key: "ca-certificates.crt",
5252
Path: "ca-certificates.crt",

0 commit comments

Comments
 (0)