Skip to content
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
19 changes: 19 additions & 0 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ type ByoDeploymentSpec struct {
SharedDeploymentSpec `json:",inline"`
}

// +kubebuilder:validation:XValidation:message="serviceAccountName and serviceAccountConfig are mutually exclusive",rule="!(has(self.serviceAccountName) && has(self.serviceAccountConfig))"
type SharedDeploymentSpec struct {
// +optional
Replicas *int32 `json:"replicas,omitempty"`
Expand Down Expand Up @@ -173,6 +174,24 @@ type SharedDeploymentSpec struct {
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
// +optional
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
// ServiceAccountName specifies the name of an existing ServiceAccount to use.
// If this field is set, the Agent controller will not create a ServiceAccount for the agent.
// This field is mutually exclusive with ServiceAccountConfig.
// +optional
ServiceAccountName *string `json:"serviceAccountName,omitempty"`
// ServiceAccountConfig configures the ServiceAccount created by the Agent controller.
// This field can only be used when ServiceAccountName is not set.
// If ServiceAccountName is not set, a default ServiceAccount (named after the agent)
// is created, and this config will be applied to it.
// +optional
ServiceAccountConfig *ServiceAccountConfig `json:"serviceAccountConfig,omitempty"`
Comment on lines +180 to +187
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a cel rule to ensure that only one of these can be set. If the ServiceAccountName is set, than we won't create the ServiceAccount. And if the config is set, then the name can't be changed.

Also can you add more comments to these fields to explain a bit about this interaction

}

type ServiceAccountConfig struct {
// +optional
Labels map[string]string `json:"labels,omitempty"`
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
}

// ToolProviderType represents the tool provider type
Expand Down
39 changes: 39 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions go/config/crd/bases/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4091,6 +4091,28 @@ spec:
type: string
type: object
type: object
serviceAccountConfig:
description: |-
ServiceAccountConfig configures the ServiceAccount created by the Agent controller.
This field can only be used when ServiceAccountName is not set.
If ServiceAccountName is not set, a default ServiceAccount (named after the agent)
is created, and this config will be applied to it.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
serviceAccountName:
description: |-
ServiceAccountName specifies the name of an existing ServiceAccount to use.
If this field is set, the Agent controller will not create a ServiceAccount for the agent.
This field is mutually exclusive with ServiceAccountConfig.
type: string
tolerations:
items:
description: |-
Expand Down Expand Up @@ -6112,6 +6134,10 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: serviceAccountName and serviceAccountConfig are mutually
exclusive
rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))'
type: object
declarative:
properties:
Expand Down Expand Up @@ -7783,6 +7809,28 @@ spec:
type: string
type: object
type: object
serviceAccountConfig:
description: |-
ServiceAccountConfig configures the ServiceAccount created by the Agent controller.
This field can only be used when ServiceAccountName is not set.
If ServiceAccountName is not set, a default ServiceAccount (named after the agent)
is created, and this config will be applied to it.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
serviceAccountName:
description: |-
ServiceAccountName specifies the name of an existing ServiceAccount to use.
If this field is set, the Agent controller will not create a ServiceAccount for the agent.
This field is mutually exclusive with ServiceAccountConfig.
type: string
tolerations:
items:
description: |-
Expand Down Expand Up @@ -9804,6 +9852,10 @@ spec:
type: object
type: array
type: object
x-kubernetes-validations:
- message: serviceAccountName and serviceAccountConfig are mutually
exclusive
rule: '!(has(self.serviceAccountName) && has(self.serviceAccountConfig))'
executeCodeBlocks:
description: |-
Allow code execution for python code blocks with this agent.
Expand Down
148 changes: 91 additions & 57 deletions go/internal/controller/translator/agent/adk_api_translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,31 @@ func (a *adkApiTranslator) buildManifest(
},
})

// Service Account
outputs.Manifest = append(outputs.Manifest, &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: objMeta(),
})
// Service Account - only created if using the default name
if *dep.ServiceAccountName == agent.Name {
sa := &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: objMeta(),
}
if dep.ServiceAccountConfig != nil {
if dep.ServiceAccountConfig.Labels != nil {
if sa.Labels == nil {
sa.Labels = make(map[string]string)
}
maps.Copy(sa.Labels, dep.ServiceAccountConfig.Labels)
}
if dep.ServiceAccountConfig.Annotations != nil {
if sa.Annotations == nil {
sa.Annotations = make(map[string]string)
}
maps.Copy(sa.Annotations, dep.ServiceAccountConfig.Annotations)
}
}
outputs.Manifest = append(outputs.Manifest, sa)
}

// Base env for both types
sharedEnv := make([]corev1.EnvVar, 0, 8)
Expand Down Expand Up @@ -465,7 +482,7 @@ func (a *adkApiTranslator) buildManifest(
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: podLabels(), Annotations: podTemplateAnnotations},
Spec: corev1.PodSpec{
ServiceAccountName: agent.Name,
ServiceAccountName: *dep.ServiceAccountName,
ImagePullSecrets: dep.ImagePullSecrets,
SecurityContext: dep.PodSecurityContext,
InitContainers: initContainers,
Expand Down Expand Up @@ -1396,19 +1413,21 @@ type resolvedDeployment struct {
ImagePullPolicy corev1.PullPolicy

// SharedDeploymentSpec merged
Replicas *int32
ImagePullSecrets []corev1.LocalObjectReference
Volumes []corev1.Volume
VolumeMounts []corev1.VolumeMount
Labels map[string]string
Annotations map[string]string
Env []corev1.EnvVar
Resources corev1.ResourceRequirements
Tolerations []corev1.Toleration
Affinity *corev1.Affinity
NodeSelector map[string]string
SecurityContext *corev1.SecurityContext
PodSecurityContext *corev1.PodSecurityContext
Replicas *int32
ImagePullSecrets []corev1.LocalObjectReference
Volumes []corev1.Volume
VolumeMounts []corev1.VolumeMount
Labels map[string]string
Annotations map[string]string
Env []corev1.EnvVar
Resources corev1.ResourceRequirements
Tolerations []corev1.Toleration
Affinity *corev1.Affinity
NodeSelector map[string]string
SecurityContext *corev1.SecurityContext
PodSecurityContext *corev1.PodSecurityContext
ServiceAccountName *string
ServiceAccountConfig *v1alpha2.ServiceAccountConfig
}

// getDefaultResources sets default resource requirements if not specified
Expand Down Expand Up @@ -1450,6 +1469,8 @@ func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *m
"/config",
}

serviceAccountName := ptr.To(agent.Name)

// Start with spec deployment spec
spec := v1alpha2.DeclarativeDeploymentSpec{}
if agent.Spec.Declarative.Deployment != nil {
Expand All @@ -1476,23 +1497,30 @@ func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *m
}

dep := &resolvedDeployment{
Image: image,
Args: args,
Port: port,
ImagePullPolicy: imagePullPolicy,
Replicas: spec.Replicas,
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
Volumes: append(slices.Clone(spec.Volumes), mdd.Volumes...),
VolumeMounts: append(slices.Clone(spec.VolumeMounts), mdd.VolumeMounts...),
Labels: getDefaultLabels(agent.Name, spec.Labels),
Annotations: maps.Clone(spec.Annotations),
Env: append(slices.Clone(spec.Env), mdd.EnvVars...),
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
Tolerations: slices.Clone(spec.Tolerations),
Affinity: spec.Affinity,
NodeSelector: maps.Clone(spec.NodeSelector),
SecurityContext: spec.SecurityContext,
PodSecurityContext: spec.PodSecurityContext,
Image: image,
Args: args,
Port: port,
ImagePullPolicy: imagePullPolicy,
Replicas: spec.Replicas,
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
Volumes: append(slices.Clone(spec.Volumes), mdd.Volumes...),
VolumeMounts: append(slices.Clone(spec.VolumeMounts), mdd.VolumeMounts...),
Labels: getDefaultLabels(agent.Name, spec.Labels),
Annotations: maps.Clone(spec.Annotations),
Env: append(slices.Clone(spec.Env), mdd.EnvVars...),
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
Tolerations: slices.Clone(spec.Tolerations),
Affinity: spec.Affinity,
NodeSelector: maps.Clone(spec.NodeSelector),
SecurityContext: spec.SecurityContext,
PodSecurityContext: spec.PodSecurityContext,
ServiceAccountName: spec.ServiceAccountName,
ServiceAccountConfig: spec.ServiceAccountConfig,
}

// If not specified, use the agent name as the service account name
if dep.ServiceAccountName == nil {
dep.ServiceAccountName = serviceAccountName
}

return dep, nil
Expand Down Expand Up @@ -1545,24 +1573,30 @@ func (a *adkApiTranslator) resolveByoDeployment(agent *v1alpha2.Agent) (*resolve
}

dep := &resolvedDeployment{
Image: image,
Cmd: cmd,
Args: args,
Port: port,
ImagePullPolicy: imagePullPolicy,
Replicas: replicas,
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
Volumes: slices.Clone(spec.Volumes),
VolumeMounts: slices.Clone(spec.VolumeMounts),
Labels: getDefaultLabels(agent.Name, spec.Labels),
Annotations: maps.Clone(spec.Annotations),
Env: slices.Clone(spec.Env),
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
Tolerations: slices.Clone(spec.Tolerations),
Affinity: spec.Affinity,
NodeSelector: maps.Clone(spec.NodeSelector),
SecurityContext: spec.SecurityContext,
PodSecurityContext: spec.PodSecurityContext,
Image: image,
Cmd: cmd,
Args: args,
Port: port,
ImagePullPolicy: imagePullPolicy,
Replicas: replicas,
ImagePullSecrets: slices.Clone(spec.ImagePullSecrets),
Volumes: slices.Clone(spec.Volumes),
VolumeMounts: slices.Clone(spec.VolumeMounts),
Labels: getDefaultLabels(agent.Name, spec.Labels),
Annotations: maps.Clone(spec.Annotations),
Env: slices.Clone(spec.Env),
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
Tolerations: slices.Clone(spec.Tolerations),
Affinity: spec.Affinity,
NodeSelector: maps.Clone(spec.NodeSelector),
SecurityContext: spec.SecurityContext,
PodSecurityContext: spec.PodSecurityContext,
ServiceAccountName: spec.ServiceAccountName,
ServiceAccountConfig: spec.ServiceAccountConfig,
}

if dep.ServiceAccountName == nil {
dep.ServiceAccountName = ptr.To(agent.Name)
}

return dep, nil
Expand Down
Loading
Loading