Skip to content

Commit

Permalink
Use after-install|reset-chroot stage to initialize system
Browse files Browse the repository at this point in the history
Signed-off-by: Andrea Mazzotti <andrea.mazzotti@suse.com>
  • Loading branch information
anmazzotti committed Aug 15, 2024
1 parent d857bda commit ff69404
Show file tree
Hide file tree
Showing 3 changed files with 447 additions and 431 deletions.
2 changes: 0 additions & 2 deletions pkg/elementalcli/elementalcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ func (r *runner) Reset(conf elementalv1.Reset) error {
func mapToInstallEnv(conf elementalv1.Install) []string {
var variables []string
// See GetInstallKeyEnvMap() in https://github.com/rancher/elemental-toolkit/blob/main/pkg/constants/constants.go
variables = append(variables, formatEV("ELEMENTAL_INSTALL_CLOUD_INIT", strings.Join(conf.ConfigURLs[:], ",")))
variables = append(variables, formatEV("ELEMENTAL_INSTALL_TARGET", conf.Device))
variables = append(variables, formatEV("ELEMENTAL_INSTALL_SYSTEM", conf.SystemURI))
variables = append(variables, formatEV("ELEMENTAL_INSTALL_FIRMWARE", conf.Firmware))
Expand All @@ -106,7 +105,6 @@ func mapToInstallEnv(conf elementalv1.Install) []string {
func mapToResetEnv(conf elementalv1.Reset) []string {
var variables []string
// See GetResetKeyEnvMap() in https://github.com/rancher/elemental-toolkit/blob/main/pkg/constants/constants.go
variables = append(variables, formatEV("ELEMENTAL_RESET_CLOUD_INIT", strings.Join(conf.ConfigURLs[:], ",")))
variables = append(variables, formatEV("ELEMENTAL_RESET_SYSTEM", conf.SystemURI))
variables = append(variables, formatEV("ELEMENTAL_RESET_PERSISTENT", strconv.FormatBool(conf.ResetPersistent)))
variables = append(variables, formatEV("ELEMENTAL_RESET_OEM", strconv.FormatBool(conf.ResetOEM)))
Expand Down
212 changes: 115 additions & 97 deletions pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ const (
registrationState = "/oem/registration/state.yaml"
)

// Temporary cloud-init configuration files.
// These paths will be passed to the `elemental` cli as additional `config-urls`.
const (
tempRegistrationConf = "/tmp/elemental-registration-conf.yaml"
tempRegistrationState = "/tmp/elemental-registration-state.yaml"
tempCloudInit = "/tmp/elemental-cloud-init.yaml"
tempSystemAgent = "/tmp/elemental-system-agent.yaml"
tempNetworkConfig = "/tmp/elemental-network-config.yaml"
afterInstallStage = "after-install-chroot"
afterResetStage = "after-reset-chroot"
elementalChrootConfigPath = "/system/oem/elemental-chroot.yaml" // Used in after-{install|reset}-chroot stages
registrationConfigPath = "/oem/elemental-registration.yaml"
stateConfigPath = "/oem/elemental-state.yaml"
cloudInitConfigPath = "/oem/elemental-cloud-init.yaml"
systemAgentConfigPath = "/oem/elemental-system-agent.yaml"
networkConfigPath = "/oem/elemental-network.yaml"
)

type Installer interface {
Expand Down Expand Up @@ -101,13 +102,10 @@ func (i *installer) InstallElemental(config elementalv1.Config, state register.S
log.Warningf("Both device and device-selector set, using device-field '%s'", config.Elemental.Install.Device)
}

additionalConfigs, err := i.getCloudInitConfigs(config, state, networkConfig)
if err != nil {
return fmt.Errorf("generating additional cloud configs: %w", err)
if err := i.writeChrootConfigurator(afterInstallStage, config, state, networkConfig); err != nil {
return fmt.Errorf("writing %s configurator: %w", afterInstallStage, err)
}

config.Elemental.Install.ConfigURLs = append(config.Elemental.Install.ConfigURLs, additionalConfigs...)

if err := i.runner.Install(config.Elemental.Install); err != nil {
return fmt.Errorf("failed to install elemental: %w", err)
}
Expand All @@ -121,11 +119,9 @@ func (i *installer) ResetElemental(config elementalv1.Config, state register.Sta
config.Elemental.Reset.ConfigURLs = []string{}
}

additionalConfigs, err := i.getCloudInitConfigs(config, state, networkConfig)
if err != nil {
return fmt.Errorf("generating additional cloud configs: %w", err)
if err := i.writeChrootConfigurator(afterResetStage, config, state, networkConfig); err != nil {
return fmt.Errorf("writing %s configurator: %w", afterResetStage, err)
}
config.Elemental.Reset.ConfigURLs = append(config.Elemental.Reset.ConfigURLs, additionalConfigs...)

if err := i.runner.Reset(config.Elemental.Reset); err != nil {
return fmt.Errorf("failed to reset elemental: %w", err)
Expand Down Expand Up @@ -239,66 +235,102 @@ func matchesGt(disk *block.Disk, req elementalv1.DeviceSelectorRequirement) (boo
return diskSize.Cmp(keySize) == 1, nil
}

// getCloudInitConfigs creates cloud-init configuration files that can be passed as additional `config-urls`
// to the `elemental` cli. We exploit this mechanism to persist information during `elemental install`
// or `elemental reset` calls into the newly installed or resetted system.
func (i *installer) getCloudInitConfigs(config elementalv1.Config, state register.State, networkConfig elementalv1.NetworkConfig) ([]string, error) {
configs := []string{}
agentConfPath, err := i.writeSystemAgentConfig(config.Elemental)
func (i *installer) writeChrootConfigurator(stage string, config elementalv1.Config, state register.State, networkConfig elementalv1.NetworkConfig) error {
chrootConfigurator := schema.YipConfig{}
chrootConfigurator.Name = "Elemental Finalize System"
chrootConfigurator.Stages = map[string][]schema.Stage{
stage: []schema.Stage{},

Check failure on line 242 in pkg/install/install.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofmt`-ed with `-s` (gofmt)
}

// Registration
registrationYipBytes, err := i.registrationConfigYip(config.Elemental.Registration)
if err != nil {
return nil, fmt.Errorf("writing system agent configuration: %w", err)
return fmt.Errorf("getting registration config yip: %w", err)
}
configs = append(configs, agentConfPath)
registrationYip := yipChrootWrap(string(registrationYipBytes), registrationConfigPath, "Registration Config")
chrootConfigurator.Stages[stage] = append(chrootConfigurator.Stages[stage], registrationYip)

if len(config.CloudConfig) > 0 {
cloudInitPath, err := i.writeCloudInit(config.CloudConfig)
if err != nil {
return nil, fmt.Errorf("writing custom cloud-init file: %w", err)
}
configs = append(configs, cloudInitPath)
// State
stateYipBytes, err := i.registrationStateYip(state)
if err != nil {
return fmt.Errorf("getting registration state config yip: %w", err)
}
stateYip := yipChrootWrap(string(stateYipBytes), stateConfigPath, "Registration State Config")
chrootConfigurator.Stages[stage] = append(chrootConfigurator.Stages[stage], stateYip)

registrationConfPath, err := i.writeRegistrationYAML(config.Elemental.Registration)
// Cloud Init
cloudInitYipBytes, err := i.cloudInitYip(config.CloudConfig)
if err != nil {
return nil, fmt.Errorf("writing registration conf plan: %w", err)
return fmt.Errorf("getting cloud-init config yip: %w", err)
}
configs = append(configs, registrationConfPath)
cloudInitYip := yipChrootWrap(string(cloudInitYipBytes), cloudInitConfigPath, "Cloud Init Config")
chrootConfigurator.Stages[stage] = append(chrootConfigurator.Stages[stage], cloudInitYip)

registrationStatePath, err := i.writeRegistrationState(state)
// Elemental System Agent
systemAgentYipBytes, err := i.elementalSystemAgentYip(config.Elemental)
if err != nil {
return nil, fmt.Errorf("writing registration state plan: %w", err)
return fmt.Errorf("getting elemental system agent config yip: %w", err)
}
systemAgentYip := yipChrootWrap(string(systemAgentYipBytes), systemAgentConfigPath, "Elemental System Agent Config")
chrootConfigurator.Stages[stage] = append(chrootConfigurator.Stages[stage], systemAgentYip)

// Network Config
networkConfigYipBytes, err := i.networkConfigYip(networkConfig)
if err != nil && !errors.Is(err, network.ErrEmptyConfig) {
return fmt.Errorf("getting network config yip: %w", err)
}
if !errors.Is(err, network.ErrEmptyConfig) {
networkConfigYip := yipChrootWrap(string(networkConfigYipBytes), networkConfigPath, "Network Config")
chrootConfigurator.Stages[stage] = append(chrootConfigurator.Stages[stage], networkConfigYip)
}
configs = append(configs, registrationStatePath)

networkConfigPath, err := i.writeNetworkConfig(networkConfig)
if errors.Is(err, network.ErrEmptyConfig) {
// Nothing to do on an empty network config.
return configs, nil
// Create dir if not exist
if err := vfs.MkdirAll(i.fs, filepath.Dir(elementalChrootConfigPath), 0700); err != nil {
return fmt.Errorf("creating directory '%s': %w", filepath.Dir(elementalChrootConfigPath), err)
}
// Create the chroot configurator yip
f, err := i.fs.Create(elementalChrootConfigPath)
if err != nil {
return nil, fmt.Errorf("writing temporary network config: %w", err)
return fmt.Errorf("creating file '%s': %w", elementalChrootConfigPath, err)
}
defer f.Close()

if err := yaml.NewEncoder(f).Encode(chrootConfigurator); err != nil {
return fmt.Errorf("writing encoded chroot configurator: %w", err)
}
configs = append(configs, networkConfigPath)

return configs, nil
return nil
}

func (i *installer) writeRegistrationYAML(reg elementalv1.Registration) (string, error) {
f, err := i.fs.Create(tempRegistrationConf)
if err != nil {
return "", fmt.Errorf("creating temporary registration conf plan file: %w", err)
func yipChrootWrap(content string, path string, name string) schema.Stage {
config := schema.Stage{Name: name}
config.Directories = []schema.Directory{
{
Path: filepath.Dir(path),
Permissions: 0700,
},
}
defer f.Close()
config.Files = []schema.File{
{
Path: path,
Content: content,
Permissions: 0600,
},
}
return config
}

func (i *installer) registrationConfigYip(reg elementalv1.Registration) ([]byte, error) {
registrationInBytes, err := yaml.Marshal(elementalv1.Config{
Elemental: elementalv1.Elemental{
Registration: reg,
},
})
if err != nil {
return "", fmt.Errorf("marshalling registration config: %w", err)
return nil, fmt.Errorf("marshalling registration config: %w", err)
}

if err := yaml.NewEncoder(f).Encode(schema.YipConfig{
yipConfig := schema.YipConfig{
Name: "Include registration config into installed system",
Stages: map[string][]schema.Stage{
"initramfs": {
Expand All @@ -319,24 +351,23 @@ func (i *installer) writeRegistrationYAML(reg elementalv1.Registration) (string,
},
},
},
}); err != nil {
return "", fmt.Errorf("writing encoded registration config config: %w", err)
}
return f.Name(), nil
}

func (i *installer) writeRegistrationState(state register.State) (string, error) {
f, err := i.fs.Create(tempRegistrationState)
yipConfigBytes, err := yaml.Marshal(yipConfig)
if err != nil {
return "", fmt.Errorf("creating temporary registration state plan file: %w", err)
return nil, fmt.Errorf("marshalling yip registration config: %w", err)
}
defer f.Close()

return yipConfigBytes, nil
}

func (i *installer) registrationStateYip(state register.State) ([]byte, error) {
stateBytes, err := yaml.Marshal(state)
if err != nil {
return "", fmt.Errorf("marshalling registration state: %w", err)
return nil, fmt.Errorf("marshalling registration state: %w", err)
}

if err := yaml.NewEncoder(f).Encode(schema.YipConfig{
yipConfig := schema.YipConfig{
Name: "Include registration state into installed system",
Stages: map[string][]schema.Stage{
"initramfs": {
Expand All @@ -357,47 +388,37 @@ func (i *installer) writeRegistrationState(state register.State) (string, error)
},
},
},
}); err != nil {
return "", fmt.Errorf("writing encoded registration state config: %w", err)
}
return f.Name(), nil
}

func (i *installer) writeCloudInit(cloudConfig map[string]runtime.RawExtension) (string, error) {
f, err := i.fs.Create(tempCloudInit)
yipConfigBytes, err := yaml.Marshal(yipConfig)
if err != nil {
return "", fmt.Errorf("creating temporary cloud init file: %w", err)
return nil, fmt.Errorf("marshalling yip state config: %w", err)
}
defer f.Close()

return yipConfigBytes, nil
}

func (i *installer) cloudInitYip(cloudConfig map[string]runtime.RawExtension) ([]byte, error) {
bytes, err := util.MarshalCloudConfig(cloudConfig)
if err != nil {
return "", fmt.Errorf("mashalling cloud config: %w", err)
return nil, fmt.Errorf("mashalling cloud config: %w", err)
}

log.Debugf("Decoded CloudConfig:\n%s\n", string(bytes))
if _, err = f.Write(bytes); err != nil {
return "", fmt.Errorf("writing cloud config: %w", err)
}
return f.Name(), nil
return bytes, nil
}

func (i *installer) writeNetworkConfig(networkConfig elementalv1.NetworkConfig) (string, error) {
func (i *installer) networkConfigYip(networkConfig elementalv1.NetworkConfig) ([]byte, error) {
networkYipConfig, err := i.networkConfigurator.GetNetworkConfigApplicator(networkConfig)
if err != nil {
return "", fmt.Errorf("getting network config applicator: %w", err)
return nil, fmt.Errorf("getting network config applicator: %w", err)
}

f, err := i.fs.Create(tempNetworkConfig)
yipConfigBytes, err := yaml.Marshal(networkYipConfig)
if err != nil {
return "", fmt.Errorf("creating temporary network-config file: %w", err)
return nil, fmt.Errorf("marshalling yip network config: %w", err)
}
defer f.Close()

if err := yaml.NewEncoder(f).Encode(networkYipConfig); err != nil {
return "", fmt.Errorf("writing encoded network-config: %w", err)
}
return f.Name(), nil
return yipConfigBytes, nil
}

func (i *installer) getConnectionInfoBytes(config elementalv1.Elemental) ([]byte, error) {
Expand Down Expand Up @@ -492,15 +513,14 @@ func (i *installer) WriteLocalSystemAgentConfig(config elementalv1.Elemental) er
return nil
}

// Write system agent cloud-init config to be consumed by install
func (i *installer) writeSystemAgentConfig(config elementalv1.Elemental) (string, error) {
func (i *installer) elementalSystemAgentYip(config elementalv1.Elemental) ([]byte, error) {
connectionInfoBytes, err := i.getConnectionInfoBytes(config)
if err != nil {
return "", fmt.Errorf("getting connection info: %w", err)
return nil, fmt.Errorf("getting connection info: %w", err)
}
agentConfigBytes, err := i.getAgentConfigBytes()
if err != nil {
return "", fmt.Errorf("getting agent config: %w", err)
return nil, fmt.Errorf("getting agent config: %w", err)
}

var stages []schema.Stage
Expand All @@ -520,21 +540,19 @@ func (i *installer) writeSystemAgentConfig(config elementalv1.Elemental) (string
},
})

f, err := i.fs.Create(tempSystemAgent)
if err != nil {
return "", fmt.Errorf("creating temporary elemental-system-agent file: %w", err)
}
defer f.Close()

if err := yaml.NewEncoder(f).Encode(schema.YipConfig{
yipConfig := schema.YipConfig{
Name: "Elemental System Agent Configuration",
Stages: map[string][]schema.Stage{
"initramfs": stages,
},
}); err != nil {
return "", fmt.Errorf("writing encoded system agent config: %w", err)
}
return f.Name(), nil

yipConfigBytes, err := yaml.Marshal(yipConfig)
if err != nil {
return nil, fmt.Errorf("marshalling yip system agent config: %w", err)
}

return yipConfigBytes, nil
}

func (i *installer) cleanupResetPlan() error {
Expand Down
Loading

0 comments on commit ff69404

Please sign in to comment.