From ce9882d162022ca82e78b504dee2b4c7a0622065 Mon Sep 17 00:00:00 2001 From: zoezhao Date: Mon, 10 Aug 2026 13:20:01 -0700 Subject: [PATCH] Remove the secretKeyRef env sources feature We don't want actors to have any access to secrets: they will be injected on the egress route instead. Removing this now so we don't need to copy secrets into substrate resources. This reverts the ActorTemplate valueFrom.secretKeyRef support added in #20 (issue #15), surgically rather than via git revert since the touched files have since evolved: - EnvVar reverts to a literal-only {name, value} shape; value is now required. EnvVarSource and SecretKeySelector are gone from the CRD. - ate-api no longer reads Secrets: the env resolver, secret cache, and the kubernetes.Interface plumbing through NewService/NewActorWorkflow are removed. Literal env mapping is folded into workloadSpecFromActorTemplate so it still reaches atelet. - The env redaction in ateinterceptors is kept: it is shared by InternalServerUnaryInterceptor and still keeps literal env values out of request logs. - The claude-code-multiplex demo passes ANTHROPIC_API_KEY as a plain env value substituted at apply time; its Secret object is removed. Note: checkpoint requests (pause/suspend) now carry literal env in the spec since the single builder always maps env; atelet only consumes env on Run/Restore, so this is inert. --- .../internal/controlapi/functional_test.go | 37 +-- cmd/ateapi/internal/controlapi/service.go | 4 +- cmd/ateapi/internal/controlapi/workflow.go | 6 - .../internal/controlapi/workflow_resume.go | 2 +- .../controlapi/workflow_testutil_test.go | 2 +- .../internal/controlapi/workload_spec.go | 185 +------------ .../internal/controlapi/workload_spec_test.go | 247 +----------------- cmd/ateapi/main.go | 2 +- demos/claude-code-multiplex/README.md | 4 +- .../claude-code-multiplex.yaml.tmpl | 30 +-- demos/claude-code-multiplex/workload/run.sh | 2 +- docs/api-guide.md | 4 +- manifests/ate-install/ate-api-server.yaml | 5 - .../generated/ate.dev_actortemplates.yaml | 41 +-- pkg/api/v1alpha1/actortemplate_types.go | 51 +--- .../v1alpha1/actortemplate_validation_test.go | 131 +--------- pkg/api/v1alpha1/zz_generated.deepcopy.go | 54 +--- 17 files changed, 54 insertions(+), 753 deletions(-) diff --git a/cmd/ateapi/internal/controlapi/functional_test.go b/cmd/ateapi/internal/controlapi/functional_test.go index d887d6c70..aaf1767d9 100644 --- a/cmd/ateapi/internal/controlapi/functional_test.go +++ b/cmd/ateapi/internal/controlapi/functional_test.go @@ -346,7 +346,7 @@ func setupTest(t *testing.T, ns string) *testContext { volPlugins := map[string]volume.VolumePluginControlPlane{ mockDriverName: mockPlugin, } - service := NewService(persistence, wc, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, scLister, dialer, k8sClient, instruments, "", volPlugins) + service := NewService(persistence, wc, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, scLister, dialer, instruments, "", volPlugins) // 5. Start REAL gRPC Server for ATE API grpcServer := grpc.NewServer(grpc.UnaryInterceptor(ateinterceptors.ServerUnaryInterceptor)) @@ -1774,24 +1774,11 @@ func TestResumeActor(t *testing.T) { } } -func TestResumeActorResolvesValueFromEnv(t *testing.T) { - ns := namespaceForTest("ns-resume-secret-env") +func TestResumeActorPassesLiteralEnv(t *testing.T) { + ns := namespaceForTest("ns-resume-literal-env") tc := setupTest(t, ns) defer tc.cleanup() - _, err := tc.k8sClient.CoreV1().Secrets(ns).Create(context.Background(), &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "api-keys", - Namespace: ns, - }, - Data: map[string][]byte{ - "anthropic": []byte("sk-test"), - }, - }, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("failed to create secret: %v", err) - } - createTemplateWithContainers(t, tc, ns, []atev1alpha1.Container{ { Name: "main", @@ -1800,23 +1787,14 @@ func TestResumeActorResolvesValueFromEnv(t *testing.T) { Env: []atev1alpha1.EnvVar{ { Name: "LITERAL", - Value: ptr.To("plain"), - }, - { - Name: "ANTHROPIC_API_KEY", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{ - Name: "api-keys", - Key: "anthropic", - }, - }, + Value: "plain", }, }, }, }) createWorkerPod(t, tc, ns, "worker-1", "node1", "pool1") - _, err = tc.client.CreateActor(context.Background(), &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ + _, err := tc.client.CreateActor(context.Background(), &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "id1"}, ActorTemplateNamespace: ns, ActorTemplateName: "tmpl1", @@ -1843,11 +1821,10 @@ func TestResumeActorResolvesValueFromEnv(t *testing.T) { gotEnv[env.GetName()] = env.GetValue() } wantEnv := map[string]string{ - "LITERAL": "plain", - "ANTHROPIC_API_KEY": "sk-test", + "LITERAL": "plain", } if diff := cmp.Diff(wantEnv, gotEnv); diff != "" { - t.Errorf("resolved env mismatch (-want +got):\n%s", diff) + t.Errorf("env mismatch (-want +got):\n%s", diff) } } diff --git a/cmd/ateapi/internal/controlapi/service.go b/cmd/ateapi/internal/controlapi/service.go index 795347a2e..fd5fd52bf 100644 --- a/cmd/ateapi/internal/controlapi/service.go +++ b/cmd/ateapi/internal/controlapi/service.go @@ -24,7 +24,6 @@ import ( "github.com/agent-substrate/substrate/internal/volume/csi" listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - "k8s.io/client-go/kubernetes" storagev1listers "k8s.io/client-go/listers/storage/v1" ) @@ -61,7 +60,6 @@ func NewService( csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister, storageClassLister storagev1listers.StorageClassLister, dialer *AteletDialer, - kubeClient kubernetes.Interface, instruments *Instruments, egressGatewayAddress string, volumePlugins map[string]volume.VolumePluginControlPlane, @@ -77,7 +75,7 @@ func NewService( instruments: instruments, volumePlugins: volumePlugins, } - s.actorWorkflow = NewActorWorkflow(persistence, workerCache, dialer, actorTemplateLister, workerPoolLister, sandboxConfigLister, storageClassLister, kubeClient, instruments, egressGatewayAddress, s) + s.actorWorkflow = NewActorWorkflow(persistence, workerCache, dialer, actorTemplateLister, workerPoolLister, sandboxConfigLister, storageClassLister, instruments, egressGatewayAddress, s) return s } diff --git a/cmd/ateapi/internal/controlapi/workflow.go b/cmd/ateapi/internal/controlapi/workflow.go index 028931696..49092f4a5 100644 --- a/cmd/ateapi/internal/controlapi/workflow.go +++ b/cmd/ateapi/internal/controlapi/workflow.go @@ -30,7 +30,6 @@ import ( "go.opentelemetry.io/otel/trace" grpcCodes "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "k8s.io/client-go/kubernetes" storagev1listers "k8s.io/client-go/listers/storage/v1" ) @@ -76,8 +75,6 @@ type ActorWorkflow struct { workerPoolLister listersv1alpha1.WorkerPoolLister sandboxConfigLister listersv1alpha1.SandboxConfigLister storageClassLister storagev1listers.StorageClassLister - kubeClient kubernetes.Interface - secretCache *envSecretCache instruments *Instruments egressGatewayAddress string pluginRegistry VolumePluginRegistry @@ -92,7 +89,6 @@ func NewActorWorkflow( workerPoolLister listersv1alpha1.WorkerPoolLister, sandboxConfigLister listersv1alpha1.SandboxConfigLister, storageClassLister storagev1listers.StorageClassLister, - kubeClient kubernetes.Interface, instruments *Instruments, egressGatewayAddress string, pluginRegistry VolumePluginRegistry, @@ -106,8 +102,6 @@ func NewActorWorkflow( workerPoolLister: workerPoolLister, sandboxConfigLister: sandboxConfigLister, storageClassLister: storageClassLister, - kubeClient: kubeClient, - secretCache: newEnvSecretCache(envSecretCacheTTL), instruments: instruments, egressGatewayAddress: egressGatewayAddress, pluginRegistry: pluginRegistry, diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 34d964943..9b3c52e15 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -600,7 +600,7 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou } client := ateletpb.NewAteomHerderClient(ateletConn) - workloadSpec, err := workloadSpecFromActorTemplateWithEnv(ctx, w.kubeClient, w.secretCache, actorTemplate, actor) + workloadSpec, err := workloadSpecFromActorTemplate(actorTemplate, actor) if err != nil { return tele, err } diff --git a/cmd/ateapi/internal/controlapi/workflow_testutil_test.go b/cmd/ateapi/internal/controlapi/workflow_testutil_test.go index ae013f5cc..b0afec726 100644 --- a/cmd/ateapi/internal/controlapi/workflow_testutil_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_testutil_test.go @@ -42,7 +42,7 @@ func newTestActorWorkflow(t *testing.T, st store.Interface, tmplNamespace, tmplN }); err != nil { t.Fatalf("add template to indexer: %v", err) } - return NewActorWorkflow(st, nil, nil, listersv1alpha1.NewActorTemplateLister(indexer), nil, nil, nil, nil, nil, "", nil) + return NewActorWorkflow(st, nil, nil, listersv1alpha1.NewActorTemplateLister(indexer), nil, nil, nil, nil, "", nil) } // seedWorkflowActor stores an actor with the given status, bound to the given diff --git a/cmd/ateapi/internal/controlapi/workload_spec.go b/cmd/ateapi/internal/controlapi/workload_spec.go index 6e1b12d0d..39a70facd 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec.go +++ b/cmd/ateapi/internal/controlapi/workload_spec.go @@ -15,27 +15,15 @@ package controlapi import ( - "context" "fmt" - "sync" - "time" "github.com/agent-substrate/substrate/internal/proto/ateletpb" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" ) -const envSecretCacheTTL = 30 * time.Second - -// workloadSpecFromActorTemplate builds a WorkloadSpec without resolving -// container env vars. Use this when downstream consumers (e.g. checkpoint -// requests) don't need env entries materialized. +// workloadSpecFromActorTemplate builds a WorkloadSpec from the template; +// container env is copied verbatim. func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) (*ateletpb.WorkloadSpec, error) { workloadSpec := &ateletpb.WorkloadSpec{ PauseImage: actorTemplate.Spec.PauseImage, @@ -69,6 +57,12 @@ func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate, act Args: ctr.Args, Readyz: toAteletReadyz(ctr.Readyz), } + for _, env := range ctr.Env { + ateletCtr.Env = append(ateletCtr.Env, &ateletpb.EnvEntry{ + Name: env.Name, + Value: env.Value, + }) + } for _, mount := range ctr.VolumeMounts { ateletCtr.VolumeMounts = append(ateletCtr.VolumeMounts, &ateletpb.VolumeMount{ Name: mount.Name, @@ -81,36 +75,6 @@ func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate, act return workloadSpec, nil } -// workloadSpecFromActorTemplateWithEnv builds a WorkloadSpec and resolves each -// container's env vars against the cluster. kubeClient must be non-nil; -// secretCache is optional and, when supplied, deduplicates Secret reads. -func workloadSpecFromActorTemplateWithEnv(ctx context.Context, kubeClient kubernetes.Interface, secretCache *envSecretCache, actorTemplate *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) (*ateletpb.WorkloadSpec, error) { - workloadSpec, err := workloadSpecFromActorTemplate(actorTemplate, actor) - if err != nil { - return nil, err - } - - resolver := envResolver{ - kubeClient: kubeClient, - namespace: actorTemplate.Namespace, - cache: secretCache, - } - - for i, ctr := range actorTemplate.Spec.Containers { - for _, env := range ctr.Env { - ateletEnv, err := resolver.resolve(ctx, ctr.Name, env) - if err != nil { - return nil, err - } - if ateletEnv != nil { - workloadSpec.Containers[i].Env = append(workloadSpec.Containers[i].Env, ateletEnv) - } - } - } - - return workloadSpec, nil -} - // appendExternalVolumes maps template external volumes to resolved actor volumes and appends them to workloadSpec // if they are referenced in container volumeMounts. func appendExternalVolumes(workloadSpec *ateletpb.WorkloadSpec, template *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) error { @@ -183,136 +147,3 @@ func toAteletReadyz(in *atev1alpha1.ContainerReadyz) *ateletpb.Readyz { } return out } - -type envResolver struct { - kubeClient kubernetes.Interface - namespace string - cache *envSecretCache -} - -func (r *envResolver) resolve(ctx context.Context, containerName string, env atev1alpha1.EnvVar) (*ateletpb.EnvEntry, error) { - envID := fmt.Sprintf("container %q env %q", containerName, env.Name) - - switch { - case env.Value != nil: - return &ateletpb.EnvEntry{ - Name: env.Name, - Value: *env.Value, - }, nil - case env.ValueFrom != nil: - value, include, err := r.resolveValueFrom(ctx, envID, env.ValueFrom) - if err != nil { - return nil, err - } - if !include { - return nil, nil - } - return &ateletpb.EnvEntry{ - Name: env.Name, - Value: value, - }, nil - } - return nil, status.Errorf(codes.FailedPrecondition, "%s has unknown value source", envID) -} - -func (r *envResolver) resolveValueFrom(ctx context.Context, envID string, valueFrom *atev1alpha1.EnvVarSource) (string, bool, error) { - if ref := valueFrom.SecretKeyRef; ref != nil { - return r.resolveSecretKeyRef(ctx, envID, ref) - } - return "", false, status.Errorf(codes.FailedPrecondition, "%s uses unsupported valueFrom source; only secretKeyRef is supported", envID) -} - -func (r *envResolver) resolveSecretKeyRef(ctx context.Context, envID string, ref *atev1alpha1.SecretKeySelector) (string, bool, error) { - if r.kubeClient == nil { - return "", false, status.Errorf(codes.FailedPrecondition, "%s cannot resolve secretKeyRef because Kubernetes client is unavailable", envID) - } - - secret, err := r.secret(ctx, ref.Name) - if err != nil { - if apierrors.IsNotFound(err) { - if isOptional(ref.Optional) { - return "", false, nil - } - return "", false, status.Errorf(codes.FailedPrecondition, "%s references missing secret %s/%s", envID, r.namespace, ref.Name) - } - return "", false, status.Errorf(codes.Internal, "while resolving %s secretKeyRef %s/%s: %v", envID, r.namespace, ref.Name, err) - } - - value, ok := secret.Data[ref.Key] - if !ok { - if isOptional(ref.Optional) { - return "", false, nil - } - return "", false, status.Errorf(codes.FailedPrecondition, "%s references missing key %q in secret %s/%s", envID, ref.Key, r.namespace, ref.Name) - } - - return string(value), true, nil -} - -func (r *envResolver) secret(ctx context.Context, name string) (*corev1.Secret, error) { - if r.cache != nil { - return r.cache.get(ctx, r.kubeClient, r.namespace, name) - } - return r.kubeClient.CoreV1().Secrets(r.namespace).Get(ctx, name, metav1.GetOptions{}) -} - -type envSecretCache struct { - mu sync.RWMutex - ttl time.Duration - entries map[envSecretCacheKey]envSecretCacheEntry -} - -type envSecretCacheKey struct { - namespace string - name string -} - -type envSecretCacheEntry struct { - secret *corev1.Secret - expiresAt time.Time -} - -func newEnvSecretCache(ttl time.Duration) *envSecretCache { - return &envSecretCache{ - ttl: ttl, - entries: map[envSecretCacheKey]envSecretCacheEntry{}, - } -} - -func (c *envSecretCache) get(ctx context.Context, kubeClient kubernetes.Interface, namespace, name string) (*corev1.Secret, error) { - key := envSecretCacheKey{ - namespace: namespace, - name: name, - } - now := time.Now() - - c.mu.RLock() - entry, ok := c.entries[key] - if ok && now.Before(entry.expiresAt) { - secret := entry.secret.DeepCopy() - c.mu.RUnlock() - return secret, nil - } - c.mu.RUnlock() - - // TODO: Make refresh smarter if this pattern sticks, for example by - // refreshing asynchronously or watching referenced Secrets. - secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - secret = secret.DeepCopy() - c.mu.Lock() - c.entries[key] = envSecretCacheEntry{ - secret: secret, - expiresAt: time.Now().Add(c.ttl), - } - c.mu.Unlock() - - return secret.DeepCopy(), nil -} - -func isOptional(optional *bool) bool { - return optional != nil && *optional -} diff --git a/cmd/ateapi/internal/controlapi/workload_spec_test.go b/cmd/ateapi/internal/controlapi/workload_spec_test.go index 0de964056..756bf86a3 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec_test.go +++ b/cmd/ateapi/internal/controlapi/workload_spec_test.go @@ -15,21 +15,14 @@ package controlapi import ( - "context" "testing" "github.com/agent-substrate/substrate/internal/proto/ateletpb" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/google/go-cmp/cmp" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/utils/ptr" ) func TestWorkloadSpecFromActorTemplate(t *testing.T) { @@ -144,7 +137,7 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) { }, }, { - name: "ignores container env", + name: "maps literal env", template: &atev1alpha1.ActorTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"}, Spec: atev1alpha1.ActorTemplateSpec{ @@ -153,20 +146,22 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) { Name: "main", Image: "main", Env: []atev1alpha1.EnvVar{ - {Name: "LITERAL", Value: ptr.To("plain")}, - { - Name: "SECRET", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{Name: "any", Key: "any"}, - }, - }, + {Name: "LITERAL", Value: "plain"}, + {Name: "EMPTY", Value: ""}, }, }, }, }, }, want: &ateletpb.WorkloadSpec{ - Containers: []*ateletpb.Container{{Name: "main", Image: "main"}}, + Containers: []*ateletpb.Container{{ + Name: "main", + Image: "main", + Env: []*ateletpb.EnvEntry{ + {Name: "LITERAL", Value: "plain"}, + {Name: "EMPTY", Value: ""}, + }, + }}, }, }, { @@ -208,147 +203,6 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) { } } -func TestWorkloadSpecFromActorTemplateWithEnv(t *testing.T) { - tests := []struct { - name string - secrets []runtime.Object - template *atev1alpha1.ActorTemplate - want *ateletpb.WorkloadSpec - wantErrCode codes.Code - }{ - { - name: "resolves literal and secretKeyRef env", - secrets: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "some-secret", Namespace: "agent-ns"}, - Data: map[string][]byte{"some-key": []byte("some-value")}, - }, - }, - template: &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"}, - Spec: atev1alpha1.ActorTemplateSpec{ - PauseImage: "pause", - Containers: []atev1alpha1.Container{ - { - Name: "main", - Image: "main", - Command: []string{"/main"}, - Env: []atev1alpha1.EnvVar{ - {Name: "LITERAL", Value: ptr.To("plain")}, - { - Name: "SOME_KEY", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{Name: "some-secret", Key: "some-key"}, - }, - }, - }, - }, - }, - }, - }, - want: &ateletpb.WorkloadSpec{ - PauseImage: "pause", - Containers: []*ateletpb.Container{ - { - Name: "main", - Image: "main", - Command: []string{"/main"}, - Env: []*ateletpb.EnvEntry{ - {Name: "LITERAL", Value: "plain"}, - {Name: "SOME_KEY", Value: "some-value"}, - }, - }, - }, - }, - }, - { - name: "skips optional missing secret", - template: &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"}, - Spec: atev1alpha1.ActorTemplateSpec{ - Containers: []atev1alpha1.Container{ - { - Name: "main", - Image: "main", - Env: []atev1alpha1.EnvVar{ - { - Name: "OPTIONAL", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{Name: "missing", Key: "key", Optional: ptr.To(true)}, - }, - }, - }, - }, - }, - }, - }, - want: &ateletpb.WorkloadSpec{ - Containers: []*ateletpb.Container{{Name: "main", Image: "main"}}, - }, - }, - { - name: "required missing secret fails", - template: &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"}, - Spec: atev1alpha1.ActorTemplateSpec{ - Containers: []atev1alpha1.Container{ - { - Name: "main", - Image: "main", - Env: []atev1alpha1.EnvVar{ - { - Name: "REQUIRED", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{Name: "missing", Key: "key"}, - }, - }, - }, - }, - }, - }, - }, - wantErrCode: codes.FailedPrecondition, - }, - { - name: "empty valueFrom fails", - template: &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"}, - Spec: atev1alpha1.ActorTemplateSpec{ - Containers: []atev1alpha1.Container{ - { - Name: "main", - Image: "main", - Env: []atev1alpha1.EnvVar{ - {Name: "EMPTY", ValueFrom: &atev1alpha1.EnvVarSource{}}, - }, - }, - }, - }, - }, - wantErrCode: codes.FailedPrecondition, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - kubeClient := fake.NewSimpleClientset(tt.secrets...) - got, err := workloadSpecFromActorTemplateWithEnv(context.Background(), kubeClient, nil, tt.template, nil) - if tt.wantErrCode != codes.OK { - if status.Code(err) != tt.wantErrCode { - t.Fatalf("error code = %v, want %v: %v", status.Code(err), tt.wantErrCode, err) - } - return - } - if err != nil { - t.Fatalf("workloadSpecFromActorTemplateWithEnv failed: %v", err) - } - if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" { - t.Errorf("WorkloadSpec mismatch (-want +got):\n%s", diff) - } - }) - } -} - func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) { got, err := workloadSpecFromActorTemplate(&atev1alpha1.ActorTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "tmpl-readyz", Namespace: "agent-ns"}, @@ -394,85 +248,6 @@ func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) { } } -func TestWorkloadSpecFromActorTemplateWithEnvCachesSecretsAcrossCalls(t *testing.T) { - ctx := context.Background() - secretCache := newEnvSecretCache(envSecretCacheTTL) - kubeClient := fake.NewSimpleClientset( - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "some-secret", - Namespace: "agent-ns", - }, - Data: map[string][]byte{ - "some-key": []byte("some-value"), - }, - }, - ) - actorTemplate := &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tmpl1", - Namespace: "agent-ns", - }, - Spec: atev1alpha1.ActorTemplateSpec{ - Containers: []atev1alpha1.Container{ - { - Name: "main", - Image: "main", - Env: []atev1alpha1.EnvVar{ - { - Name: "SOME_KEY", - ValueFrom: &atev1alpha1.EnvVarSource{ - SecretKeyRef: &atev1alpha1.SecretKeySelector{ - Name: "some-secret", - Key: "some-key", - }, - }, - }, - }, - }, - }, - }, - } - - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { - t.Fatalf("first workloadSpecFromActorTemplateWithEnv failed: %v", err) - } - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { - t.Fatalf("second workloadSpecFromActorTemplateWithEnv failed: %v", err) - } - if got := secretGetCount(kubeClient); got != 1 { - t.Fatalf("secret gets before TTL expiry = %d, want 1", got) - } - - expireSecretCache(secretCache) - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { - t.Fatalf("third workloadSpecFromActorTemplateWithEnv failed: %v", err) - } - if got := secretGetCount(kubeClient); got != 2 { - t.Fatalf("secret gets after TTL expiry = %d, want 2", got) - } -} - -func expireSecretCache(secretCache *envSecretCache) { - secretCache.mu.Lock() - defer secretCache.mu.Unlock() - - for key, entry := range secretCache.entries { - entry.expiresAt = entry.expiresAt.Add(-envSecretCacheTTL) - secretCache.entries[key] = entry - } -} - -func secretGetCount(kubeClient *fake.Clientset) int { - count := 0 - for _, action := range kubeClient.Actions() { - if action.GetVerb() == "get" && action.GetResource().Resource == "secrets" { - count++ - } - } - return count -} - func TestAppendExternalVolumes(t *testing.T) { template := &atev1alpha1.ActorTemplate{ Spec: atev1alpha1.ActorTemplateSpec{ diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index 755f09639..54d1d1004 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -187,7 +187,7 @@ func main() { volPlugins := make(map[string]volume.VolumePluginControlPlane) ateletDialer := controlapi.NewAteletDialer(workerPodInformer.GetIndexer(), ateletPodInformer.GetIndexer(), *ateletClientCredBundle, *podIdentityCACerts) - sm := controlapi.NewService(redisPersistence, workerCache, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, storageClassLister, ateletDialer, clientset, instruments, *egressGatewayAddress, volPlugins) + sm := controlapi.NewService(redisPersistence, workerCache, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, storageClassLister, ateletDialer, instruments, *egressGatewayAddress, volPlugins) jwtIssuerDiscoveryClient := buildK8sServiceAccountIssuerDiscoveryClient(ctx, *clientJWTCAFile, *clientJWTIssuer) diff --git a/demos/claude-code-multiplex/README.md b/demos/claude-code-multiplex/README.md index 695fb3292..eb33216f2 100644 --- a/demos/claude-code-multiplex/README.md +++ b/demos/claude-code-multiplex/README.md @@ -38,7 +38,7 @@ This guide assumes you know Kubernetes and the general shape of agent runtimes ( | Path | Purpose | |---|---| -| `demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl` | Namespace, Secret, WorkerPool, and ActorTemplates in a single envsubst template | +| `demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl` | Namespace, WorkerPool, and ActorTemplates in a single envsubst template | | `hack/install-demo-claude-code-multiplex.sh` | Sourced by `install-ate.sh`; registers `--deploy-demo-claude-code-multiplex` and `--delete-demo-claude-code-multiplex` | | `demos/claude-code-multiplex/workload/` | The agent container image source (Dockerfile + entrypoint that wires Claude Code; built and pushed by the deploy step) | | `demos/claude-code-multiplex/ui/` | Static dashboard (`index.html` + `server.go`) that talks to the cluster | @@ -55,7 +55,7 @@ BUCKET_NAME=your-substrate-bucket \ ./hack/install-ate.sh --deploy-demo-claude-code-multiplex ``` -This creates the `claude-multiplex-demo` namespace, an `anthropic-api-key` Secret, a 2-pod `WorkerPool`, and three `ActorTemplate` objects named `agent-luna`, `agent-mars`, `agent-orion`. Under the hood, the deploy function builds the workload image with `docker buildx`, pushes it to `${KO_DOCKER_REPO}/claude-multiplex-demo-workload`, resolves the pushed sha256 digest, and substitutes the digest-pinned reference plus `ANTHROPIC_API_KEY` and `BUCKET_NAME` into the manifest template at apply time. The ActorTemplates consume the key through `valueFrom.secretKeyRef`. +This creates the `claude-multiplex-demo` namespace, a 2-pod `WorkerPool`, and three `ActorTemplate` objects named `agent-luna`, `agent-mars`, `agent-orion`. Under the hood, the deploy function builds the workload image with `docker buildx`, pushes it to `${KO_DOCKER_REPO}/claude-multiplex-demo-workload`, resolves the pushed sha256 digest, and substitutes the digest-pinned reference plus `ANTHROPIC_API_KEY` and `BUCKET_NAME` into the manifest template at apply time. ### 2. Start the dashboard diff --git a/demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl b/demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl index eb06f284a..aa2addf3a 100644 --- a/demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl +++ b/demos/claude-code-multiplex/claude-code-multiplex.yaml.tmpl @@ -13,8 +13,8 @@ # limitations under the License. # Three ActorTemplates share a 2-pod WorkerPool, so substrate must suspend -# at least one actor at any moment. ANTHROPIC_API_KEY is stored in a Secret -# and referenced from ActorTemplate container env with valueFrom.secretKeyRef. +# at least one actor at any moment. ANTHROPIC_API_KEY is substituted into +# the ActorTemplate container env as a plain value at apply time. # # WORKLOAD_IMAGE is the resolved sha256-digest reference for the # claude-multiplex-demo-workload image — built and pushed to @@ -28,17 +28,6 @@ metadata: --- -apiVersion: v1 -kind: Secret -metadata: - name: anthropic-api-key - namespace: claude-multiplex-demo -type: Opaque -stringData: - api-key: "${ANTHROPIC_API_KEY}" - ---- - # 2 worker replicas for 3 actors — the multiplex pressure that makes the # substrate suspend/resume behavior visible. apiVersion: ate.dev/v1alpha1 @@ -77,10 +66,7 @@ spec: - name: INTERVAL_SECONDS value: "45" - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: anthropic-api-key - key: api-key + value: "${ANTHROPIC_API_KEY}" workerSelector: matchLabels: workload: claude-multiplex @@ -107,10 +93,7 @@ spec: - name: INTERVAL_SECONDS value: "45" - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: anthropic-api-key - key: api-key + value: "${ANTHROPIC_API_KEY}" workerSelector: matchLabels: workload: claude-multiplex @@ -137,10 +120,7 @@ spec: - name: INTERVAL_SECONDS value: "45" - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: anthropic-api-key - key: api-key + value: "${ANTHROPIC_API_KEY}" workerSelector: matchLabels: workload: claude-multiplex diff --git a/demos/claude-code-multiplex/workload/run.sh b/demos/claude-code-multiplex/workload/run.sh index ced38e28e..f2b0938f9 100755 --- a/demos/claude-code-multiplex/workload/run.sh +++ b/demos/claude-code-multiplex/workload/run.sh @@ -21,7 +21,7 @@ # Env vars: # TASK — the prompt to pass to claude-code each tick # INTERVAL_SECONDS — sleep length between ticks (longer = more multiplex headroom) -# ANTHROPIC_API_KEY — required; supplied via Secret-backed env +# ANTHROPIC_API_KEY — required; supplied via container env set -u diff --git a/docs/api-guide.md b/docs/api-guide.md index 2ef434710..1a87113cf 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -151,7 +151,7 @@ The sandbox binaries (e.g. the gVisor `runsc` binary) are **no longer configured Because a snapshot is not restorable across sandbox runtimes, `sandboxClass` is a **hard scheduling gate**: an actor is only ever placed on a `WorkerPool` of the matching class. It is AND'd with `workerSelector` (and the actor's `worker_selector`), which can only narrow the eligible pools further. It defaults to `gvisor` and, like the rest of the spec, is immutable, so each template's class is fixed at creation. -Container environment variables support literal `value` entries and `valueFrom.secretKeyRef`. Secret references are resolved by `ate-api-server` from the `ActorTemplate` namespace when a workload spec is materialized. For the golden actor, the resolved values are captured in the golden snapshot and future actors inherit those values until the golden snapshot is recreated. For an actor that bypasses the golden snapshot and boots from the current template spec, the resolved values are sent to atelet but are not serialized into the public Actor API. Other Kubernetes `valueFrom` sources are not supported yet. Secret changes do not automatically restart actors or invalidate snapshots; rotating a Secret requires an explicit actor or template lifecycle action. +Container environment variables support literal `value` entries only. Values are not interpolated (`$(VAR)` references are not expanded), and Kubernetes `envFrom`/`valueFrom` sources are not supported. ### Workload Connectivity (Uniform DNS) Substrate uses a **Uniform DNS Mesh**: every actor created from a template is automatically reachable through the **Substrate Router** via its atespace and name: @@ -173,7 +173,7 @@ Each entry in `containers` describes one process to run in the actor's sandbox. | `image` | `string` | **Required.** Must be pinned by digest (`...@sha256:...`) — changing the image invalidates snapshots. | | `command` | `[]string` | Optional. Entrypoint array. If unset, the image's `ENTRYPOINT` is used. If set, it replaces **both** the image's `ENTRYPOINT` and `CMD`. | | `args` | `[]string` | Optional. Arguments to the entrypoint. If unset, the image's `CMD` is used (unless `command` is set, which discards the image's `CMD`). If set, it replaces the image's `CMD`. | -| `env` | `[]EnvVar` | Optional. Literal `value` entries or `valueFrom.secretKeyRef`. | +| `env` | `[]EnvVar` | Optional. Literal `value` entries. | | `readyz` | `ContainerReadyz` | Optional. HTTP readiness probe — see [Container Readiness Probe](#container-readiness-probe-readyz). | | `volumeMounts` | `[]VolumeMount` | Optional. Mounts a `spec.volumes` entry (e.g. `durableDir`) into this container. | diff --git a/manifests/ate-install/ate-api-server.yaml b/manifests/ate-install/ate-api-server.yaml index 560f32e20..ecdfbf8ed 100644 --- a/manifests/ate-install/ate-api-server.yaml +++ b/manifests/ate-install/ate-api-server.yaml @@ -31,11 +31,6 @@ rules: - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "watch", "list"] -# Secret reads for env source resolution are intentionally NOT granted -# cluster-wide here. Each demo / tenant is responsible for granting -# ate-api-server read access only to the specific Secrets referenced by its -# ActorTemplates (e.g. via a namespace-scoped Role + RoleBinding using -# resourceNames). --- # Create Service Account for Workload Identity apiVersion: v1 diff --git a/manifests/ate-install/generated/ate.dev_actortemplates.yaml b/manifests/ate-install/generated/ate.dev_actortemplates.yaml index 1dff97416..b074bd47b 100644 --- a/manifests/ate-install/generated/ate.dev_actortemplates.yaml +++ b/manifests/ate-install/generated/ate.dev_actortemplates.yaml @@ -97,7 +97,7 @@ spec: EnvVar represents an environment variable supplied to a container in an ActorTemplate. It models only a subset of Kubernetes Pod env behavior: literal values are not expanded with Kubernetes-style $(VAR) references, - envFrom is not supported, and valueFrom currently supports only secretKeyRef. + and envFrom and valueFrom are not supported. properties: name: description: |- @@ -108,52 +108,15 @@ spec: type: string value: description: |- - Variable value. Mutually exclusive with ValueFrom. Value is the literal value of the environment variable. Unlike in Kubernetes pods, this value is not interpolated, and $(VAR) references are not expanded. minLength: 0 type: string - valueFrom: - description: |- - Source for the environment variable's value. Mutually exclusive with - Value. - maxProperties: 1 - minProperties: 1 - properties: - secretKeyRef: - description: Selects a key of a Secret in the ActorTemplate's - namespace. - properties: - key: - description: Key to select within the Secret. - minLength: 1 - pattern: ^[-._a-zA-Z0-9]+$ - type: string - name: - description: Name of the referent Secret. - maxLength: 253 - type: string - x-kubernetes-validations: - - message: Name must be a valid DNS subdomain - rule: '!format.dns1123Subdomain().validate(self).hasValue()' - optional: - description: Specify whether the Secret or its - key must be defined. - type: boolean - required: - - key - - name - type: object - type: object required: - name + - value type: object - x-kubernetes-validations: - - message: exactly one of the fields in [value valueFrom] - must be set - rule: '[has(self.value),has(self.valueFrom)].filter(x,x==true).size() - == 1' maxItems: 32 type: array image: diff --git a/pkg/api/v1alpha1/actortemplate_types.go b/pkg/api/v1alpha1/actortemplate_types.go index 1a2480a33..5574e604e 100644 --- a/pkg/api/v1alpha1/actortemplate_types.go +++ b/pkg/api/v1alpha1/actortemplate_types.go @@ -206,9 +206,7 @@ type HTTPGetAction struct { // EnvVar represents an environment variable supplied to a container in an // ActorTemplate. It models only a subset of Kubernetes Pod env behavior: // literal values are not expanded with Kubernetes-style $(VAR) references, -// envFrom is not supported, and valueFrom currently supports only secretKeyRef. -// -// +kubebuilder:validation:ExactlyOneOf={value, valueFrom} +// and envFrom and valueFrom are not supported. type EnvVar struct { // Name is the name of the environment variable. May be any printable ASCII // character except '='. @@ -218,56 +216,13 @@ type EnvVar struct { // +kubebuilder:validation:Pattern=`^[ -<>-~]+$` Name string `json:"name"` - // Exactly one of the following must be specified. - - // Variable value. Mutually exclusive with ValueFrom. // Value is the literal value of the environment variable. Unlike in // Kubernetes pods, this value is not interpolated, and $(VAR) // references are not expanded. // - // +optional - // +kubebuilder:validation:MinLength=0 - Value *string `json:"value,omitempty"` - - // Source for the environment variable's value. Mutually exclusive with - // Value. - // - // +optional - ValueFrom *EnvVarSource `json:"valueFrom,omitempty"` -} - -// EnvVarSource represents a source for the value of an EnvVar. Exactly one of -// its fields must be set. -// -// +kubebuilder:validation:MinProperties=1 -// +kubebuilder:validation:MaxProperties=1 -type EnvVarSource struct { - // Selects a key of a Secret in the ActorTemplate's namespace. - // - // +optional - SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` -} - -// SecretKeySelector selects a key from a Secret. -type SecretKeySelector struct { - // Name of the referent Secret. - // - // +required - // +kubebuilder:validation:MaxLength=253 - // +kubebuilder:validation:XValidation:rule="!format.dns1123Subdomain().validate(self).hasValue()",message="Name must be a valid DNS subdomain" - Name string `json:"name"` - - // Key to select within the Secret. - // // +required - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:Pattern=`^[-._a-zA-Z0-9]+$` - Key string `json:"key"` - - // Specify whether the Secret or its key must be defined. - // - // +optional - Optional *bool `json:"optional,omitempty"` + // +kubebuilder:validation:MinLength=0 + Value string `json:"value"` } // SnapshotScope defines what components to include in a snapshot. diff --git a/pkg/api/v1alpha1/actortemplate_validation_test.go b/pkg/api/v1alpha1/actortemplate_validation_test.go index 4b0a5d54c..7dfec3147 100644 --- a/pkg/api/v1alpha1/actortemplate_validation_test.go +++ b/pkg/api/v1alpha1/actortemplate_validation_test.go @@ -29,7 +29,6 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -214,7 +213,7 @@ func TestActorTemplateValidation(t *testing.T) { name: "valid EnvVar", mutate: func(at *ActorTemplate) { at.Spec.Containers[0].Env = []EnvVar{ - {Name: "FOO", Value: ptr.To("BAR")}, + {Name: "FOO", Value: "BAR"}, } }, wantErr: false, @@ -222,7 +221,7 @@ func TestActorTemplateValidation(t *testing.T) { name: "long EnvVar", mutate: func(at *ActorTemplate) { for range 32 { - at.Spec.Containers[0].Env = append(at.Spec.Containers[0].Env, EnvVar{Name: "X", Value: ptr.To("Y")}) + at.Spec.Containers[0].Env = append(at.Spec.Containers[0].Env, EnvVar{Name: "X", Value: "Y"}) } }, wantErr: false, @@ -230,7 +229,7 @@ func TestActorTemplateValidation(t *testing.T) { name: "too-many EnvVar", mutate: func(at *ActorTemplate) { for range 33 { - at.Spec.Containers[0].Env = append(at.Spec.Containers[0].Env, EnvVar{Name: "X", Value: ptr.To("Y")}) + at.Spec.Containers[0].Env = append(at.Spec.Containers[0].Env, EnvVar{Name: "X", Value: "Y"}) } }, wantErr: true, @@ -238,143 +237,29 @@ func TestActorTemplateValidation(t *testing.T) { }, { name: "envVar Name with space", mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO BAR", Value: ptr.To("VAL")}} + at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO BAR", Value: "VAL"}} }, wantErr: false, // strange but valid }, { name: "empty EnvVar Name", mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{Name: "", Value: ptr.To("VAL")}} + at.Spec.Containers[0].Env = []EnvVar{{Name: "", Value: "VAL"}} }, wantErr: true, errMsg: "Invalid value", }, { name: "invalid EnvVar Name (contains '=')", mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO=BAR", Value: ptr.To("VAL")}} + at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO=BAR", Value: "VAL"}} }, wantErr: true, errMsg: "Invalid value", }, { - name: "missing EnvVar Value", + name: "empty EnvVar Value", mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO"}} - }, - wantErr: true, - errMsg: "Invalid value", - }, { - name: "EnvVar with ValueFrom SecretKeyRef", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: "my-secret", - Key: "my-key", - }, - }, - }} + at.Spec.Containers[0].Env = []EnvVar{{Name: "FOO", Value: ""}} }, wantErr: false, - }, { - name: "EnvVar with both Value and ValueFrom", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - Value: ptr.To("BAR"), - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: "my-secret", - Key: "my-key", - }, - }, - }} - }, - wantErr: true, - errMsg: "exactly one of the fields in", - }, { - name: "EnvVarSource empty", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{}, - }} - }, - wantErr: true, - errMsg: "Invalid value", - }, { - name: "SecretKeySelector missing Name", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Key: "my-key", - }, - }, - }} - }, - wantErr: true, - errMsg: "Name must be a valid DNS subdomain", - }, { - name: "SecretKeySelector Name too long", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: strings.Repeat("x", 254), - Key: "my-key", - }, - }, - }} - }, - wantErr: true, - errMsg: "Too long", - }, { - name: "SecretKeySelector invalid Name", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: "Invalid_Name", - Key: "my-key", - }, - }, - }} - }, - wantErr: true, - errMsg: "Name must be a valid DNS subdomain", - }, { - name: "SecretKeySelector missing Key", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: "my-secret", - }, - }, - }} - }, - wantErr: true, - errMsg: "at least 1 chars long", - }, { - name: "SecretKeySelector invalid Key", - mutate: func(at *ActorTemplate) { - at.Spec.Containers[0].Env = []EnvVar{{ - Name: "FOO", - ValueFrom: &EnvVarSource{ - SecretKeyRef: &SecretKeySelector{ - Name: "my-secret", - Key: "invalid/key", - }, - }, - }} - }, - wantErr: true, - errMsg: "Invalid value", }, { name: "valid Readyz with default path", mutate: func(at *ActorTemplate) { diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index bdcf3505c..fb0b8841a 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -245,9 +245,7 @@ func (in *Container) DeepCopyInto(out *Container) { if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]EnvVar, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + copy(*out, *in) } if in.Readyz != nil { in, out := &in.Readyz, &out.Readyz @@ -309,16 +307,6 @@ func (in *DurableDirVolumeSource) DeepCopy() *DurableDirVolumeSource { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvVar) DeepCopyInto(out *EnvVar) { *out = *in - if in.Value != nil { - in, out := &in.Value, &out.Value - *out = new(string) - **out = **in - } - if in.ValueFrom != nil { - in, out := &in.ValueFrom, &out.ValueFrom - *out = new(EnvVarSource) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar. @@ -331,26 +319,6 @@ func (in *EnvVar) DeepCopy() *EnvVar { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) { - *out = *in - if in.SecretKeyRef != nil { - in, out := &in.SecretKeyRef, &out.SecretKeyRef - *out = new(SecretKeySelector) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVarSource. -func (in *EnvVarSource) DeepCopy() *EnvVarSource { - if in == nil { - return nil - } - out := new(EnvVarSource) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalVolumeTemplate) DeepCopyInto(out *ExternalVolumeTemplate) { *out = *in @@ -488,26 +456,6 @@ func (in *SandboxConfigSpec) DeepCopy() *SandboxConfigSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { - *out = *in - if in.Optional != nil { - in, out := &in.Optional, &out.Optional - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. -func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { - if in == nil { - return nil - } - out := new(SecretKeySelector) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SnapshotsConfig) DeepCopyInto(out *SnapshotsConfig) { *out = *in