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
91 changes: 91 additions & 0 deletions build/rofl/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ func (m *Manifest) GetMetadata(deployment string) map[string]string {
return meta
}

// ResolveArtifacts resolves the artifact configuration for the given deployment by overlaying
// global manifest artifacts and then deployment-specific artifacts on top of the provided defaults.
func (m *Manifest) ResolveArtifacts(deployment string, defaults ArtifactsConfig) ArtifactsConfig {
resolved := defaults
resolved.Merge(m.Artifacts)

if deployment != "" {
if d := m.Deployments[deployment]; d != nil {
resolved.Merge(d.Artifacts)
}
}

return resolved
}

// SourceFileName returns the filename of the manifest file from which the manifest was loaded or
// an empty string in case the filename is not available.
func (m *Manifest) SourceFileName() string {
Expand Down Expand Up @@ -319,6 +334,8 @@ type Deployment struct {
Debug bool `yaml:"debug,omitempty" json:"debug,omitempty"`
// OCIRepository is the optional OCI repository where one can push the ORC to.
OCIRepository string `yaml:"oci_repository,omitempty" json:"oci_repository,omitempty"`
// Artifacts are optional deployment-specific artifact location overrides.
Artifacts *ArtifactsConfig `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
// TrustRoot is the optional trust root configuration.
TrustRoot *TrustRootConfig `yaml:"trust_root,omitempty" json:"trust_root,omitempty"`
// Policy is the ROFL app policy.
Expand Down Expand Up @@ -564,6 +581,26 @@ type ArtifactsConfig struct {
Container ContainerArtifactsConfig `yaml:"container,omitempty" json:"container,omitempty"`
}

// Merge overlays non-empty artifact fields from another artifact configuration.
func (ac *ArtifactsConfig) Merge(other *ArtifactsConfig) {
if other == nil {
return
}
if other.Builder != "" {
ac.Builder = other.Builder
}
if other.Firmware != "" {
ac.Firmware = other.Firmware
}
if other.Kernel != "" {
ac.Kernel = other.Kernel
}
if other.Stage2 != "" {
ac.Stage2 = other.Stage2
}
ac.Container.Merge(&other.Container)
}

type artifactUpgrade struct {
existing *string
new string
Expand Down Expand Up @@ -599,6 +636,22 @@ func upgradePossible(check []artifactUpgrade) bool {
return false
}

// upgradeExplicitArtifacts upgrades only explicitly configured artifact fields.
func upgradeExplicitArtifacts(upgrade []artifactUpgrade) bool {
var changed bool
for _, artifact := range upgrade {
if artifact.new == "" || *artifact.existing == "" {
continue
}
if *artifact.existing == artifact.new {
continue
}
*artifact.existing = artifact.new
changed = true
}
return changed
}

// UpgradePossible returns true iff any explicitly set artifacts differ from latest.
// Empty fields are ignored (they use defaults from code, so already latest).
func (ac *ArtifactsConfig) UpgradePossible(latest *ArtifactsConfig) bool {
Expand Down Expand Up @@ -636,6 +689,21 @@ func (ac *ArtifactsConfig) UpgradeTo(latest *ArtifactsConfig) bool {
return changed
}

// UpgradeExplicitTo upgrades only explicitly configured artifacts to the latest version.
//
// Returns true iff any artifacts have been updated.
func (ac *ArtifactsConfig) UpgradeExplicitTo(latest *ArtifactsConfig) bool {
var changed bool
changed = upgradeExplicitArtifacts([]artifactUpgrade{
{&ac.Builder, latest.Builder},
{&ac.Firmware, latest.Firmware},
{&ac.Kernel, latest.Kernel},
{&ac.Stage2, latest.Stage2},
})
changed = ac.Container.UpgradeExplicitTo(&latest.Container) || changed
return changed
}

// ContainerArtifactsConfig is the container artifacts configuration.
type ContainerArtifactsConfig struct {
// Runtime is the URI/path to the container runtime artifact (empty to use default).
Expand All @@ -644,6 +712,19 @@ type ContainerArtifactsConfig struct {
Compose string `yaml:"compose,omitempty" json:"compose,omitempty"`
}

// Merge overlays non-empty container artifact fields from another container artifact configuration.
func (cc *ContainerArtifactsConfig) Merge(other *ContainerArtifactsConfig) {
if other == nil {
return
}
if other.Runtime != "" {
cc.Runtime = other.Runtime
}
if other.Compose != "" {
cc.Compose = other.Compose
}
}

// UpgradeTo upgrades the artifacts to the latest version by updating any relevant fields.
//
// Returns true iff any artifacts have been updated.
Expand All @@ -653,3 +734,13 @@ func (cc *ContainerArtifactsConfig) UpgradeTo(latest *ContainerArtifactsConfig)
{&cc.Runtime, latest.Runtime},
})
}

// UpgradeExplicitTo upgrades only explicitly configured container artifacts to the latest version.
//
// Returns true iff any artifacts have been updated.
func (cc *ContainerArtifactsConfig) UpgradeExplicitTo(latest *ContainerArtifactsConfig) bool {
return upgradeExplicitArtifacts([]artifactUpgrade{
{&cc.Compose, latest.Compose},
{&cc.Runtime, latest.Runtime},
})
}
157 changes: 157 additions & 0 deletions build/rofl/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,133 @@ func TestManifestSerialization(t *testing.T) {
require.NoError(err, "dec.Validate")
}

func TestDeploymentArtifactsSerialization(t *testing.T) {
require := require.New(t)

const manifestYaml = `
name: my-container-app
version: 0.1.0
tee: tdx
kind: container
resources:
memory: 512
cpus: 1
storage:
kind: disk-persistent
size: 512
artifacts:
firmware: global-firmware
kernel: global-kernel
stage2: global-stage2
container:
runtime: global-runtime
compose: compose.yaml
deployments:
testnet:
network: testnet
paratime: sapphire
artifacts:
container:
compose: compose.testnet.yaml
`

var m Manifest
err := yaml.Unmarshal([]byte(manifestYaml), &m)
require.NoError(err, "yaml.Unmarshal")
require.NoError(m.Validate())
require.NotNil(m.Artifacts)
require.Equal("compose.yaml", m.Artifacts.Container.Compose)
require.NotNil(m.Deployments["testnet"].Artifacts)
require.Equal("compose.testnet.yaml", m.Deployments["testnet"].Artifacts.Container.Compose)

enc, err := yaml.Marshal(m)
require.NoError(err, "yaml.Marshal")

var dec Manifest
err = yaml.Unmarshal(enc, &dec)
require.NoError(err, "yaml.Unmarshal(round-trip)")
require.EqualValues(m, dec, "serialization should round-trip")
require.NoError(dec.Validate())
}

func TestResolveArtifacts(t *testing.T) {
require := require.New(t)

const (
defaultFirmware = "default-firmware"
defaultStage2 = "default-stage2"
defaultRuntime = "default-runtime"
globalKernel = "global-kernel"
globalCompose = "global-compose"
deploymentStage2 = "deployment-stage2"
deploymentCompose = "deployment-compose"
)

defaults := ArtifactsConfig{
Firmware: defaultFirmware,
Kernel: "default-kernel",
Stage2: defaultStage2,
Container: ContainerArtifactsConfig{
Runtime: defaultRuntime,
Compose: "default-compose",
},
}
m := Manifest{
Artifacts: &ArtifactsConfig{
Kernel: globalKernel,
Container: ContainerArtifactsConfig{
Compose: globalCompose,
},
},
Deployments: map[string]*Deployment{
"testnet": {
Network: "testnet",
ParaTime: "sapphire",
Artifacts: &ArtifactsConfig{
Stage2: deploymentStage2,
Container: ContainerArtifactsConfig{
Compose: deploymentCompose,
},
},
},
"mainnet": {
Network: "mainnet",
ParaTime: "sapphire",
},
},
}

require.Equal(ArtifactsConfig{
Firmware: defaultFirmware,
Kernel: globalKernel,
Stage2: deploymentStage2,
Container: ContainerArtifactsConfig{
Runtime: defaultRuntime,
Compose: deploymentCompose,
},
}, m.ResolveArtifacts("testnet", defaults))

require.Equal(ArtifactsConfig{
Firmware: defaultFirmware,
Kernel: globalKernel,
Stage2: defaultStage2,
Container: ContainerArtifactsConfig{
Runtime: defaultRuntime,
Compose: globalCompose,
},
}, m.ResolveArtifacts("mainnet", defaults))

require.Equal(ArtifactsConfig{
Firmware: defaultFirmware,
Kernel: globalKernel,
Stage2: defaultStage2,
Container: ContainerArtifactsConfig{
Runtime: defaultRuntime,
Compose: globalCompose,
},
}, m.ResolveArtifacts("missing", defaults))
}

func TestLoadManifest(t *testing.T) {
require := require.New(t)

Expand Down Expand Up @@ -264,6 +391,36 @@ func TestUpgradeArtifacts(t *testing.T) {
require.False(changed)
}

func TestUpgradeExplicitArtifacts(t *testing.T) {
require := require.New(t)

existing := ArtifactsConfig{
Kernel: "old-kernel",
Container: ContainerArtifactsConfig{
Compose: "compose.testnet.yaml",
},
}
latest := ArtifactsConfig{
Firmware: "latest-firmware",
Kernel: "latest-kernel",
Stage2: "latest-stage2",
Container: ContainerArtifactsConfig{
Runtime: "latest-runtime",
},
}

changed := existing.UpgradeExplicitTo(&latest)
require.True(changed)
require.Equal("", existing.Firmware)
require.Equal("latest-kernel", existing.Kernel)
require.Equal("", existing.Stage2)
require.Equal("", existing.Container.Runtime)
require.Equal("compose.testnet.yaml", existing.Container.Compose)

changed = existing.UpgradeExplicitTo(&latest)
require.False(changed)
}

func TestUpgradePossible(t *testing.T) {
require := require.New(t)

Expand Down
Loading
Loading