Skip to content

feat: add ServiceAccountName field to SpinApp #372

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

Merged
merged 3 commits into from
Mar 6, 2025
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
5 changes: 5 additions & 0 deletions api/v1alpha1/spinapp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ type SpinAppSpec struct {
// If this is not provided all components are executed.
// +kubebuilder:validation:MinItems:=1
Components []string `json:"components,omitempty"`

// ServiceAccountName is the name of the Kubernetes service account to use for the pod.
// If not specified, the default service account will be used.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

// SpinAppStatus defines the observed state of SpinApp
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/core.spinkube.dev_spinapps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,11 @@ spec:
type: object
type: array
type: object
serviceAccountName:
description: |-
ServiceAccountName is the name of the Kubernetes service account to use for the pod.
If not specified, the default service account will be used.
type: string
serviceAnnotations:
additionalProperties:
type: string
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
Expand Down Expand Up @@ -214,7 +213,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
Expand Down
30 changes: 26 additions & 4 deletions internal/controller/spinapp_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config

labels := constructAppLabels(app)

serviceAccountName := getServiceAccountName(ctx, app)

var container corev1.Container
if config.RuntimeClassName != nil {
container = corev1.Container{
Expand Down Expand Up @@ -508,10 +510,11 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config
Annotations: templateAnnotations,
},
Spec: corev1.PodSpec{
RuntimeClassName: config.RuntimeClassName,
Containers: []corev1.Container{container},
ImagePullSecrets: app.Spec.ImagePullSecrets,
Volumes: volumes,
RuntimeClassName: config.RuntimeClassName,
ServiceAccountName: serviceAccountName,
Containers: []corev1.Container{container},
ImagePullSecrets: app.Spec.ImagePullSecrets,
Volumes: volumes,
},
},
},
Expand All @@ -530,6 +533,25 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config
return dep, nil
}

// getServiceAccountName returns the service account name to use for the deployment.
// If serviceAccountName is specified on the SpinApp, it returns that value.
// Otherwise, it returns "default" which is the Kubernetes default.
func getServiceAccountName(ctx context.Context, app *spinv1alpha1.SpinApp) string {
log := logging.FromContext(ctx).WithValues("component", "getServiceAccountName")

log.Debug("Determining service account name",
"app", app.Name,
"namespace", app.Namespace,
"serviceAccountNameInSpec", app.Spec.ServiceAccountName)

if app.Spec.ServiceAccountName != "" {
log.Debug("Using service account from SpinApp", "serviceAccountName", app.Spec.ServiceAccountName)
return app.Spec.ServiceAccountName
}

return "default"
}

// findDeploymentForApp finds the deployment for a SpinApp.
func (r *SpinAppReconciler) findDeploymentForApp(ctx context.Context, app *spinv1alpha1.SpinApp) (*appsv1.Deployment, error) {
var deployment appsv1.Deployment
Expand Down
61 changes: 61 additions & 0 deletions internal/controller/spinapp_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,64 @@ func TestReconcile_Integration_Deployment_SpinCAInjection(t *testing.T) {
cancelFunc()
wg.Wait()
}

func TestReconcile_Integration_Deployment_ServiceAccountName(t *testing.T) {
t.Parallel()

envTest, mgr, _ := setupController(t)

ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFunc()

var wg sync.WaitGroup
wg.Add(1)
go func() {
require.NoError(t, mgr.Start(ctx))
wg.Done()
}()

executor := &spinv1alpha1.SpinAppExecutor{
ObjectMeta: metav1.ObjectMeta{
Name: "executor",
Namespace: "default",
},
Spec: spinv1alpha1.SpinAppExecutorSpec{
CreateDeployment: true,
DeploymentConfig: &spinv1alpha1.ExecutorDeploymentConfig{
RuntimeClassName: generics.Ptr("foobar"),
},
},
}

require.NoError(t, envTest.k8sClient.Create(ctx, executor))

spinApp := &spinv1alpha1.SpinApp{
ObjectMeta: metav1.ObjectMeta{
Name: "app",
Namespace: "default",
},
Spec: spinv1alpha1.SpinAppSpec{
Executor: "executor",
Image: "ghcr.io/radu-matei/perftest:v1",
ServiceAccountName: "my-service-account",
},
}

require.NoError(t, envTest.k8sClient.Create(ctx, spinApp))

var deployment appsv1.Deployment
require.Eventually(t, func() bool {
err := envTest.k8sClient.Get(ctx,
types.NamespacedName{
Namespace: "default",
Name: spinApp.Name},
&deployment)
return err == nil
}, 3*time.Second, 100*time.Millisecond)

require.Equal(t, "my-service-account", deployment.Spec.Template.Spec.ServiceAccountName)

// Terminate the context to force the manager to shut down.
cancelFunc()
wg.Wait()
}
1 change: 1 addition & 0 deletions internal/webhook/spinapp_validating.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (v *SpinAppValidator) validateSpinApp(ctx context.Context, spinApp *spinv1a
if err := validateAnnotations(spinApp.Spec, executor); err != nil {
allErrs = append(allErrs, err)
}

if len(allErrs) == 0 {
return nil
}
Expand Down
Loading