From cca0e773f69d3daf5311ed3bd9526093ee7ecd7e Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 17 Oct 2022 13:06:31 +0200 Subject: [PATCH] support ignition in edge simplified-installer and raw-image Signed-off-by: Antonio Murdaca Signed-off-by: Antonio Murdaca Signed-off-by: Irene Diez Co-authored-by: Irene Diez Signed-off-by: Antonio Murdaca --- internal/distro/rhel9/edge.go | 4 + internal/distro/rhel9/images.go | 5 + internal/image/ostree_raw.go | 4 +- internal/manifest/commit_deployment.go | 18 +++ internal/osbuild/grub2_stage.go | 1 + internal/osbuild/ignition_stage.go | 12 ++ test/cases/ostree-simplified-installer.sh | 159 +++++++++++++++++++--- test/data/ansible/check_ostree.yaml | 24 +++- 8 files changed, 209 insertions(+), 18 deletions(-) diff --git a/internal/distro/rhel9/edge.go b/internal/distro/rhel9/edge.go index b63a258b6..f604019fc 100644 --- a/internal/distro/rhel9/edge.go +++ b/internal/distro/rhel9/edge.go @@ -382,6 +382,10 @@ func edgeCommitPackageSet(t *imageType) rpmmd.PackageSet { ps = ps.Append(aarch64EdgeCommitPackageSet(t)) } + if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || !common.VersionLessThan(t.arch.distro.osVersion, "9-stream") { + ps.Include = append(ps.Include, "ignition", "ignition-edge", "ssh-key-dir") + } + return ps } diff --git a/internal/distro/rhel9/images.go b/internal/distro/rhel9/images.go index 293a62ab9..855039e1d 100644 --- a/internal/distro/rhel9/images.go +++ b/internal/distro/rhel9/images.go @@ -5,6 +5,7 @@ import ( "math/rand" "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/container" "github.com/osbuild/osbuild-composer/internal/distro" "github.com/osbuild/osbuild-composer/internal/fdo" @@ -243,6 +244,9 @@ func edgeContainerImage(workload workload.Workload, img.Platform = t.platform img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations) + if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || !common.VersionLessThan(t.arch.distro.osVersion, "9-stream") { + img.OSCustomizations.EnabledServices = append(img.OSCustomizations.EnabledServices, "ignition-firstboot-complete.service", "coreos-ignition-write-issues", "coreos-ignition-write-issues") + } img.ContainerLanguage = img.OSCustomizations.Language img.Environment = t.environment img.Workload = workload @@ -375,6 +379,7 @@ func edgeSimplifiedInstallerImage(workload workload.Workload, Checksum: options.OSTree.FetchChecksum, } rawImg := image.NewOSTreeRawImage(commit) + rawImg.Ignition = true rawImg.Users = users.UsersFromBP(customizations.GetUsers()) rawImg.Groups = users.GroupsFromBP(customizations.GetGroups()) diff --git a/internal/image/ostree_raw.go b/internal/image/ostree_raw.go index 67336d792..84a3bd49e 100644 --- a/internal/image/ostree_raw.go +++ b/internal/image/ostree_raw.go @@ -36,6 +36,8 @@ type OSTreeRawImage struct { Locale string Filename string + + Ignition bool } func NewOSTreeRawImage(commit ostree.CommitSpec) *OSTreeRawImage { @@ -46,7 +48,7 @@ func NewOSTreeRawImage(commit ostree.CommitSpec) *OSTreeRawImage { } func ostreeCompressedImagePipelines(img *OSTreeRawImage, m *manifest.Manifest, buildPipeline *manifest.Build) *manifest.XZ { - osPipeline := manifest.NewOSTreeDeployment(m, buildPipeline, img.Commit, img.OSName, img.Platform) + osPipeline := manifest.NewOSTreeDeployment(m, buildPipeline, img.Commit, img.OSName, img.Ignition, img.Platform) osPipeline.PartitionTable = img.PartitionTable osPipeline.Remote = img.Remote osPipeline.KernelOptionsAppend = img.KernelOptionsAppend diff --git a/internal/manifest/commit_deployment.go b/internal/manifest/commit_deployment.go index 494c8c5cb..a247fd12b 100644 --- a/internal/manifest/commit_deployment.go +++ b/internal/manifest/commit_deployment.go @@ -37,6 +37,9 @@ type OSTreeDeployment struct { platform platform.Platform PartitionTable *disk.PartitionTable + + // Whether ignition is in use or not + ignition bool } // NewOSTreeDeployment creates a pipeline for an ostree deployment from a @@ -45,6 +48,7 @@ func NewOSTreeDeployment(m *Manifest, buildPipeline *Build, commit ostree.CommitSpec, osName string, + ignition bool, platform platform.Platform) *OSTreeDeployment { p := &OSTreeDeployment{ @@ -52,6 +56,7 @@ func NewOSTreeDeployment(m *Manifest, commit: commit, osName: osName, platform: platform, + ignition: ignition, } buildPipeline.addDependent(p) m.addPipeline(p) @@ -95,6 +100,14 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline { kernelOpts := osbuild.GenImageKernelOptions(p.PartitionTable) kernelOpts = append(kernelOpts, p.KernelOptionsAppend...) + if p.ignition { + kernelOpts = append(kernelOpts, + "coreos.no_persist_ip", // users cannot add connections as we don't have a live iso, this prevents connections to bleed into the system from the ign initrd + "ignition.platform.id=metal", + "$ignition_firstboot", + ) + } + pipeline.AddStage(osbuild.NewOSTreeDeployStage( &osbuild.OSTreeDeployStageOptions{ OsName: p.osName, @@ -170,6 +183,10 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline { pipeline.AddStage(grpStage) } + if p.ignition { + pipeline.AddStage(osbuild.NewIgnitionStage(&osbuild.IgnitionStageOptions{})) + } + // if no root password is set, lock the root account hasRoot := false for _, user := range p.Users { @@ -216,6 +233,7 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline { p.platform.GetBIOSPlatform(), p.platform.GetUEFIVendor(), true) grubOptions.Greenboot = true + grubOptions.Ignition = p.ignition grubOptions.Config = &osbuild.GRUB2Config{ Default: "saved", Timeout: 1, diff --git a/internal/osbuild/grub2_stage.go b/internal/osbuild/grub2_stage.go index 2712b09bb..8e233b271 100644 --- a/internal/osbuild/grub2_stage.go +++ b/internal/osbuild/grub2_stage.go @@ -26,6 +26,7 @@ type GRUB2StageOptions struct { Greenboot bool `json:"greenboot,omitempty"` WriteCmdLine *bool `json:"write_cmdline,omitempty"` Config *GRUB2Config `json:"config,omitempty"` + Ignition bool `json:"ignition,omitempty"` } type GRUB2UEFI struct { diff --git a/internal/osbuild/ignition_stage.go b/internal/osbuild/ignition_stage.go index ae07747f9..32af0b678 100644 --- a/internal/osbuild/ignition_stage.go +++ b/internal/osbuild/ignition_stage.go @@ -5,6 +5,18 @@ import ( "fmt" ) +type IgnitionStageOptions struct { +} + +func (IgnitionStageOptions) isStageOptions() {} + +func NewIgnitionStage(options *IgnitionStageOptions) *Stage { + return &Stage{ + Type: "org.osbuild.ignition", + Options: options, + } +} + type IgnitionStageInputInline struct { InlineFile IgnitionStageInput `json:"inlinefile"` } diff --git a/test/cases/ostree-simplified-installer.sh b/test/cases/ostree-simplified-installer.sh index e522f3fdf..64eb3d771 100755 --- a/test/cases/ostree-simplified-installer.sh +++ b/test/cases/ostree-simplified-installer.sh @@ -38,6 +38,7 @@ sudo tee /tmp/integration.xml > /dev/null << EOF + @@ -72,6 +73,7 @@ IMAGE_KEY="edge-${TEST_UUID}" HTTP_GUEST_ADDRESS=192.168.100.50 PUB_KEY_GUEST_ADDRESS=192.168.100.51 ROOT_CERT_GUEST_ADDRESS=192.168.100.52 +IGNITION_GUEST_ADDRESS=192.168.100.53 PROD_REPO_URL=http://192.168.100.1/repo PROD_REPO=/var/www/html/repo STAGE_REPO_ADDRESS=192.168.200.1 @@ -643,7 +645,7 @@ ansible_become_pass=${EDGE_USER_PASSWORD} EOF # Test IoT/Edge OS -sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e edge_type=edge-simplified-installer -e fdo_credential="true" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e skip_rollback_test="true" -e edge_type=edge-simplified-installer -e fdo_credential="true" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 check_result # Clean up BIOS VM @@ -765,7 +767,7 @@ ansible_become_pass=${EDGE_USER_PASSWORD} EOF # Test IoT/Edge OS -sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e edge_type=edge-simplified-installer -e fdo_credential="true" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e skip_rollback_test="true" -e edge_type=edge-simplified-installer -e fdo_credential="true" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 check_result greenprint "๐Ÿงน Clean up VM" @@ -777,13 +779,90 @@ sudo virsh vol-delete --pool images "$LIBVIRT_IMAGE_PATH" ################################################################## ## -## Build edge-simplified-installer without FDO +## Build edge-simplified-installer without FDO & with Ignition ## ################################################################## +# TODO(runcom): change this to butane to check that too +# also, write the very same test for ostree-raw-image +IGN_PATH="${HTTPD_PATH}/ignition" +sudo mkdir -p ${IGN_PATH} +IGN_CONFIG_PATH="${IGN_PATH}/config.ign" +sudo tee "$IGN_CONFIG_PATH" > /dev/null << EOF +{ + "ignition": { + "config": { + "merge": [ + { + "source": "http://192.168.100.1/ignition/sample.ign" + } + ] + }, + "timeouts": { + "httpTotal": 30 + }, + "version": "3.3.0" + }, + "passwd": { + "users": [ + { + "groups": [ + "wheel" + ], + "name": "core", + "passwordHash": "\$6\$GRmb7S0p8vsYmXzH\$o0E020S.9JQGaHkszoog4ha4AQVs3sk8q0DvLjSMxoxHBKnB2FBXGQ/OkwZQfW/76ktHd0NX5nls2LPxPuUdl.", + "sshAuthorizedKeys": [ + "${SSH_KEY_PUB}" + ] + } + ] + } +} +EOF + +IGN_CONFIG_SAMPLE_PATH="${IGN_PATH}/sample.ign" +sudo tee "$IGN_CONFIG_SAMPLE_PATH" > /dev/null << EOF +{ + "ignition": { + "version": "3.3.0" + }, + "storage": { + "files": [ + { + "path": "/usr/local/bin/startup.sh", + "contents": { + "compression": "", + "source": "data:;base64,IyEvYmluL2Jhc2gKZWNobyAiSGVsbG8sIFdvcmxkISIK" + }, + "mode": 493 + } + ] + }, + "systemd": { + "units": [ + { + "contents": "[Unit]\nDescription=A hello world unit!\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/local/bin/startup.sh\n[Install]\nWantedBy=multi-user.target\n", + "enabled": true, + "name": "hello.service" + }, + { + "dropins": [ + { + "contents": "[Service]\nEnvironment=LOG_LEVEL=trace\n", + "name": "log_trace.conf" + } + ], + "name": "fdo-client-linuxapp.service" + } + ] + } +} +EOF +sudo chmod -R +r ${HTTPD_PATH}/ignition/* + tee "$BLUEPRINT_FILE" > /dev/null << EOF name = "simplified_iso_without_fdo" -description = "A rhel-edge simplified-installer image without FDO" +description = "A rhel-edge simplified-installer image without FDO with Ignition" version = "0.0.1" modules = [] groups = [] @@ -791,6 +870,8 @@ groups = [] [customizations] installation_device = "/dev/vda" +[customizations.kernel] +append = "ignition.config.url=http://192.168.100.1/ignition/config.ign" EOF greenprint "๐Ÿ“„ simplified_iso_without_fdo blueprint" @@ -828,7 +909,7 @@ sudo virt-install --name="${IMAGE_KEY}-simplified_iso_without_fdo"\ --disk path="${LIBVIRT_IMAGE_PATH}",format=qcow2 \ --ram "${MEMORY}" \ --vcpus 2 \ - --network network=integration,mac=34:49:22:B0:83:32 \ + --network network=integration,mac=34:49:22:B0:83:33 \ --os-type linux \ --os-variant ${OS_VARIANT} \ --cdrom "/var/lib/libvirt/images/${ISO_FILENAME}" \ @@ -852,7 +933,7 @@ sudo virsh start "${IMAGE_KEY}-simplified_iso_without_fdo" # Check for ssh ready to go. greenprint "๐Ÿ›ƒ Checking for SSH is ready to go" for LOOP_COUNTER in $(seq 0 30); do - RESULTS="$(wait_for_ssh_up $ROOT_CERT_GUEST_ADDRESS)" + RESULTS="$(wait_for_ssh_up $IGNITION_GUEST_ADDRESS)" if [[ $RESULTS == 1 ]]; then echo "SSH is ready now! ๐Ÿฅณ" break @@ -869,20 +950,41 @@ INSTALL_HASH=$(curl "${PROD_REPO_URL}/refs/heads/${OSTREE_REF}") # Add instance IP address into /etc/ansible/hosts sudo tee "${TEMPDIR}"/inventory > /dev/null << EOF [ostree_guest] -${ROOT_CERT_GUEST_ADDRESS} +${IGNITION_GUEST_ADDRESS} + +[ostree_guest:vars] +ansible_python_interpreter=/usr/bin/python3 +ansible_user=core +ansible_private_key_file=${SSH_KEY} +ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" +ansible_become=yes +ansible_become_method=sudo +ansible_become_pass=${EDGE_USER_PASSWORD} +EOF + +# Test IoT/Edge OS +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e skip_rollback_test="true" -e ignition="true" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +check_result + +# now try with blueprint user + +# Add instance IP address into /etc/ansible/hosts +sudo tee "${TEMPDIR}"/inventory > /dev/null << EOF +[ostree_guest] +${IGNITION_GUEST_ADDRESS} [ostree_guest:vars] ansible_python_interpreter=/usr/bin/python3 ansible_user=admin ansible_private_key_file=${SSH_KEY} ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" -ansible_become=yes +ansible_become=yes ansible_become_method=sudo ansible_become_pass=${EDGE_USER_PASSWORD} EOF # Test IoT/Edge OS -sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${INSTALL_HASH}" -e skip_rollback_test="true" -e ignition="true" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 check_result ######################################################################## @@ -902,7 +1004,7 @@ version = "0.0.1" installation_device = "/dev/vda" [customizations.ignition.embedded] -url = "http://some-server/config.ig" +url = "http://192.168.100.1/ignition/config.ign" EOF greenprint "๐Ÿ“„ simplified_iso_with_ignition_embedded_url blueprint " @@ -934,6 +1036,8 @@ EOF check_result sudo umount /mnt/installer + + # TODO(runcom): run with this image and basically check the same as the previous test with core user else greenprint "Skipping ignition embedded url test, it's only for RHEL9" fi @@ -1033,8 +1137,8 @@ sudo composer-cli compose delete "${COMPOSE_ID}" > /dev/null sudo composer-cli blueprints delete upgrade > /dev/null greenprint "๐Ÿ—ณ Upgrade ostree image/commit" -sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" admin@${ROOT_CERT_GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |sudo -S rpm-ostree upgrade" -sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" admin@${ROOT_CERT_GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |nohup sudo -S systemctl reboot &>/dev/null & exit" +sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" admin@${IGNITION_GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |sudo -S rpm-ostree upgrade" +sudo ssh "${SSH_OPTIONS[@]}" -i "${SSH_KEY}" admin@${IGNITION_GUEST_ADDRESS} "echo ${EDGE_USER_PASSWORD} |nohup sudo -S systemctl reboot &>/dev/null & exit" # Sleep 10 seconds here to make sure vm restarted already sleep 10 @@ -1043,7 +1147,7 @@ sleep 10 greenprint "๐Ÿ›ƒ Checking for SSH is ready to go" # shellcheck disable=SC2034 # Unused variables left for readability for LOOP_COUNTER in $(seq 0 30); do - RESULTS="$(wait_for_ssh_up $ROOT_CERT_GUEST_ADDRESS)" + RESULTS="$(wait_for_ssh_up $IGNITION_GUEST_ADDRESS)" if [[ $RESULTS == 1 ]]; then echo "SSH is ready now! ๐Ÿฅณ" break @@ -1054,23 +1158,46 @@ done # Check ostree upgrade result check_result +# try with core user + # Add instance IP address into /etc/ansible/hosts sudo tee "${TEMPDIR}"/inventory > /dev/null << EOF [ostree_guest] -${ROOT_CERT_GUEST_ADDRESS} +${IGNITION_GUEST_ADDRESS} + +[ostree_guest:vars] +ansible_python_interpreter=/usr/bin/python3 +ansible_user=core +ansible_private_key_file=${SSH_KEY} +ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" +ansible_become=yes +ansible_become_method=sudo +ansible_become_pass=${EDGE_USER_PASSWORD} +EOF + +# Test IoT/Edge OS +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${UPGRADE_HASH}" -e skip_rollback_test="true" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +check_result + +# now try with blueprint user + +# Add instance IP address into /etc/ansible/hosts +sudo tee "${TEMPDIR}"/inventory > /dev/null << EOF +[ostree_guest] +${IGNITION_GUEST_ADDRESS} [ostree_guest:vars] ansible_python_interpreter=/usr/bin/python3 ansible_user=admin ansible_private_key_file=${SSH_KEY} ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" -ansible_become=yes +ansible_become=yes ansible_become_method=sudo ansible_become_pass=${EDGE_USER_PASSWORD} EOF # Test IoT/Edge OS -sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${UPGRADE_HASH}" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 +sudo ansible-playbook -v -i "${TEMPDIR}"/inventory -e image_type=redhat -e ostree_commit="${UPGRADE_HASH}" -e skip_rollback_test="true" -e edge_type=edge-simplified-installer -e fdo_credential="false" /usr/share/tests/osbuild-composer/ansible/check_ostree.yaml || RESULTS=0 check_result # Final success clean up diff --git a/test/data/ansible/check_ostree.yaml b/test/data/ansible/check_ostree.yaml index cba311188..463d8fb49 100644 --- a/test/data/ansible/check_ostree.yaml +++ b/test/data/ansible/check_ostree.yaml @@ -10,6 +10,7 @@ total_counter: "0" failed_counter: "0" firewall_feature: "false" + ignition: "false" tasks: # current target host's IP address @@ -48,6 +49,27 @@ - set_fact: checking_stage: "{{ result_stage.stdout }}" + - name: check Ignition has run and the config was provided correctly + block: + - name: check user provided config + shell: cat /etc/.ignition-result.json | jq '.userConfigProvided' + register: user_provided_config + - assert: + that: + - user_provided_config.stdout == "true" + fail_msg: "no ign user provided config" + success_msg: "ignition has run with user provided config" + always: + - set_fact: + total_counter: "{{ total_counter | int + 1 }}" + rescue: + # TODO: gather Ignition logs + - name: failed count + 1 + set_fact: + failed_counter: "{{ failed_counter | int + 1 }}" + when: ignition == "true" and ((ansible_facts['distribution'] == 'RedHat' and ansible_facts['distribution_version'] is version('9.2', '>=')) or + (ansible_facts['distribution'] == 'CentOS' and (ansible_facts['distribution_version'] == '9'))) + - name: wait for FDO onboarding block: - wait_for: @@ -790,7 +812,7 @@ - name: failed count + 1 set_fact: failed_counter: "{{ failed_counter | int + 1 }}" - when: fdo_credential == "true" + when: fdo_credential == "true" and skip_rollback_test == "false" - name: check fdo-client-linuxapp logs command: journalctl -u fdo-client-linuxapp