Skip to content

Commit

Permalink
Implement remote machines reset (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
anmazzotti authored Aug 4, 2023
1 parent cf023ac commit ffb9c01
Show file tree
Hide file tree
Showing 28 changed files with 1,542 additions and 405 deletions.
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ build-manifests: $(KUSTOMIZE) generate
$(MAKE) build-crds
$(MAKE) build-rbac

ALL_VERIFY_CHECKS = manifests vendor
ALL_VERIFY_CHECKS = manifests vendor generate

.PHONY: verify
verify: $(addprefix verify-,$(ALL_VERIFY_CHECKS))
Expand All @@ -247,3 +247,10 @@ verify-vendor: vendor
git diff; \
echo "generated files are out of date, run make generate"; exit 1; \
fi

.PHONY: verify-generate
verify-generate: generate
@if !(git diff --quiet HEAD); then \
git diff; \
echo "generated files are out of date, run make generate"; exit 1; \
fi
9 changes: 7 additions & 2 deletions api/v1beta1/machineinventory_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ import (
)

var (
MachineInventoryFinalizer = "machineinventory.elemental.cattle.io"
PlanSecretType corev1.SecretType = "elemental.cattle.io/plan"
MachineInventoryFinalizer = "machineinventory.elemental.cattle.io"
PlanSecretType corev1.SecretType = "elemental.cattle.io/plan"
PlanTypeAnnotation = "elemental.cattle.io/plan.type"
PlanTypeEmpty = "empty"
PlanTypeBootstrap = "bootstrap"
PlanTypeReset = "reset"
MachineInventoryResettableAnnotation = "elemental.cattle.io/resettable"
)

type MachineInventorySpec struct {
Expand Down
24 changes: 24 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ type Install struct {
ConfigDir string `json:"config-dir,omitempty" yaml:"config-dir,omitempty"`
}

type Reset struct {
// +optional
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" mapstructure:"enabled"`
// +optional
// +kubebuilder:default:=true
ResetPersistent bool `json:"reset-persistent,omitempty" yaml:"reset-persistent,omitempty" mapstructure:"reset-persistent"`
// +optional
// +kubebuilder:default:=true
ResetOEM bool `json:"reset-oem,omitempty" yaml:"reset-oem,omitempty" mapstructure:"reset-oem"`
// +optional
ConfigURLs []string `json:"config-urls,omitempty" yaml:"config-urls,omitempty" mapstructure:"config-urls"`
// +optional
SystemURI string `json:"system-uri,omitempty" yaml:"system-uri,omitempty" mapstructure:"system-uri"`
// +optional
Debug bool `json:"debug,omitempty" yaml:"debug,omitempty" mapstructure:"debug"`
// +optional
PowerOff bool `json:"poweroff,omitempty" yaml:"poweroff,omitempty" mapstructure:"poweroff"`
// +optional
// +kubebuilder:default:=true
Reboot bool `json:"reboot,omitempty" yaml:"reboot,omitempty" mapstructure:"reboot"`
}

type Registration struct {
// +optional
URL string `json:"url,omitempty" yaml:"url,omitempty" mapstructure:"url"`
Expand Down Expand Up @@ -80,6 +102,8 @@ type Elemental struct {
// +optional
Install Install `json:"install,omitempty" yaml:"install,omitempty"`
// +optional
Reset Reset `json:"reset,omitempty" yaml:"reset,omitempty"`
// +optional
Registration Registration `json:"registration,omitempty" yaml:"registration,omitempty"`
// +optional
SystemAgent SystemAgent `json:"system-agent,omitempty" yaml:"system-agent,omitempty"`
Expand Down
21 changes: 21 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

24 changes: 24 additions & 0 deletions charts/crds/templates/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,30 @@ spec:
url:
type: string
type: object
reset:
properties:
config-urls:
items:
type: string
type: array
debug:
type: boolean
enabled:
type: boolean
poweroff:
type: boolean
reboot:
default: true
type: boolean
reset-oem:
default: true
type: boolean
reset-persistent:
default: true
type: boolean
system-uri:
type: string
type: object
system-agent:
properties:
secret-name:
Expand Down
97 changes: 64 additions & 33 deletions cmd/register/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ import (

const (
defaultStatePath = "/oem/registration/state.yaml"
defaultLiveStatePath = "/tmp/registration/state.yaml"
defaultConfigPath = "/oem/registration/config.yaml"
defaultLiveConfigPath = "/run/initramfs/live/livecd-cloud-config.yaml"
registrationUpdateSuppressTimer = 24 * time.Hour
)

var (
cfg elementalv1.Config
debug bool
configPath string
statePath string
cfg elementalv1.Config
debug bool
reset bool
installation bool
configPath string
statePath string
)

var (
Expand All @@ -56,14 +59,18 @@ func main() {
fs := vfs.OSFS
installer := install.NewInstaller(fs)
stateHandler := register.NewFileStateHandler(fs)
client := register.NewClient(stateHandler)
client := register.NewClient()
cmd := newCommand(fs, client, stateHandler, installer)
if err := cmd.Execute(); err != nil {
log.Fatalf("FATAL: %s", err)
}
}

func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHandler, installer install.Installer) *cobra.Command {
// Reset config and viper cache
cfg = elementalv1.Config{}
viper.Reset()
// Define command (using closures)
cmd := &cobra.Command{
Use: "elemental-register",
Short: "Elemental register command",
Expand All @@ -78,13 +85,16 @@ func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHa
if err := initConfig(fs); err != nil {
return fmt.Errorf("initializing configuration: %w", err)
}
// Determine if registration should execute or skip a cycle
// Load Registration State
if err := stateHandler.Init(statePath); err != nil {
return fmt.Errorf("initializing state handler on path '%s': %w", statePath, err)
}
if skip, err := shouldSkipRegistration(stateHandler, installer); err != nil {
return fmt.Errorf("determining if registration should run: %w", err)
} else if skip {
registrationState, err := getRegistrationState(stateHandler, reset)
if err != nil {
return fmt.Errorf("getting registration state: %w", err)
}
// Determine if registration should execute or skip a cycle
if !installation && !reset && !registrationState.HasLastUpdateElapsed(registrationUpdateSuppressTimer) {
log.Info("Nothing to do")
return nil
}
Expand All @@ -93,25 +103,40 @@ func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHa
if err != nil {
return fmt.Errorf("validating CA: %w", err)
}
// Register
data, err := client.Register(cfg.Elemental.Registration, caCert)
// Register (and fetch the remote MachineRegistration)
data, err := client.Register(cfg.Elemental.Registration, caCert, &registrationState)
if err != nil {
return fmt.Errorf("registering machine: %w", err)
}
if err := stateHandler.Save(registrationState); err != nil {
return fmt.Errorf("saving registration state: %w", err)
}
// Validate remote config
log.Debugf("Fetched configuration from manager cluster:\n%s\n\n", string(data))
if err := yaml.Unmarshal(data, &cfg); err != nil {
return fmt.Errorf("parsing returned configuration: %w", err)
}
// Install
if !installer.IsSystemInstalled() {
log.Info("Installing Elemental")
return installer.InstallElemental(cfg)
if installation {
log.Info("Installing elemental")
if err := installer.InstallElemental(cfg, registrationState); err != nil {
return fmt.Errorf("installing elemental: %w", err)
}
return nil
}
// Reset
if reset {
log.Info("Resetting Elemental")
if err := installer.ResetElemental(cfg, registrationState); err != nil {
return fmt.Errorf("resetting elemental: %w", err)
}
return nil
}

return nil
},
}
//Define flags
//Define and bind flags
cmd.Flags().StringVar(&cfg.Elemental.Registration.URL, "registration-url", "", "Registration url to get the machine config from")
_ = viper.BindPFlag("elemental.registration.url", cmd.Flags().Lookup("registration-url"))
cmd.Flags().StringVar(&cfg.Elemental.Registration.CACert, "registration-ca-cert", "", "File with the custom CA certificate to use against he registration url")
Expand All @@ -125,23 +150,40 @@ func newCommand(fs vfs.FS, client register.Client, stateHandler register.StateHa
cmd.Flags().StringVar(&cfg.Elemental.Registration.Auth, "auth", "tpm", "Registration authentication method")
_ = viper.BindPFlag("elemental.registration.auth", cmd.Flags().Lookup("auth"))
cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
if installer.IsSystemInstalled() {
cmd.Flags().StringVar(&configPath, "config-path", defaultConfigPath, "The full path of the elemental-register config")
} else {
cmd.Flags().StringVar(&configPath, "config-path", defaultLiveConfigPath, "The full path of the elemental-register config")
}
cmd.Flags().StringVar(&configPath, "config-path", defaultConfigPath, "The full path of the elemental-register config")
cmd.Flags().StringVar(&statePath, "state-path", defaultStatePath, "The full path of the elemental-register config")
cmd.PersistentFlags().BoolP("version", "v", false, "print version and exit")
_ = viper.BindPFlag("version", cmd.PersistentFlags().Lookup("version"))
cmd.Flags().BoolVar(&reset, "reset", false, "Reset the machine to its original post-installation state")
cmd.Flags().BoolVar(&installation, "install", false, "Install a new machine")
return cmd
}

func getRegistrationState(stateHandler register.StateHandler, reset bool) (register.State, error) {
// If we are resetting, we create an empty state to perform an initial registration.
if reset {
return register.State{}, nil
}
registrationState, err := stateHandler.Load()
if err != nil {
return register.State{}, fmt.Errorf("loading registration state: %w", err)
}
return registrationState, nil
}

func initConfig(fs vfs.FS) error {
log.Infof("Register version %s, commit %s, commit date %s", version.Version, version.Commit, version.CommitDate)
if installation && reset {
return errors.New("--install and --reset flags are mutually exclusive")
}
if debug {
log.EnableDebugLogging()
}
log.Infof("Register version %s, commit %s, commit date %s", version.Version, version.Commit, version.CommitDate)

// If we are installing from a live environment, the default config path must be updated
if installation {
configPath = defaultLiveConfigPath
statePath = defaultLiveStatePath
}
// Use go-vfs afero compatibility layer (required by Viper)
afs := vfsafero.NewAferoFS(fs)
viper.SetFs(afs)
Expand All @@ -158,17 +200,6 @@ func initConfig(fs vfs.FS) error {
return nil
}

func shouldSkipRegistration(stateHandler register.StateHandler, installer install.Installer) (bool, error) {
if !installer.IsSystemInstalled() {
return false, nil
}
state, err := stateHandler.Load()
if err != nil {
return false, fmt.Errorf("loading registration state")
}
return !state.HasLastUpdateElapsed(registrationUpdateSuppressTimer), nil
}

func getRegistrationCA(fs vfs.FS, config elementalv1.Config) ([]byte, error) {
registration := config.Elemental.Registration

Expand Down
Loading

0 comments on commit ffb9c01

Please sign in to comment.