-
Notifications
You must be signed in to change notification settings - Fork 79
firstboot: initial support #1705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package blueprint | ||
|
|
||
| type FirstbootCustomization struct { | ||
| // Type of the firstboot customization. Supported values are: | ||
| // "custom", "satellite", and "aap". | ||
| Type string `json:"type,omitempty" toml:"type,omitempty"` | ||
|
|
||
| // Optional firstboot name. Must be unique within the blueprint and only | ||
| // alphanumeric characters with dashes and underscores are allowed. | ||
| Name string `json:"name,omitempty" toml:"name,omitempty"` | ||
|
|
||
| // Ignore errors when executing the firstboot script and continue with | ||
| // execution of the following firstboot scripts, if any. By default, | ||
| // firstboot scripts are executed in order and if one of them fails, the | ||
| // execution stops immediately. | ||
| IgnoreFailure bool `json:"ignore_failure,omitempty" toml:"ignore_failure,omitempty"` | ||
|
|
||
| // Optional CA certificate to enroll into the system before executing the | ||
| // firstboot script. | ||
| CACerts []string `json:"cacerts,omitempty" toml:"cacerts,omitempty"` | ||
|
|
||
| CustomFirstbootCustomization | ||
| SatelliteFirstbootCustomization | ||
| AAPFirstbootCustomization | ||
| } | ||
|
|
||
| type CustomFirstbootCustomization struct { | ||
| // Strings without shebang will be interpreted as shell scripts, otherwise | ||
| // the script will be executed using the shebang interpreter. Required if | ||
| // type is set to "custom". | ||
| Contents string `json:"contents,omitempty" toml:"contents,omitempty"` | ||
| } | ||
|
|
||
| type SatelliteFirstbootCustomization struct { | ||
| // Registration command as generated by the Satellite server. Required, if | ||
| // type is set to "satellite". | ||
| Command string `json:"command,omitempty" toml:"command,omitempty"` | ||
| } | ||
|
|
||
| type AAPFirstbootCustomization struct { | ||
| // Job template URL as generated by the AAP server. Required if type is set | ||
| // to "aap". Example URLs are | ||
| // https://aap.example.com/api/controller/v2/job_templates/9/callback/ or | ||
| // https://aap.example.com/api/v2/job_templates/9/callback/ depending on the | ||
| // AAP version. | ||
| JobTemplateURL string `json:"job_template_url,omitempty" toml:"job_template_url,omitempty"` | ||
|
|
||
| // The host config key. Required if type is set to "aap". | ||
| HostConfigKey string `json:"host_config_key,omitempty" toml:"host_config_key,omitempty"` | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package firstboot | ||
|
|
||
| import ( | ||
| "errors" | ||
| "net/url" | ||
|
|
||
| "github.com/osbuild/images/pkg/blueprint" | ||
| ) | ||
|
|
||
| type FirstbootOptions struct { | ||
| Custom []CustomFirstbootOptions | ||
| Satellite *SatelliteFirstbootOptions | ||
| AAP *AAPFirstbootOptions | ||
| } | ||
|
|
||
| type CustomFirstbootOptions struct { | ||
| Contents string | ||
| Name string | ||
| IgnoreFailure bool | ||
| } | ||
|
|
||
| type SatelliteFirstbootOptions struct { | ||
| Command string | ||
| CACerts []string | ||
| } | ||
|
|
||
| type AAPFirstbootOptions struct { | ||
| JobTemplateURL string | ||
| HostConfigKey string | ||
| CACerts []string | ||
| } | ||
|
|
||
| func FirstbootOptionsFromBP(bpFirstboot blueprint.FirstbootCustomization) (*FirstbootOptions, error) { | ||
| var custom []CustomFirstbootOptions | ||
| for _, c := range bpFirstboot.Custom { | ||
|
Check failure on line 35 in pkg/customizations/firstboot/firstboot.go
|
||
| if c.Contents == "" { | ||
| return nil, errors.New("custom firstboot script contents cannot be empty") | ||
| } | ||
|
|
||
| custom = append(custom, CustomFirstbootOptions{ | ||
| Contents: c.Contents, | ||
| Name: c.Name, | ||
| IgnoreFailure: c.IgnoreFailure, | ||
| }) | ||
| } | ||
|
|
||
| var satellite *SatelliteFirstbootOptions | ||
| if bpFirstboot.Satellite != nil { | ||
|
Check failure on line 48 in pkg/customizations/firstboot/firstboot.go
|
||
| if bpFirstboot.Satellite.Command == "" { | ||
|
Check failure on line 49 in pkg/customizations/firstboot/firstboot.go
|
||
| return nil, errors.New("satellite firstboot command cannot be empty") | ||
| } | ||
|
|
||
| var certs []string | ||
| for _, cert := range bpFirstboot.Satellite.CACerts { | ||
|
Check failure on line 54 in pkg/customizations/firstboot/firstboot.go
|
||
| certs = append(certs, cert) | ||
| } | ||
|
|
||
| satellite = &SatelliteFirstbootOptions{ | ||
| Command: bpFirstboot.Satellite.Command, | ||
|
Check failure on line 59 in pkg/customizations/firstboot/firstboot.go
|
||
| CACerts: certs, | ||
| } | ||
| } | ||
|
|
||
| var aap *AAPFirstbootOptions | ||
| if bpFirstboot.AAP != nil { | ||
|
Check failure on line 65 in pkg/customizations/firstboot/firstboot.go
|
||
| if bpFirstboot.AAP.JobTemplateURL == "" { | ||
|
Check failure on line 66 in pkg/customizations/firstboot/firstboot.go
|
||
| return nil, errors.New("AAP firstboot job template URL cannot be empty") | ||
| } | ||
|
|
||
| if _, err := url.ParseRequestURI(bpFirstboot.AAP.JobTemplateURL); err != nil { | ||
|
Check failure on line 70 in pkg/customizations/firstboot/firstboot.go
|
||
| return nil, errors.New("AAP firstboot job template URL is not a valid URI") | ||
| } | ||
|
|
||
| var certs []string | ||
| for _, cert := range bpFirstboot.AAP.CACerts { | ||
|
Check failure on line 75 in pkg/customizations/firstboot/firstboot.go
|
||
| certs = append(certs, cert) | ||
| } | ||
|
|
||
| aap = &AAPFirstbootOptions{ | ||
| JobTemplateURL: bpFirstboot.AAP.JobTemplateURL, | ||
|
Check failure on line 80 in pkg/customizations/firstboot/firstboot.go
|
||
| HostConfigKey: bpFirstboot.AAP.HostConfigKey, | ||
| CACerts: certs, | ||
| } | ||
| } | ||
|
|
||
| return &FirstbootOptions{ | ||
| Custom: custom, | ||
| Satellite: satellite, | ||
| AAP: aap, | ||
| }, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| package firstboot_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/osbuild/images/pkg/blueprint" | ||
| "github.com/osbuild/images/pkg/customizations/firstboot" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestFirstbootOptionsFromValidBP(t *testing.T) { | ||
| validBP := blueprint.FirstbootCustomization{ | ||
| Custom: []blueprint.CustomFirstbootCustomization{ | ||
| {Contents: "echo hello", Name: "greet"}, | ||
| }, | ||
| Satellite: &blueprint.SatelliteFirstbootCustomization{ | ||
| Command: "satellite-command", | ||
| CACerts: []string{"cert1", "cert2"}, | ||
| }, | ||
| AAP: &blueprint.AAPFirstbootCustomization{ | ||
| JobTemplateURL: "https://example.com/job-template", | ||
| HostConfigKey: "host-config-key", | ||
| CACerts: []string{"cert3"}, | ||
| }, | ||
| } | ||
|
|
||
| options, err := firstboot.FirstbootOptionsFromBP(validBP) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| assert.Len(t, options.Custom, 1) | ||
| assert.Equal(t, "echo hello", options.Custom[0].Contents) | ||
|
|
||
| assert.NotNil(t, options.Satellite) | ||
| assert.Equal(t, "satellite-command", options.Satellite.Command) | ||
| assert.ElementsMatch(t, []string{"cert1", "cert2"}, options.Satellite.CACerts) | ||
|
|
||
| assert.NotNil(t, options.AAP) | ||
| assert.Equal(t, "https://example.com/job-template", options.AAP.JobTemplateURL) | ||
| assert.Equal(t, "host-config-key", options.AAP.HostConfigKey) | ||
| assert.ElementsMatch(t, []string{"cert3"}, options.AAP.CACerts) | ||
| } | ||
|
|
||
| func TestFirstbootOptionsFromEmptyBP(t *testing.T) { | ||
| emptyBP := blueprint.FirstbootCustomization{} | ||
| options, err := firstboot.FirstbootOptionsFromBP(emptyBP) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| assert.Empty(t, options.Custom) | ||
| assert.Nil(t, options.Satellite) | ||
| assert.Nil(t, options.AAP) | ||
| } | ||
|
|
||
| func TestFirstbootOptionsFromBPWithEmptyCustomScript(t *testing.T) { | ||
| bpWithEmptyCustom := blueprint.FirstbootCustomization{ | ||
| Custom: []blueprint.CustomFirstbootCustomization{ | ||
| {Contents: "", Name: "empty-script"}, | ||
| }, | ||
| } | ||
|
|
||
| _, err := firstboot.FirstbootOptionsFromBP(bpWithEmptyCustom) | ||
| assert.Error(t, err, "expected error for empty custom script contents") | ||
| } | ||
|
|
||
| func TestFirstbootOptionsFromBPWithEmptySatelliteCommand(t *testing.T) { | ||
| bpWithEmptySatellite := blueprint.FirstbootCustomization{ | ||
| Satellite: &blueprint.SatelliteFirstbootCustomization{ | ||
| Command: "", | ||
| }, | ||
| } | ||
|
|
||
| _, err := firstboot.FirstbootOptionsFromBP(bpWithEmptySatellite) | ||
| assert.Error(t, err, "expected error for empty satellite command") | ||
| } | ||
|
|
||
| func TestFirstbootOptionsFromBPWithEmptyAAPJobTemplateURL(t *testing.T) { | ||
| bpWithEmptyAAP := blueprint.FirstbootCustomization{ | ||
| AAP: &blueprint.AAPFirstbootCustomization{ | ||
| JobTemplateURL: "", | ||
| }, | ||
| } | ||
|
|
||
| _, err := firstboot.FirstbootOptionsFromBP(bpWithEmptyAAP) | ||
| assert.Error(t, err, "expected error for empty AAP job template URL") | ||
| } | ||
|
|
||
| func TestFirstbootOptionsFromBPWithInvalidAAPJobTemplateURL(t *testing.T) { | ||
| bpWithInvalidAAP := blueprint.FirstbootCustomization{ | ||
| AAP: &blueprint.AAPFirstbootCustomization{ | ||
| JobTemplateURL: "not-a-valid-url", | ||
| }, | ||
| } | ||
|
|
||
| _, err := firstboot.FirstbootOptionsFromBP(bpWithInvalidAAP) | ||
| assert.Error(t, err, "expected error for invalid AAP job template URL") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think CRC also accepts a skip tls verification option too:
https://github.com/osbuild/image-builder-crc/blob/cbfac18e1a143fc761a396a4b2a0b3538dba2fb1/internal/v1/api.yaml#L1480-L1492
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking not having any certs would skip but being explicit is probably safer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya that's valid, just making a note of it :)