Skip to content

Commit e53cca7

Browse files
feat: add per-deployment ROFL artifacts
1 parent 2e263c2 commit e53cca7

12 files changed

Lines changed: 589 additions & 60 deletions

File tree

build/rofl/manifest.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@ func (m *Manifest) GetMetadata(deployment string) map[string]string {
231231
return meta
232232
}
233233

234+
// ResolveArtifacts resolves the artifact configuration for the given deployment by overlaying
235+
// global manifest artifacts and then deployment-specific artifacts on top of the provided defaults.
236+
func (m *Manifest) ResolveArtifacts(deployment string, defaults ArtifactsConfig) ArtifactsConfig {
237+
resolved := defaults
238+
resolved.Merge(m.Artifacts)
239+
240+
if deployment != "" {
241+
if d := m.Deployments[deployment]; d != nil {
242+
resolved.Merge(d.Artifacts)
243+
}
244+
}
245+
246+
return resolved
247+
}
248+
234249
// SourceFileName returns the filename of the manifest file from which the manifest was loaded or
235250
// an empty string in case the filename is not available.
236251
func (m *Manifest) SourceFileName() string {
@@ -319,6 +334,8 @@ type Deployment struct {
319334
Debug bool `yaml:"debug,omitempty" json:"debug,omitempty"`
320335
// OCIRepository is the optional OCI repository where one can push the ORC to.
321336
OCIRepository string `yaml:"oci_repository,omitempty" json:"oci_repository,omitempty"`
337+
// Artifacts are optional deployment-specific artifact location overrides.
338+
Artifacts *ArtifactsConfig `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
322339
// TrustRoot is the optional trust root configuration.
323340
TrustRoot *TrustRootConfig `yaml:"trust_root,omitempty" json:"trust_root,omitempty"`
324341
// Policy is the ROFL app policy.
@@ -564,6 +581,26 @@ type ArtifactsConfig struct {
564581
Container ContainerArtifactsConfig `yaml:"container,omitempty" json:"container,omitempty"`
565582
}
566583

584+
// Merge overlays non-empty artifact fields from another artifact configuration.
585+
func (ac *ArtifactsConfig) Merge(other *ArtifactsConfig) {
586+
if other == nil {
587+
return
588+
}
589+
if other.Builder != "" {
590+
ac.Builder = other.Builder
591+
}
592+
if other.Firmware != "" {
593+
ac.Firmware = other.Firmware
594+
}
595+
if other.Kernel != "" {
596+
ac.Kernel = other.Kernel
597+
}
598+
if other.Stage2 != "" {
599+
ac.Stage2 = other.Stage2
600+
}
601+
ac.Container.Merge(&other.Container)
602+
}
603+
567604
type artifactUpgrade struct {
568605
existing *string
569606
new string
@@ -599,6 +636,22 @@ func upgradePossible(check []artifactUpgrade) bool {
599636
return false
600637
}
601638

639+
// upgradeExplicitArtifacts upgrades only explicitly configured artifact fields.
640+
func upgradeExplicitArtifacts(upgrade []artifactUpgrade) bool {
641+
var changed bool
642+
for _, artifact := range upgrade {
643+
if artifact.new == "" || *artifact.existing == "" {
644+
continue
645+
}
646+
if *artifact.existing == artifact.new {
647+
continue
648+
}
649+
*artifact.existing = artifact.new
650+
changed = true
651+
}
652+
return changed
653+
}
654+
602655
// UpgradePossible returns true iff any explicitly set artifacts differ from latest.
603656
// Empty fields are ignored (they use defaults from code, so already latest).
604657
func (ac *ArtifactsConfig) UpgradePossible(latest *ArtifactsConfig) bool {
@@ -636,6 +689,21 @@ func (ac *ArtifactsConfig) UpgradeTo(latest *ArtifactsConfig) bool {
636689
return changed
637690
}
638691

692+
// UpgradeExplicitTo upgrades only explicitly configured artifacts to the latest version.
693+
//
694+
// Returns true iff any artifacts have been updated.
695+
func (ac *ArtifactsConfig) UpgradeExplicitTo(latest *ArtifactsConfig) bool {
696+
var changed bool
697+
changed = upgradeExplicitArtifacts([]artifactUpgrade{
698+
{&ac.Builder, latest.Builder},
699+
{&ac.Firmware, latest.Firmware},
700+
{&ac.Kernel, latest.Kernel},
701+
{&ac.Stage2, latest.Stage2},
702+
})
703+
changed = ac.Container.UpgradeExplicitTo(&latest.Container) || changed
704+
return changed
705+
}
706+
639707
// ContainerArtifactsConfig is the container artifacts configuration.
640708
type ContainerArtifactsConfig struct {
641709
// Runtime is the URI/path to the container runtime artifact (empty to use default).
@@ -644,6 +712,19 @@ type ContainerArtifactsConfig struct {
644712
Compose string `yaml:"compose,omitempty" json:"compose,omitempty"`
645713
}
646714

715+
// Merge overlays non-empty container artifact fields from another container artifact configuration.
716+
func (cc *ContainerArtifactsConfig) Merge(other *ContainerArtifactsConfig) {
717+
if other == nil {
718+
return
719+
}
720+
if other.Runtime != "" {
721+
cc.Runtime = other.Runtime
722+
}
723+
if other.Compose != "" {
724+
cc.Compose = other.Compose
725+
}
726+
}
727+
647728
// UpgradeTo upgrades the artifacts to the latest version by updating any relevant fields.
648729
//
649730
// Returns true iff any artifacts have been updated.
@@ -653,3 +734,13 @@ func (cc *ContainerArtifactsConfig) UpgradeTo(latest *ContainerArtifactsConfig)
653734
{&cc.Runtime, latest.Runtime},
654735
})
655736
}
737+
738+
// UpgradeExplicitTo upgrades only explicitly configured container artifacts to the latest version.
739+
//
740+
// Returns true iff any artifacts have been updated.
741+
func (cc *ContainerArtifactsConfig) UpgradeExplicitTo(latest *ContainerArtifactsConfig) bool {
742+
return upgradeExplicitArtifacts([]artifactUpgrade{
743+
{&cc.Compose, latest.Compose},
744+
{&cc.Runtime, latest.Runtime},
745+
})
746+
}

build/rofl/manifest_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,133 @@ func TestManifestSerialization(t *testing.T) {
190190
require.NoError(err, "dec.Validate")
191191
}
192192

193+
func TestDeploymentArtifactsSerialization(t *testing.T) {
194+
require := require.New(t)
195+
196+
const manifestYaml = `
197+
name: my-container-app
198+
version: 0.1.0
199+
tee: tdx
200+
kind: container
201+
resources:
202+
memory: 512
203+
cpus: 1
204+
storage:
205+
kind: disk-persistent
206+
size: 512
207+
artifacts:
208+
firmware: global-firmware
209+
kernel: global-kernel
210+
stage2: global-stage2
211+
container:
212+
runtime: global-runtime
213+
compose: compose.yaml
214+
deployments:
215+
testnet:
216+
network: testnet
217+
paratime: sapphire
218+
artifacts:
219+
container:
220+
compose: compose.testnet.yaml
221+
`
222+
223+
var m Manifest
224+
err := yaml.Unmarshal([]byte(manifestYaml), &m)
225+
require.NoError(err, "yaml.Unmarshal")
226+
require.NoError(m.Validate())
227+
require.NotNil(m.Artifacts)
228+
require.Equal("compose.yaml", m.Artifacts.Container.Compose)
229+
require.NotNil(m.Deployments["testnet"].Artifacts)
230+
require.Equal("compose.testnet.yaml", m.Deployments["testnet"].Artifacts.Container.Compose)
231+
232+
enc, err := yaml.Marshal(m)
233+
require.NoError(err, "yaml.Marshal")
234+
235+
var dec Manifest
236+
err = yaml.Unmarshal(enc, &dec)
237+
require.NoError(err, "yaml.Unmarshal(round-trip)")
238+
require.EqualValues(m, dec, "serialization should round-trip")
239+
require.NoError(dec.Validate())
240+
}
241+
242+
func TestResolveArtifacts(t *testing.T) {
243+
require := require.New(t)
244+
245+
const (
246+
defaultFirmware = "default-firmware"
247+
defaultStage2 = "default-stage2"
248+
defaultRuntime = "default-runtime"
249+
globalKernel = "global-kernel"
250+
globalCompose = "global-compose"
251+
deploymentStage2 = "deployment-stage2"
252+
deploymentCompose = "deployment-compose"
253+
)
254+
255+
defaults := ArtifactsConfig{
256+
Firmware: defaultFirmware,
257+
Kernel: "default-kernel",
258+
Stage2: defaultStage2,
259+
Container: ContainerArtifactsConfig{
260+
Runtime: defaultRuntime,
261+
Compose: "default-compose",
262+
},
263+
}
264+
m := Manifest{
265+
Artifacts: &ArtifactsConfig{
266+
Kernel: globalKernel,
267+
Container: ContainerArtifactsConfig{
268+
Compose: globalCompose,
269+
},
270+
},
271+
Deployments: map[string]*Deployment{
272+
"testnet": {
273+
Network: "testnet",
274+
ParaTime: "sapphire",
275+
Artifacts: &ArtifactsConfig{
276+
Stage2: deploymentStage2,
277+
Container: ContainerArtifactsConfig{
278+
Compose: deploymentCompose,
279+
},
280+
},
281+
},
282+
"mainnet": {
283+
Network: "mainnet",
284+
ParaTime: "sapphire",
285+
},
286+
},
287+
}
288+
289+
require.Equal(ArtifactsConfig{
290+
Firmware: defaultFirmware,
291+
Kernel: globalKernel,
292+
Stage2: deploymentStage2,
293+
Container: ContainerArtifactsConfig{
294+
Runtime: defaultRuntime,
295+
Compose: deploymentCompose,
296+
},
297+
}, m.ResolveArtifacts("testnet", defaults))
298+
299+
require.Equal(ArtifactsConfig{
300+
Firmware: defaultFirmware,
301+
Kernel: globalKernel,
302+
Stage2: defaultStage2,
303+
Container: ContainerArtifactsConfig{
304+
Runtime: defaultRuntime,
305+
Compose: globalCompose,
306+
},
307+
}, m.ResolveArtifacts("mainnet", defaults))
308+
309+
require.Equal(ArtifactsConfig{
310+
Firmware: defaultFirmware,
311+
Kernel: globalKernel,
312+
Stage2: defaultStage2,
313+
Container: ContainerArtifactsConfig{
314+
Runtime: defaultRuntime,
315+
Compose: globalCompose,
316+
},
317+
}, m.ResolveArtifacts("missing", defaults))
318+
}
319+
193320
func TestLoadManifest(t *testing.T) {
194321
require := require.New(t)
195322

@@ -264,6 +391,36 @@ func TestUpgradeArtifacts(t *testing.T) {
264391
require.False(changed)
265392
}
266393

394+
func TestUpgradeExplicitArtifacts(t *testing.T) {
395+
require := require.New(t)
396+
397+
existing := ArtifactsConfig{
398+
Kernel: "old-kernel",
399+
Container: ContainerArtifactsConfig{
400+
Compose: "compose.testnet.yaml",
401+
},
402+
}
403+
latest := ArtifactsConfig{
404+
Firmware: "latest-firmware",
405+
Kernel: "latest-kernel",
406+
Stage2: "latest-stage2",
407+
Container: ContainerArtifactsConfig{
408+
Runtime: "latest-runtime",
409+
},
410+
}
411+
412+
changed := existing.UpgradeExplicitTo(&latest)
413+
require.True(changed)
414+
require.Equal("", existing.Firmware)
415+
require.Equal("latest-kernel", existing.Kernel)
416+
require.Equal("", existing.Stage2)
417+
require.Equal("", existing.Container.Runtime)
418+
require.Equal("compose.testnet.yaml", existing.Container.Compose)
419+
420+
changed = existing.UpgradeExplicitTo(&latest)
421+
require.False(changed)
422+
}
423+
267424
func TestUpgradePossible(t *testing.T) {
268425
require := require.New(t)
269426

0 commit comments

Comments
 (0)