Conversation
|
Thanks! Just a quick question to see if I understand how this will work: Will every string in the slice create a separate unit? So it looks like: bp.Firstboot = []string{
"#!/bin/sh\nset -e\necho do stuff",
`#/usr/bin/python\n if __name__ == "__main__":\n print("do other stuff")`,
}and that generates a "osbuild-firstboot1.service" and "osbuild-firstboot2.service"? |
|
Good question, initially I thought creating separate units and that is one way of doing that. But then we would need to create dependencies among them to ensure the order which is I think very important (first you want to register, then you want to do something else). So I am currently thinking about one unit + multiple scripts. Something like: [Unit]
ConditionFileIsExecutable=/usr/local/sbin/osbuild-custom-first-boot-1
ConditionFileIsExecutable=/usr/local/sbin/osbuild-custom-first-boot-2
ConditionFileIsExecutable=/usr/local/sbin/osbuild-custom-first-boot-3
ConditionPathExists=!/var/local/.osbuild-custom-first-boot-done
Wants=network-online.target
After=network-online.target
After=osbuild-first-boot.service
[Service]
Type=oneshot
ExecStart=-/usr/local/sbin/osbuild-custom-first-boot-1
ExecStart=-/usr/local/sbin/osbuild-custom-first-boot-2
ExecStart=-/usr/local/sbin/osbuild-custom-first-boot-3
ExecStartPost=/usr/bin/touch /var/local/.osbuild-custom-first-boot-done
RemainAfterExit=yes
[Install]
WantedBy=basic.targetThis (untested) unit should execute script 1, 2 and 3 in this order ignoring their return codes (meaning failure in script 1 will not break the execution). The question is if we want to actually have that behavior or we want to error out early. In that case removing the firstboot:
- name: "optional-name" # must be alphanum without spaces (used in filenames)
fail_early: true
contents: "#!/usr/bin/python\nprint 'Hello'\n"The advantage of this is that we can easily extend, string is a string and always a breaking change. What you all think? CC @achilleas-k |
23ea06f to
2982d08
Compare
|
I rebased, now it is a new type instead of string, this is much more extensible in the future: package blueprint
type FirstbootCustomization struct {
// Required firstboot script contents. Strings without shebang will be
// interpreted as shell scripts, otherwise the script will be executed
// using the shebang interpreter.
Contents string `json:"contents,omitempty" toml:"contents,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"`
} |
|
Having multiple units and adding |
|
So @achilleas-k made a good call that I should probably think about AAP/Satellite registration scripts too, since we have decided today we want to have them as full customizations. I amended an idea based on templates, this would mean we do not need to create a full customization for every new registration script. |
70cc7c5 to
4eaece6
Compare
|
Converting to draft, let me file an |
I am afraid I must take this idea back as I did what @thozza suggested I hope. Also @kingsleyzissou I am suggesting not to add explicit field for skip_tls for AAP as I think this could be done by simply not providing any TLS CA but I can add it if you prefer that. |
|
@achilleas-k how about this? |
|
So this follows the model we used for In the partitioning case, we landed on that design because it's a good way to describe the reality of the way the partition table is laid out, i.e. partitions are ordered and not homogeneous. So, first of all, a couple of questions for how we imagine the implementation:
EDIT/UPDATE: I know this flip-flopping and endless discussion can be frustrating and might be delaying other work, but we should really be giving a lot of thought to this because we'll be stuck with it for a while. I expect that however we end up structuring the new customization here, we'll be doing the same for the new blueprint schema as well. |
| } | ||
|
|
||
| // CommonCustomization contains common fields for all firstboot customizations. | ||
| type CommonCustomization struct { |
There was a problem hiding this comment.
This is under the blueprint namespace, so the name is misleading (blueprint.CommonCustomization). It should include the term firstboot in there somewhere.
pkg/blueprint/customizations.go
Outdated
| RHSM *RHSMCustomization `json:"rhsm,omitempty" toml:"rhsm,omitempty"` | ||
| CACerts *CACustomization `json:"cacerts,omitempty" toml:"cacerts,omitempty"` | ||
| ContainersStorage *ContainerStorageCustomization `json:"containers-storage,omitempty" toml:"containers-storage,omitempty"` | ||
| Firstboot []FirstbootCustomization `json:"firstboot,omitempty" toml:"firstboot,omitempty"` |
There was a problem hiding this comment.
Top-level arrays here have a tendency to get us into trouble when we realise we need to add some kind of global option for the customization group. For example, Disk could have originally been added as an array of PartitionCustomization (which is what we did back when we created Filesystem as an array of FilesystemCustomization), but by adding it as a DiskCustomization that includes an array of partitions, we were then able to add the global disk MinSize and later Type (for dos vs gpt), which we didn't have initially.
I can't think of a global option off the top of my head that we would want to add, but in my experience that's not a good indicator that we're not going to need it.
I'd say let's not overthink this since blueprint is going away soon. What is more relevant I think is how we solved thisin UBP and things are very different there since we decided the Go code is fully generated the options were limited. OpenAPI/JSON Schema does support union types (and more) so the approach is to use raw JSON string and a detection code that utilizes JSON parser to auto-detect the structure. This is used for structs which do not have any flag that defines the type, there are also cases where there is such field present, for example: In this case, everything is straightforward and there is no embedding. This is again limitation of the generator as it does not generate any embedded structures, at least user-defined ones. The way we share code is at the JSON Schema level by creating subschemas. These can either be anonymous or named, so either copies or Go types are generated.
Yes and yes. Let me elaborate below.
Yes. Ability to merge all types into one (ordered) list is important because I think there are use cases when you want to register Satellite first and only then execute something (let me do an ugly example of I understand that people often think that Satellite users only use Satellite for all the management, but that is almost never the case. I have seen Satellite provisioning that registers a host to a different Satellite, various extra customizations that detect an environment or often workarounds for the process itself. |
|
Rebased @achilleas-k concerns about root-level namespace. If you do not like the idea of having ordered list of various types, then we can just limit custom firstboot scripts to be called after AAP/Sat and see how users like it and possibly add some PreScripts array if they need to also execute stuff before. |
There was a problem hiding this comment.
Thanks! I can see a potential issue. As far as I know, with this, it would be possible to create a FirstbootScriptCustomization, that has a Type = "satellite", but can as well have fields of AAP or CustomFirstBoot? Or both at once?
I think that we can also do something like the following, but then I do understand the need to put them under the same type, which is FirstbootScriptCustomization.
type SatelliteFirstbootCustomization struct {
FirstbootCommonCustomization
SatelliteCACerts []string `json:"satellite_cacerts,omitempty" toml:"satellite_cacerts,omitempty"`
SatelliteCommand string `json:"satellite_command,omitempty" toml:"satellite_command,omitempty"`
}I am just a bit scared that we might eventually end up creating hybrids. Do we ever use interfaces? One other of my thoughts after reminding myself how Go works after such a long time was, that we can have an interface "FirstbooScriptCustomization" with a function .Type(). Something like:
type FirstbootScript interface {
Type() string
}
type FirstbootCustomization struct {
Scripts []FirstbootScript `json:"scripts,omitempty" toml:"scripts,omitempty"`
}
type FirstbootCommonCustomization struct {
Name string `json:"name,omitempty" toml:"name,omitempty"`
IgnoreFailure bool `json:"ignore_failure,omitempty" toml:"ignore_failure,omitempty"`
}
type CustomFirstboot struct {
FirstbootCommonCustomization
CustomContents string `json:"custom_contents,omitempty" toml:"custom_contents,omitempty"`
}
func (c CustomFirstboot) Type() string { return "custom" }But that kinda does not feel like the golang we are using, maybe just a food for thoughts.
|
Good point @avitova this brings an important topic now that For UBP we agreed that basic validation should be done there, this is a typical example of such validation that does not depend on any other input like image type or architecture. But now that you pointed out on the necessary validation that can be tricky, particularly with legacy package blueprint
// FirstbootCustomization contains fields specific to firstboot customizations.
// The execution order is:
// - satellite registration
// - aap registration
// - custom scripts (keeping the order in which they are entered)
type FirstbootCustomization struct {
// Registration command as generated by the Satellite server.
SatelliteCommand string `json:"satellite_command,omitempty" toml:"satellite_command,omitempty"`
// Job template URL as generated by the AAP server.
// 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.
AAPJobTemplateURL string `json:"aap_job_template_url,omitempty" toml:"aap_job_template_url,omitempty"`
// The host config key. Required if AAPJobTemplateURL is set.
AAPHostConfigKey string `json:"aap_host_config_key,omitempty" toml:"aap_host_config_key,omitempty"`
// Optional CA certificate(s) to enroll into the system during image build phase.
// The certificate will be available during firstboot for utilities like
// curl or wget for TLS verification.
CACerts []string `json:"cacerts,omitempty" toml:"cacerts,omitempty"`
// Custom firstboot scripts to be executed during the firstboot process. They are executed
// after registration.
Scripts []FirstbootScriptCustomization `json:"scripts,omitempty" toml:"scripts,omitempty"`
}
type FirstbootScriptCustomization struct {
// 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"`
// Content 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".
Content string `json:"content,omitempty" toml:"content,omitempty"`
}That is a ton cleaner, is it? |
There was a problem hiding this comment.
Thanks for all the work. LGTM.
A few notes below.
The strict unmarshalling I think should be done, perhaps in a follow-up. I also think if we have disagreements on the implementation (struct separation and embedding, unmarshalling methods, etc), we can revisit this later with a bit of experience in how easy or hard this structure is to work with. I think in terms of the user-facing format we all agree this is the best one though (it leaves the whole structure open to expansion at the top level and it orders multiple scripts of different types), so we can lock it in. The implementation can change without affecting the format.
I'd also like to expand the validation a bit more. For example, error cases like invalid or duplicate names aren't caught anywhere. Again though, let's leave this for a follow-up.
| // CustomFirstbootCustomization contains fields specific to custom firstboot | ||
| // customizations. All fields must be prefixed with "Custom". |
There was a problem hiding this comment.
What does All fields must be prefixed with "Custom". mean? Same for the other docstrings. Is it left over from a previous version?
| // SelectUnion returns the specific firstboot customization types. The detection | ||
| // is based on the "type" field in the JSON payload. Returns nil for irrelevant types, | ||
| // or if the type is unknown or if there is an error during unmarshaling. | ||
| func (sp FirstbootScriptCustomization) SelectUnion() (*CustomFirstbootCustomization, *SatelliteFirstbootCustomization, *AAPFirstbootCustomization, error) { | ||
| var fcc FirstbootCommonCustomization | ||
| err := json.Unmarshal(sp.union, &fcc) | ||
| if err != nil { | ||
| return nil, nil, nil, err | ||
| } | ||
|
|
||
| switch strings.ToLower(fcc.Type) { | ||
| case "custom": | ||
| var cc CustomFirstbootCustomization | ||
| err = json.Unmarshal(sp.union, &cc) | ||
| if err != nil { | ||
| return nil, nil, nil, err | ||
| } | ||
| return &cc, nil, nil, nil | ||
| case "satellite": | ||
| var sc SatelliteFirstbootCustomization | ||
| err = json.Unmarshal(sp.union, &sc) | ||
| if err != nil { | ||
| return nil, nil, nil, err | ||
| } | ||
| return nil, &sc, nil, err | ||
| case "aap": | ||
| var ac AAPFirstbootCustomization | ||
| err = json.Unmarshal(sp.union, &ac) | ||
| if err != nil { | ||
| return nil, nil, nil, err | ||
| } | ||
| return nil, nil, &ac, err | ||
| default: | ||
| return nil, nil, nil, ErrUnknownFirstbootCustomization | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
One thing this doesn't catch is cross-type fields being set. For example, if type == "custom" and job_template_url != "", that should be invalid. We can catch this automatically with strict unmarshalling (DisallowUnknownFields) and I think that would go a long way to avoiding silently ignoring invalid configs which can lead to confusion.
There was a problem hiding this comment.
This is where JSON/OpenAPI Schema is good at, UBP will do this for us. But sure, good point, adding DisallowUnknownFields.
pkg/blueprint/toml_json_bridge.go
Outdated
| return buf.Bytes(), nil | ||
| } | ||
|
|
||
| // tomlEq compares two TOML byte slices for equality by unmarshaling them into |
There was a problem hiding this comment.
By unmarshaling them into what?
| } | ||
| } | ||
|
|
||
| func TestSelectUnion(t *testing.T) { |
There was a problem hiding this comment.
A couple of error test cases would be good here too.
Sorry what strict unmarshalling? Edit: Oh I see it in the review comments. |
e8aa955 to
64e1032
Compare
|
All remarks fixed, one additional change: I noticed that some fields can be missing, so I added errors for that plus tests. |
avitova
left a comment
There was a problem hiding this comment.
Two comments, but I am not requesting changes because one is just a food for thought and the other is just enhancement of error messages.
Adds a new customization called firstboot. There are three types of firstboot entries: - custom - satellite registration - aap registration Required field is "type" which decides the type, optionally name can be used when generating filenames and failure of one firstboot script will stop chain of execution, unless a flag is set. The contents for the custom firstboot can be any executable script, such as shell script, Python, or any other script that can be executed by the system. If the firstboot string does not contain shebang, it is executed depending on the target OS configuration, typically as a shell script. Satellite requires the full registration command, as generated by Satellite UI or CLI. AAP requires hook URL and a key in order to generate the registration command. Both entries optionally take one or more CACerts with CA for the registration command. If no CA cert is provided for the AAP registration, then insecure HTTP connection will be used.
|
I think I am good here now. |
|
@achilleas-k I know this PR is a long read but in terms of code it is pretty trivial, thanks! |
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
tag v1.14.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.14.0 ---------------- - customization: add firstboot (osbuild/blueprint#23) - Author: Lukáš Zapletal, Reviewers: Achilleas Koutsou, Katarína Sieklová — Somewhere on the Internet, 2025-09-29 --- tag v1.15.0 Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com> Changes with 1.15.0 ---------------- - blueprint: new customization: dnf (osbuild/blueprint#34) - Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Michael Vogt, Tomáš Hozza — Somewhere on the Internet, 2025-09-29 ---
customization: add firstboot
Adds a new customization called firstboot. There are three types of
firstboot entries:
Required field is "type" which decides the type, optionally name can be
used when generating filenames and failure of one firstboot script will
stop chain of execution, unless a flag is set.
The contents for the custom firstboot can be any executable script, such
as shell script, Python, or any other script that can be executed by the
system. If the firstboot string does not contain shebang, it is executed
depending on the target OS configuration, typically as a shell script.
Satellite requires the full registration command, as generated by
Satellite UI or CLI. AAP requires hook URL and a key in order to
generate the registration command. Both entries optionally take one or
more CACerts with CA for the registration command. If no CA cert is
provided for the AAP registration, then HTTP address will be used.
I am starting my work on proper firstboot support. This is the first step.
Edit: Since
blueprintis now fully extracted fromimagesI need to get this one merged first.