diff --git a/cmd/image/qcow2ova/prep/loop.go b/cmd/image/qcow2ova/prep/loop.go index cebe32d8..8d97f4ff 100644 --- a/cmd/image/qcow2ova/prep/loop.go +++ b/cmd/image/qcow2ova/prep/loop.go @@ -55,14 +55,19 @@ func partprobe(device string) error { return nil } -// get partition number of the image +// get partition number of the image - returns the largest partition (typically root) func getPartition(device string) (string, error) { - args := fmt.Sprintf("fdisk -l %s | grep ^/dev | wc -l", device) + // Use fdisk to get partition info and find the largest one + args := fmt.Sprintf("fdisk -l %s | grep '^%s' | awk '{print $1, $4}' | sort -k2 -n | tail -1 | awk '{print $1}' | sed 's/.*p//'", device, device) exitcode, out, err := utils.RunCMD("bash", "-c", args) if exitcode != 0 { return "", fmt.Errorf("failed to get partition for device: %s, exitcode: %d, stdout: %s, err: %s", device, exitcode, out, err) } - return strings.TrimSpace(out), nil + partition := strings.TrimSpace(out) + if partition == "" { + return "", fmt.Errorf("no partition found for device: %s", device) + } + return partition, nil } // growpart resizes the partition diff --git a/cmd/image/qcow2ova/prep/prepare.go b/cmd/image/qcow2ova/prep/prepare.go index c4a9475d..fac5f3ce 100644 --- a/cmd/image/qcow2ova/prep/prepare.go +++ b/cmd/image/qcow2ova/prep/prepare.go @@ -52,22 +52,27 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error { } partDev := lo + "p" + partition - - err = mount("nouuid", partDev, mnt) + err = growpart(lo, partition) if err != nil { - return err + klog.Warningf("growpart failed (non-fatal): %v", err) } - defer Umount(mnt) - err = growpart(lo, partition) + fsType, err := getFSType(partDev) if err != nil { return err } - fsType, err := getFSType(partDev) + mountOpts := "defaults" + if fsType == "xfs" { + mountOpts = "nouuid" + } + + err = mount(mountOpts, partDev, mnt) if err != nil { return err } + defer Umount(mnt) + switch fsType { case "xfs": @@ -105,7 +110,12 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error { } // Verify /boot is mounted properly and files are present. - bootDirFiles := []string{"config-*.ppc64le", "efi", "grub2", "initramfs-*.ppc64le.img", "loader", "symvers-*.ppc64le.*", "System.map-*.ppc64le", "vmlinuz-*.ppc64le"} + var bootDirFiles []string + if dist == "ubuntu" { + bootDirFiles = []string{"config-*-generic", "grub", "initrd.img-*-generic", "System.map-*-generic", "vmlinux-*-generic"} + } else { + bootDirFiles = []string{"config-*.ppc64le", "efi", "grub2", "initramfs-*.ppc64le.img", "loader", "symvers-*.ppc64le.*", "System.map-*.ppc64le", "vmlinuz-*.ppc64le"} + } for _, file := range bootDirFiles { exist, err := checkFileExists(filepath.Join(mnt, "boot", file)) if err != nil { @@ -176,7 +186,7 @@ func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) e //} //defer os.Chdir(cwd) switch dist := strings.ToLower(dist); dist { - case "rhel", "centos": + case "rhel", "centos", "ubuntu": return prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd) case "coreos": klog.Info("No image preparation required for the coreos.") diff --git a/cmd/image/qcow2ova/prep/templates.go b/cmd/image/qcow2ova/prep/templates.go index 4ae0860c..cf3c257d 100644 --- a/cmd/image/qcow2ova/prep/templates.go +++ b/cmd/image/qcow2ova/prep/templates.go @@ -28,29 +28,74 @@ set -o pipefail mv /etc/resolv.conf /etc/resolv.conf.orig || true echo "nameserver 9.9.9.9" | tee /etc/resolv.conf + {{if eq .Dist "rhel"}} subscription-manager register --force --auto-attach --username={{ .RHNUser }} --password={{ .RHNPassword }} -{{end}} -{{if .RootPasswd }} -echo {{ .RootPasswd }} | passwd root --stdin -{{end}} yum update -y && yum install -y yum-utils yum install -y cloud-init +{{end}} + +{{ if eq .Dist "ubuntu" }} +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get -y upgrade +apt-get -y install cloud-init multipath-tools +{{ end }} + +{{ if .RootPasswd }} +echo "DEBUG: Setting password for dist={{ .Dist }}" +{{ if or (eq .Dist "rhel") (eq .Dist "centos") }} +echo {{ .RootPasswd }} | passwd root --stdin +{{ else if eq .Dist "ubuntu" }} +echo "root:{{ .RootPasswd }}" | chpasswd +{{ else }} +echo "ERROR: Unknown distribution {{ .Dist }}" +exit 1 +{{ end }} +{{ end }} + +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} yum reinstall grub2-common -y rm -rf /etc/systemd/system/multi-user.target.wants/firewalld.service +{{end}} + +{{if eq .Dist "ubuntu"}} +apt install --reinstall grub-common +{{end}} + +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} rpm -vih --nodeps https://public.dhe.ibm.com/software/server/POWER/Linux/yum/download/ibm-power-repo-latest.noarch.rpm sed -i 's/^more \/opt\/ibm\/lop\/notice/#more \/opt\/ibm\/lop\/notice/g' /opt/ibm/lop/configure echo 'y' | /opt/ibm/lop/configure +{{end}} + +{{if eq .Dist "ubuntu"}} +# For Ubuntu, install IBM Power tools from Ubuntu repositories or skip if not available +if [ -f /opt/ibm/lop/configure ]; then + sed -i 's/^more \/opt\/ibm\/lop\/notice/#more \/opt\/ibm\/lop\/notice/g' /opt/ibm/lop/configure + echo 'y' | /opt/ibm/lop/configure +fi +{{end}} + {{if eq .Dist "rhel"}} # Disable the AT repository due to slowness in nature yum-config-manager --disable Advance_Toolchain {{end}} + {{if eq .Dist "centos"}} yum-config-manager --add-repo=https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/RHEL/$(rpm -E %{rhel})/ppc64le/ rpm --import https://public.dhe.ibm.com/software/server/POWER/Linux/yum/IBM/RHEL/$(rpm -E %{rhel})/ppc64le/repodata/repomd.xml.key {{end}} + +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} yum install powerpc-utils librtas DynamicRM devices.chrp.base.ServiceRM rsct.opt.storagerm rsct.core rsct.basic rsct.core src -y yum install -y device-mapper-multipath +{{end}} + +{{if eq .Dist "ubuntu"}} +apt install -y powerpc-utils +{{end}} + cat < /etc/multipath.conf defaults { user_friendly_names yes @@ -65,8 +110,10 @@ defaults { find_multipaths smart } EOF + sed -i 's/GRUB_TIMEOUT=.*$/GRUB_TIMEOUT=60/g' /etc/default/grub sed -i 's/GRUB_CMDLINE_LINUX=.*$/GRUB_CMDLINE_LINUX="console=tty0 console=hvc0,115200n8 biosdevname=0 crashkernel=auto rd.shell rd.debug rd.driver.pre=dm_multipath log_buf_len=1M "/g' /etc/default/grub +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} echo 'force_drivers+=" dm-multipath "' >/etc/dracut.conf.d/10-mp.conf dracut --regenerate-all --force for kernel in $(rpm -q kernel | sort -V | sed 's/kernel-//') @@ -74,18 +121,47 @@ do echo "Generating initramfs for kernel version: ${kernel}" dracut --kver ${kernel} --force --add multipath --include /etc/multipath /etc/multipath --include /etc/multipath.conf /etc/multipath.conf done +{{end}} + +{{if eq .Dist "ubuntu"}} +# Check if Ubuntu is using dracut or initramfs-tools +if command -v dracut >/dev/null 2>&1; then + # Ubuntu with dracut (24.04+) + echo 'force_drivers+=" dm-multipath "' >/etc/dracut.conf.d/10-mp.conf + dracut --regenerate-all --force + for kernel in $(ls /boot/vmlinuz-* | sed 's/\/boot\/vmlinuz-//'); do + echo "Generating initramfs for kernel version: ${kernel}" + dracut --kver ${kernel} --force --add multipath --include /etc/multipath /etc/multipath --include /etc/multipath.conf /etc/multipath.conf + done +else + # Ubuntu with initramfs-tools (older versions) + update-initramfs -u +fi +{{end}} + +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} grub2-mkconfig -o /boot/grub2/grub.cfg rm -rf /etc/sysconfig/network-scripts/ifcfg-eth0 +{{end}} + +{{if eq .Dist "ubuntu"}} +update-grub +{{end}} + {{if eq .Dist "rhel"}} subscription-manager unregister subscription-manager clean {{end}} +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} # Remove the ibm repositories used for the rsct installation rpm -e ibm-power-repo-*.noarch +{{end}} mv /etc/resolv.conf.orig /etc/resolv.conf || true +{{if or (eq .Dist "rhel") (eq .Dist "centos")}} touch /.autorelabel +{{end}} ` var CloudConfig = `# latest file from cloud-init-22.1-1.el8.noarch @@ -176,13 +252,13 @@ runcmd: system_info: default_user: - name: cloud-user + name: {{if eq .Dist "ubuntu"}}ubuntu{{else}}cloud-user{{end}} lock_passwd: true gecos: Cloud User groups: [adm, systemd-journal] sudo: ["ALL=(ALL) NOPASSWD:ALL"] shell: /bin/bash - distro: rhel + distro: {{.Dist}} paths: cloud_dir: /var/lib/cloud templates_dir: /etc/cloud/templates @@ -218,3 +294,16 @@ func Render(dist, rhnuser, rhnpasswd, rootpasswd string) (string, error) { } return wr.String(), nil } + +func RenderCloudConfig(dist string) (string, error) { + s := Setup{ + Dist: dist, + } + var wr bytes.Buffer + t := template.Must(template.New("cloud").Parse(CloudConfig)) + err := t.Execute(&wr, s) + if err != nil { + return "", fmt.Errorf("error while rendering the cloud config template: %v", err) + } + return wr.String(), nil +} diff --git a/cmd/image/qcow2ova/qcow2ova.go b/cmd/image/qcow2ova/qcow2ova.go index 76abf9cd..7e0b4548 100644 --- a/cmd/image/qcow2ova/qcow2ova.go +++ b/cmd/image/qcow2ova/qcow2ova.go @@ -113,8 +113,8 @@ Qcow2 images location: prep.CloudConfig = string(content) } - if !utils.Contains([]string{"rhel", "centos", "coreos"}, strings.ToLower(opt.ImageDist)) { - klog.Errorln("--image-dist is a mandatory flag and one of these [rhel, centos, coreos]") + if !utils.Contains([]string{"rhel", "centos", "coreos", "ubuntu"}, strings.ToLower(opt.ImageDist)) { + klog.Errorln("--image-dist is a mandatory flag and one of these [rhel, centos, coreos, ubuntu]") os.Exit(1) } diff --git a/cmd/image/sync/mock/sync_client_generated.go b/cmd/image/sync/mock/sync_client_generated.go index 92ea897f..4f45c269 100644 --- a/cmd/image/sync/mock/sync_client_generated.go +++ b/cmd/image/sync/mock/sync_client_generated.go @@ -14,11 +14,6 @@ // Code generated by MockGen. DO NOT EDIT. // Source: ./sync_client.go -// -// Generated by this command: -// -// mockgen -source=./sync_client.go -destination=./mock/sync_client_generated.go -package=mock -copyright_file=../../../hack/copyright_file -// // Package mock is a generated GoMock package. package mock @@ -26,7 +21,7 @@ package mock import ( reflect "reflect" - gomock "go.uber.org/mock/gomock" + gomock "github.com/golang/mock/gomock" ) // MockSyncClient is a mock of SyncClient interface. @@ -62,7 +57,7 @@ func (m *MockSyncClient) CheckBucketLocationConstraint(bucketName, bucketLocatio } // CheckBucketLocationConstraint indicates an expected call of CheckBucketLocationConstraint. -func (mr *MockSyncClientMockRecorder) CheckBucketLocationConstraint(bucketName, bucketLocationConstraint any) *gomock.Call { +func (mr *MockSyncClientMockRecorder) CheckBucketLocationConstraint(bucketName, bucketLocationConstraint interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckBucketLocationConstraint", reflect.TypeOf((*MockSyncClient)(nil).CheckBucketLocationConstraint), bucketName, bucketLocationConstraint) } @@ -76,7 +71,7 @@ func (m *MockSyncClient) CopyObjectToBucket(srcBucketName, destBucketName, objec } // CopyObjectToBucket indicates an expected call of CopyObjectToBucket. -func (mr *MockSyncClientMockRecorder) CopyObjectToBucket(srcBucketName, destBucketName, objectName any) *gomock.Call { +func (mr *MockSyncClientMockRecorder) CopyObjectToBucket(srcBucketName, destBucketName, objectName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CopyObjectToBucket", reflect.TypeOf((*MockSyncClient)(nil).CopyObjectToBucket), srcBucketName, destBucketName, objectName) } @@ -91,7 +86,7 @@ func (m *MockSyncClient) SelectObjects(bucketName, regex string) ([]string, erro } // SelectObjects indicates an expected call of SelectObjects. -func (mr *MockSyncClientMockRecorder) SelectObjects(bucketName, regex any) *gomock.Call { +func (mr *MockSyncClientMockRecorder) SelectObjects(bucketName, regex interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectObjects", reflect.TypeOf((*MockSyncClient)(nil).SelectObjects), bucketName, regex) } diff --git a/pkg/audit/audit.go b/pkg/audit/audit.go index ccdaaf68..4146b520 100644 --- a/pkg/audit/audit.go +++ b/pkg/audit/audit.go @@ -75,10 +75,15 @@ func (a *Audit) Log(name, op, value string) { func Delete(file string) { check_file, err := os.Stat(file) if err != nil { - klog.Errorf("cannot retrieve file stats, err: %v", err) + if os.IsNotExist(err) { + return + } + klog.V(2).Infof("cannot retrieve file stats, err: %v", err) return } if check_file.Size() == 0 { - os.Remove(file) + if err := os.Remove(file); err != nil { + klog.V(2).Infof("failed to remove empty audit file: %v", err) + } } }