mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-10 10:13:50 +08:00
Add `status` fields to all Substrate resources that need it. Move server-owned fields under it. Fixes #1006 .
148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package controlapi
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"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"
|
|
)
|
|
|
|
// 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{}
|
|
|
|
// add volumes
|
|
for _, vol := range actorTemplate.Spec.Volumes {
|
|
// volume is durable-dir type
|
|
if vol.VolumeSource.DurableDir != nil {
|
|
workloadSpec.Volumes = append(workloadSpec.Volumes, &ateletpb.Volume{
|
|
Name: vol.Name,
|
|
Type: ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR,
|
|
Source: &ateletpb.Volume_DurableDir{
|
|
DurableDir: &ateletpb.DurableDirVolume{},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// TODO: order may be important for nested mounts. Also need to think about
|
|
// nested mount support in general.
|
|
if err := appendExternalVolumes(workloadSpec, actorTemplate, actor); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, ctr := range actorTemplate.Spec.Containers {
|
|
ateletCtr := &ateletpb.Container{
|
|
Name: ctr.Name,
|
|
Image: ctr.Image,
|
|
Command: ctr.Command,
|
|
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,
|
|
MountPath: mount.MountPath,
|
|
})
|
|
}
|
|
workloadSpec.Containers = append(workloadSpec.Containers, ateletCtr)
|
|
}
|
|
|
|
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 {
|
|
if template == nil {
|
|
return nil
|
|
}
|
|
for _, vol := range template.Spec.Volumes {
|
|
if vol.ExternalVolumeTemplate != nil {
|
|
if !isVolumeMounted(vol.Name, template) {
|
|
continue
|
|
}
|
|
if actor == nil {
|
|
return fmt.Errorf("actor is required when externalVolumeTemplate is present")
|
|
}
|
|
|
|
var storageVolID string
|
|
var volType string
|
|
var volCtx map[string]string
|
|
for _, dbVol := range actor.GetStatus().GetActorVolumes() {
|
|
if dbVol.GetVolumeName() == vol.Name {
|
|
storageVolID = dbVol.GetStorageVolumeId()
|
|
volType = dbVol.GetVolumeType()
|
|
volCtx = dbVol.GetVolumeContext()
|
|
break
|
|
}
|
|
}
|
|
if storageVolID == "" {
|
|
return fmt.Errorf("volume %s not found for actor %s", vol.Name, actor.GetMetadata().GetName())
|
|
}
|
|
workloadSpec.Volumes = append(workloadSpec.Volumes, &ateletpb.Volume{
|
|
Name: vol.Name,
|
|
Type: ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL,
|
|
Source: &ateletpb.Volume_External{
|
|
External: &ateletpb.ExternalVolumeSource{
|
|
StorageVolumeId: storageVolID,
|
|
VolumeType: volType,
|
|
VolumeContext: volCtx,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isVolumeMounted(volumeName string, template *atev1alpha1.ActorTemplate) bool {
|
|
for _, ctr := range template.Spec.Containers {
|
|
for _, mount := range ctr.VolumeMounts {
|
|
if mount.Name == volumeName {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// toAteletReadyz projects the CRD readyz field onto the ateletpb wire type.
|
|
// Returns nil when the source is nil so containers without a probe stay
|
|
// unchanged on the wire.
|
|
func toAteletReadyz(in *atev1alpha1.ContainerReadyz) *ateletpb.Readyz {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
out := &ateletpb.Readyz{TimeoutSeconds: in.TimeoutSeconds}
|
|
if in.HTTPGet != nil {
|
|
out.HttpGet = &ateletpb.HTTPGetAction{
|
|
Path: in.HTTPGet.Path,
|
|
Port: in.HTTPGet.Port,
|
|
}
|
|
}
|
|
return out
|
|
}
|