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
10 changes: 0 additions & 10 deletions api/v1alpha1/operatorconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ type JumpstarterTargetMapping struct {
// Example: "j storage flash ${IMAGE}"
// +optional
FlashCmd string `json:"flashCmd,omitempty"`

// Architecture is the default CPU architecture for builds targeting this device
// Example: "arm64"
// +optional
Architecture string `json:"architecture,omitempty"`

// ExtraArgs are default extra arguments passed to AIB for builds targeting this device
// Example: ["--separate-partitions"]
// +optional
ExtraArgs []string `json:"extraArgs,omitempty"`
}

// DefaultJumpstarterImage is the default container image for Jumpstarter CLI operations
Expand Down
7 changes: 1 addition & 6 deletions api/v1alpha1/zz_generated.deepcopy.go

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

12 changes: 6 additions & 6 deletions cmd/caib/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,26 +813,26 @@ func fetchTargetDefaults(ctx context.Context, api *buildapiclient.Client, target
}

// applyTargetDefaults applies architecture and extra-args defaults from the operator config
// target mapping. CLI flags override mapping defaults when explicitly set.
// target defaults (ConfigMap). CLI flags override defaults when explicitly set.
func applyTargetDefaults(cmd *cobra.Command, config *buildapitypes.OperatorConfigResponse, req *buildapitypes.BuildRequest) {
if config == nil || len(config.JumpstarterTargets) == 0 {
if config == nil || len(config.TargetDefaults) == 0 {
return
}

defaults, exists := config.JumpstarterTargets[string(req.Target)]
defaults, exists := config.TargetDefaults[string(req.Target)]
if !exists {
return
}

if defaults.Architecture != "" && !cmd.Flags().Changed("arch") {
req.Architecture = buildapitypes.Architecture(defaults.Architecture)
fmt.Printf("Using architecture %q from target mapping for %q\n", defaults.Architecture, req.Target)
fmt.Printf("Using architecture %q from target defaults for %q\n", defaults.Architecture, req.Target)
}

if len(defaults.ExtraArgs) > 0 {
// Mapping args come first, user args appended
// Default args come first, user args appended
req.AIBExtraArgs = append(defaults.ExtraArgs, req.AIBExtraArgs...)
fmt.Printf("Prepending extra args %v from target mapping for %q\n", defaults.ExtraArgs, req.Target)
fmt.Printf("Prepending extra args %v from target defaults for %q\n", defaults.ExtraArgs, req.Target)
}
}

Expand Down
27 changes: 10 additions & 17 deletions cmd/caib/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestApplyTargetDefaults_NilConfig(t *testing.T) {
func TestApplyTargetDefaults_EmptyTargets(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{},
TargetDefaults: map[string]buildapitypes.TargetDefaults{},
}
req := &buildapitypes.BuildRequest{
Target: "ebbr",
Expand All @@ -53,8 +53,8 @@ func TestApplyTargetDefaults_EmptyTargets(t *testing.T) {
func TestApplyTargetDefaults_NoMatchingTarget(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
"qemu": {Selector: "board-type=qemu"},
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"qemu": {},
},
}
req := &buildapitypes.BuildRequest{
Expand All @@ -72,9 +72,8 @@ func TestApplyTargetDefaults_NoMatchingTarget(t *testing.T) {
func TestApplyTargetDefaults_AppliesArchFromMapping(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ebbr": {
Selector: "board-type=ebbr",
Architecture: archARM64,
},
},
Expand All @@ -94,9 +93,8 @@ func TestApplyTargetDefaults_AppliesArchFromMapping(t *testing.T) {
func TestApplyTargetDefaults_ExplicitArchOverridesMapping(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, true) // user explicitly set --arch amd64
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ebbr": {
Selector: "board-type=ebbr",
Architecture: archARM64,
},
},
Expand All @@ -116,9 +114,8 @@ func TestApplyTargetDefaults_ExplicitArchOverridesMapping(t *testing.T) {
func TestApplyTargetDefaults_ExplicitArchArm64OverridesMapping(t *testing.T) {
cmd := newCmdWithArchFlag(archARM64, true) // user explicitly set --arch arm64
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ebbr": {
Selector: "board-type=ebbr",
Architecture: archAMD64, // mapping says amd64
},
},
Expand All @@ -138,9 +135,8 @@ func TestApplyTargetDefaults_ExplicitArchArm64OverridesMapping(t *testing.T) {
func TestApplyTargetDefaults_PrependsExtraArgs(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ride": {
Selector: "board-type=ride",
ExtraArgs: []string{"--separate-partitions"},
},
},
Expand All @@ -167,9 +163,8 @@ func TestApplyTargetDefaults_PrependsExtraArgs(t *testing.T) {
func TestApplyTargetDefaults_ExtraArgsWithNoUserArgs(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ride": {
Selector: "board-type=ride",
ExtraArgs: []string{"--separate-partitions", "--verbose"},
},
},
Expand All @@ -195,9 +190,8 @@ func TestApplyTargetDefaults_ExtraArgsWithNoUserArgs(t *testing.T) {
func TestApplyTargetDefaults_BothArchAndExtraArgs(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"ride": {
Selector: "board-type=ride",
Architecture: archARM64,
ExtraArgs: []string{"--separate-partitions"},
},
Expand Down Expand Up @@ -228,9 +222,8 @@ func TestApplyTargetDefaults_BothArchAndExtraArgs(t *testing.T) {
func TestApplyTargetDefaults_MappingWithEmptyArchDoesNotOverride(t *testing.T) {
cmd := newCmdWithArchFlag(archAMD64, false)
config := &buildapitypes.OperatorConfigResponse{
JumpstarterTargets: map[string]buildapitypes.TargetDefaults{
TargetDefaults: map[string]buildapitypes.TargetDefaults{
"qemu": {
Selector: "board-type=qemu",
// Architecture intentionally empty
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,18 +442,6 @@ spec:
description: JumpstarterTargetMapping defines the Jumpstarter
configuration for a specific build target
properties:
architecture:
description: |-
Architecture is the default CPU architecture for builds targeting this device
Example: "arm64"
type: string
extraArgs:
description: |-
ExtraArgs are default extra arguments passed to AIB for builds targeting this device
Example: ["--separate-partitions"]
items:
type: string
type: array
flashCmd:
description: |-
FlashCmd is the command template for flashing the device
Expand Down
61 changes: 55 additions & 6 deletions internal/buildapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-logr/logr"
"github.com/google/uuid"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -82,6 +83,44 @@ var loadOperatorConfigFn = func(
return operatorConfig, nil
}

var loadTargetDefaultsFn = func(
ctx context.Context,
k8sClient client.Client,
namespace string,
) (map[string]TargetDefaults, error) {
cm := &corev1.ConfigMap{}
if err := k8sClient.Get(ctx, types.NamespacedName{
Namespace: namespace,
Name: "aib-target-defaults",
}, cm); err != nil {
return nil, err
}

data, ok := cm.Data["target-defaults.yaml"]
if !ok {
return nil, nil
}

var parsed struct {
Targets map[string]struct {
Architecture string `yaml:"architecture"`
ExtraArgs []string `yaml:"extraArgs"`
} `yaml:"targets"`
}
if err := yaml.Unmarshal([]byte(data), &parsed); err != nil {
return nil, fmt.Errorf("failed to parse target-defaults.yaml: %w", err)
}

result := make(map[string]TargetDefaults, len(parsed.Targets))
for name, t := range parsed.Targets {
result[name] = TargetDefaults{
Architecture: t.Architecture,
ExtraArgs: t.ExtraArgs,
}
}
return result, nil
}

// defaultInternalRegistryURL is an alias for the shared constant.
const defaultInternalRegistryURL = tasks.DefaultInternalRegistryURL

Expand Down Expand Up @@ -2489,20 +2528,30 @@ func (a *APIServer) handleGetOperatorConfig(c *gin.Context) {
return
}

// Build the response with Jumpstarter target mappings
// Build the response with Jumpstarter target mappings (flash-specific, from CRD)
response := OperatorConfigResponse{}

if operatorConfig.Spec.Jumpstarter != nil && len(operatorConfig.Spec.Jumpstarter.TargetMappings) > 0 {
response.JumpstarterTargets = make(map[string]TargetDefaults)
response.JumpstarterTargets = make(map[string]JumpstarterTarget)
for target, mapping := range operatorConfig.Spec.Jumpstarter.TargetMappings {
response.JumpstarterTargets[target] = TargetDefaults{
Selector: mapping.Selector,
Architecture: mapping.Architecture,
ExtraArgs: mapping.ExtraArgs,
response.JumpstarterTargets[target] = JumpstarterTarget{
Selector: mapping.Selector,
FlashCmd: mapping.FlashCmd,
}
}
}

// Load build defaults from target-defaults ConfigMap
targetDefaults, err := loadTargetDefaultsFn(ctx, k8sClient, namespace)
if err != nil {
if !k8serrors.IsNotFound(err) {
a.log.Error(err, "failed to load target defaults ConfigMap", "reqID", reqID, "namespace", namespace)
}
// Non-fatal: continue without target defaults
} else if len(targetDefaults) > 0 {
response.TargetDefaults = targetDefaults
}

c.JSON(http.StatusOK, response)
}

Expand Down
26 changes: 19 additions & 7 deletions internal/buildapi/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,23 @@ var _ = Describe("APIServer", func() {
var (
originalGetClientFromRequestFn func(*gin.Context) (ctrlclient.Client, error)
originalLoadOperatorConfigFn func(context.Context, ctrlclient.Client, string) (*automotivev1alpha1.OperatorConfig, error)
originalLoadTargetDefaultsFn func(context.Context, ctrlclient.Client, string) (map[string]TargetDefaults, error)
originalNamespace string
hasOriginalNamespace bool
)

BeforeEach(func() {
originalGetClientFromRequestFn = getClientFromRequestFn
originalLoadOperatorConfigFn = loadOperatorConfigFn
originalLoadTargetDefaultsFn = loadTargetDefaultsFn
originalNamespace, hasOriginalNamespace = os.LookupEnv("BUILD_API_NAMESPACE")
Expect(os.Setenv("BUILD_API_NAMESPACE", "default")).To(Succeed())
})

AfterEach(func() {
getClientFromRequestFn = originalGetClientFromRequestFn
loadOperatorConfigFn = originalLoadOperatorConfigFn
loadTargetDefaultsFn = originalLoadTargetDefaultsFn
if hasOriginalNamespace {
Expect(os.Setenv("BUILD_API_NAMESPACE", originalNamespace)).To(Succeed())
} else {
Expand Down Expand Up @@ -153,9 +156,10 @@ var _ = Describe("APIServer", func() {
var response OperatorConfigResponse
Expect(json.Unmarshal(w.Body.Bytes(), &response)).To(Succeed())
Expect(response.JumpstarterTargets).To(BeNil())
Expect(response.TargetDefaults).To(BeNil())
})

It("should return jumpstarter target mappings when config exists", func() {
It("should return jumpstarter targets and target defaults when config exists", func() {
config := &automotivev1alpha1.OperatorConfig{
Spec: automotivev1alpha1.OperatorConfigSpec{
Jumpstarter: &automotivev1alpha1.JumpstarterConfig{
Expand All @@ -164,9 +168,8 @@ var _ = Describe("APIServer", func() {
Selector: "board-type=qemu",
},
"ebbr": {
Selector: "board-type=ebbr",
Architecture: "arm64",
ExtraArgs: []string{"--separate-partitions"},
Selector: "board-type=ebbr",
FlashCmd: "j storage flash ${IMAGE}",
},
},
},
Expand All @@ -179,6 +182,11 @@ var _ = Describe("APIServer", func() {
loadOperatorConfigFn = func(_ context.Context, _ ctrlclient.Client, _ string) (*automotivev1alpha1.OperatorConfig, error) {
return config, nil
}
loadTargetDefaultsFn = func(_ context.Context, _ ctrlclient.Client, _ string) (map[string]TargetDefaults, error) {
return map[string]TargetDefaults{
"ebbr": {Architecture: "arm64", ExtraArgs: []string{"--separate-partitions"}},
}, nil
}

req, err := http.NewRequest(http.MethodGet, "/v1/config", nil)
Expect(err).NotTo(HaveOccurred())
Expand All @@ -193,9 +201,13 @@ var _ = Describe("APIServer", func() {
var response OperatorConfigResponse
Expect(json.Unmarshal(w.Body.Bytes(), &response)).To(Succeed())
Expect(response.JumpstarterTargets).To(HaveLen(2))
Expect(response.JumpstarterTargets["qemu"]).To(Equal(TargetDefaults{Selector: "board-type=qemu"}))
Expect(response.JumpstarterTargets["ebbr"]).To(Equal(TargetDefaults{
Selector: "board-type=ebbr",
Expect(response.JumpstarterTargets["qemu"]).To(Equal(JumpstarterTarget{Selector: "board-type=qemu"}))
Expect(response.JumpstarterTargets["ebbr"]).To(Equal(JumpstarterTarget{
Selector: "board-type=ebbr",
FlashCmd: "j storage flash ${IMAGE}",
}))
Expect(response.TargetDefaults).To(HaveLen(1))
Expect(response.TargetDefaults["ebbr"]).To(Equal(TargetDefaults{
Architecture: "arm64",
ExtraArgs: []string{"--separate-partitions"},
}))
Expand Down
15 changes: 11 additions & 4 deletions internal/buildapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,17 +252,24 @@ type BuildListItem struct {
DiskImage string `json:"diskImage,omitempty"`
}

// TargetDefaults contains build defaults and Jumpstarter configuration for a target
// JumpstarterTarget contains flash-specific config for a target (from CRD)
type JumpstarterTarget struct {
Selector string `json:"selector"`
FlashCmd string `json:"flashCmd,omitempty"`
}

// TargetDefaults contains build defaults for a target (from ConfigMap)
type TargetDefaults struct {
Selector string `json:"selector"`
Architecture string `json:"architecture,omitempty"`
ExtraArgs []string `json:"extraArgs,omitempty"`
}

// OperatorConfigResponse returns relevant operator configuration for CLI validation
type OperatorConfigResponse struct {
// JumpstarterTargets contains the target mappings with build defaults
JumpstarterTargets map[string]TargetDefaults `json:"jumpstarterTargets,omitempty"`
// JumpstarterTargets contains flash-specific config per target (from CRD)
JumpstarterTargets map[string]JumpstarterTarget `json:"jumpstarterTargets,omitempty"`
// TargetDefaults contains build defaults per target (from ConfigMap)
TargetDefaults map[string]TargetDefaults `json:"targetDefaults,omitempty"`
}

type (
Expand Down
2 changes: 1 addition & 1 deletion internal/common/tasks/scripts/push_artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ distro="$(params.distro)"
target="$(params.target)"
arch="$(params.arch)"

config_file="/etc/partition-config/partition-rules.yaml"
config_file="/etc/target-defaults/target-defaults.yaml"
default_partitions=""
if [ -f "$config_file" ]; then
# Use yq to extract included partitions for target (using bracket notation for safety)
Expand Down
Loading