diff --git a/cmd/mapt/cmd/azure/hosts/constants.go b/cmd/mapt/cmd/azure/hosts/constants.go index 30575e654..5adc4c407 100644 --- a/cmd/mapt/cmd/azure/hosts/constants.go +++ b/cmd/mapt/cmd/azure/hosts/constants.go @@ -9,6 +9,6 @@ const ( paramLinuxVersion = "version" paramLinuxVersionDesc = "linux version. Version should be formated as X.Y (Major.minor)" defaultUbuntuVersion = "24.04" - defaultRHELVersion = "9.4" + defaultRHELVersion = "9.7" defaultFedoraVersion = "42" ) diff --git a/pkg/integrations/integrations.go b/pkg/integrations/integrations.go index 4252f3318..9843f7d84 100644 --- a/pkg/integrations/integrations.go +++ b/pkg/integrations/integrations.go @@ -1,9 +1,7 @@ package integrations import ( - "fmt" - "strings" - + cloudinit "github.com/redhat-developer/mapt/pkg/util/cloud-init" "github.com/redhat-developer/mapt/pkg/util/file" ) @@ -49,11 +47,5 @@ func GetIntegrationSnippetAsCloudInitWritableFile(intCfg IntegrationConfig, user if err != nil || len(*snippet) == 0 { return snippet, err } - lines := strings.Split(strings.TrimSpace(*snippet), "\n") - for i, line := range lines { - // Added 6 spaces before each line - lines[i] = fmt.Sprintf(" %s", line) - } - identedSnippet := strings.Join(lines, "\n") - return &identedSnippet, nil + return cloudinit.IndentWriteFile(snippet) } diff --git a/pkg/provider/azure/action/rhel/expand-root-disk.sh b/pkg/provider/azure/action/rhel/expand-root-disk.sh new file mode 100644 index 000000000..f79f27443 --- /dev/null +++ b/pkg/provider/azure/action/rhel/expand-root-disk.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -eu + +# Detect root LV (e.g. /dev/mapper/rootvg-rootlv) +ROOT_LV=$(findmnt -n -o SOURCE /) + +# Get VG name +VG=$(lvs --noheadings -o vg_name "$ROOT_LV" | xargs) + +# Get PV device (e.g. /dev/sda4 or /dev/nvme0n1p4) +PV=$(pvs --noheadings -o pv_name --select vg_name="$VG" | xargs) + +# Extract base disk and partition number safely +DISK_PATH="$PV" + +# NVMe devices end with pN (nvme0n1p4) +case "$DISK_PATH" in + *nvme*) + DISK=$(echo "$DISK_PATH" | sed -E 's/p[0-9]+$//') + PART=$(echo "$DISK_PATH" | sed -E 's/.*p([0-9]+)$/\1/') + ;; + *) + # Standard SCSI (sda4 → sda + 4) + DISK=$(echo "$DISK_PATH" | sed -E 's/[0-9]+$//') + PART=$(echo "$DISK_PATH" | sed -E 's/.*([0-9]+)$/\1/') + ;; +esac + +# Expand partition +growpart "$DISK" "$PART" + +# Resize PV +pvresize "$PV" + +# Extend LV to full free space and resize filesystem +lvextend -r -l +100%FREE "$ROOT_LV" \ No newline at end of file diff --git a/pkg/provider/azure/action/rhel/rhel.go b/pkg/provider/azure/action/rhel/rhel.go index 88512ed3b..480429aed 100644 --- a/pkg/provider/azure/action/rhel/rhel.go +++ b/pkg/provider/azure/action/rhel/rhel.go @@ -1,6 +1,10 @@ package rhel import ( + _ "embed" + "fmt" + "strings" + maptContext "github.com/redhat-developer/mapt/pkg/manager/context" cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request" spotTypes "github.com/redhat-developer/mapt/pkg/provider/api/spot" @@ -24,19 +28,41 @@ type RhelArgs struct { Spot *spotTypes.SpotArgs } +func imageRef(version, arch string) *data.ImageReference { + if arch == "arm64" { + return &data.ImageReference{ + Publisher: "rhel-arm64", + Offer: "RHEL", + Sku: fmt.Sprintf( + "%s-arm64", + strings.ReplaceAll(version, ".", "_"))} + } + return &data.ImageReference{ + Publisher: "RedHat", + Offer: "RHEL", + Sku: fmt.Sprintf( + "%s-gen2", + strings.ReplaceAll(version, ".", ""))} +} + +//go:embed expand-root-disk.sh +var ExpandRootDisk []byte + func Create(mCtxArgs *maptContext.ContextArgs, r *RhelArgs) (err error) { logging.Debug("Creating RHEL Server") rhelCloudConfig := &rhelApi.CloudConfigArgs{ - SNCProfile: r.ProfileSNC, - SubsUsername: r.SubsUsername, - SubsPassword: r.SubsUserpass, - Username: r.Username} + SNCProfile: r.ProfileSNC, + SubsUsername: r.SubsUsername, + SubsPassword: r.SubsUserpass, + Username: r.Username, + ExpandRootDisk: ExpandRootDisk} azureLinuxRequest := &azureLinux.LinuxArgs{ Prefix: r.Prefix, Location: r.Location, ComputeRequest: r.ComputeRequest, Spot: r.Spot, + ImageRef: imageRef(r.Version, r.Arch), Version: r.Version, Arch: r.Arch, OSType: data.RHEL, diff --git a/pkg/provider/azure/data/images.go b/pkg/provider/azure/data/images.go index fc2f29b26..aced2ddac 100644 --- a/pkg/provider/azure/data/images.go +++ b/pkg/provider/azure/data/images.go @@ -35,7 +35,7 @@ func IsImageOffered(ctx context.Context, req ImageRequest) error { } // for azure offered VM images: https://learn.microsoft.com/en-us/rest/api/compute/virtual-machine-images/get // there's a different API to check but currently we only check availability of community images - return fmt.Errorf("no valid image to check") + return nil } func getCommunityImage(ctx context.Context, c *armcompute.ClientFactory, id, region *string) (*armcompute.CommunityGalleryImagesClientGetResponse, error) { diff --git a/pkg/target/host/rhel/cloud-config-base b/pkg/target/host/rhel/cloud-config-base index 2db674814..19fdb131a 100644 --- a/pkg/target/host/rhel/cloud-config-base +++ b/pkg/target/host/rhel/cloud-config-base @@ -3,26 +3,38 @@ rh_subscription: username: {{.SubscriptionUsername}} password: {{.SubscriptionPassword}} auto-attach: true +growpart: + mode: auto + devices: ["/"] + ignore_growroot_disabled: false runcmd: + - /usr/local/bin/expand-root-disk.sh - while fuser /var/lib/rpm/.rpm.lock > /dev/null 2>&1 ; do sleep 1 ; done - dnf install -y podman {{ if .ActionsRunnerSnippet }} - sudo -u {{ .Username }} bash -c /opt/install-ghrunner.sh{{ end }} {{ if .CirrusSnippet }} - /opt/setup-cirrus-service.sh{{ end }} {{ if .GitLabSnippet }} - sudo -u {{ .Username }} bash -c /opt/install-glrunner.sh{{ end }} -{{ if .ActionsRunnerSnippet }}write_files: +write_files: +{{- if .ExpandRootDisk }} + - path: /usr/local/bin/expand-root-disk.sh + permissions: '0755' + content: | +{{ .ExpandRootDisk }} +{{- end }} +{{ if .ActionsRunnerSnippet }} # Github actions runner installation - content: | {{ .ActionsRunnerSnippet }} path: /opt/install-ghrunner.sh permissions: '0755' {{ end }} -{{ if .CirrusSnippet }}write_files: +{{ if .CirrusSnippet }} - content: | {{.CirrusSnippet}} path: /opt/setup-cirrus-service.sh permissions: '0755' {{ end }} -{{ if .GitLabSnippet }}write_files: +{{ if .GitLabSnippet }} - content: | {{.GitLabSnippet}} path: /opt/install-glrunner.sh diff --git a/pkg/target/host/rhel/cloud-config.go b/pkg/target/host/rhel/cloud-config.go index 714066f98..fa3e04eb2 100644 --- a/pkg/target/host/rhel/cloud-config.go +++ b/pkg/target/host/rhel/cloud-config.go @@ -9,6 +9,7 @@ import ( "github.com/redhat-developer/mapt/pkg/integrations/cirrus" "github.com/redhat-developer/mapt/pkg/integrations/github" "github.com/redhat-developer/mapt/pkg/integrations/gitlab" + cloudinit "github.com/redhat-developer/mapt/pkg/util/cloud-init" "github.com/redhat-developer/mapt/pkg/util/file" ) @@ -16,6 +17,7 @@ type CloudConfigArgs struct { SNCProfile bool SubsUsername, SubsPassword string Username string + ExpandRootDisk []byte } type userDataValues struct { @@ -25,6 +27,7 @@ type userDataValues struct { ActionsRunnerSnippet string CirrusSnippet string GitLabSnippet string + ExpandRootDisk string } //go:embed cloud-config-base @@ -50,15 +53,22 @@ func (r *CloudConfigArgs) CloudConfig() (*string, error) { if err != nil { return nil, err } - userdata, err := file.Template( - userDataValues{ - r.SubsUsername, - r.SubsPassword, - r.Username, - *ghActionsRunnerSnippet, - *cirrusSnippet, - *gitlabSnippet}, - templateConfig) + udv := userDataValues{ + SubscriptionUsername: r.SubsUsername, + SubscriptionPassword: r.SubsPassword, + Username: r.Username, + ActionsRunnerSnippet: *ghActionsRunnerSnippet, + CirrusSnippet: *cirrusSnippet, + GitLabSnippet: *gitlabSnippet} + if r.ExpandRootDisk != nil { + snippet := string(r.ExpandRootDisk[:]) + iSnippet, err := cloudinit.IndentWriteFile(&snippet) + if err != nil { + return nil, err + } + udv.ExpandRootDisk = *iSnippet + } + userdata, err := file.Template(udv, templateConfig) if err != nil { return nil, err } @@ -88,15 +98,22 @@ func (r *CloudConfigArgs) CloudConfigWithGitLabToken(gitlabAuthToken string) (st if err != nil { return "", err } - userdata, err := file.Template( - userDataValues{ - r.SubsUsername, - r.SubsPassword, - r.Username, - *ghActionsRunnerSnippet, - *cirrusSnippet, - *gitlabSnippet}, - templateConfig) + udv := userDataValues{ + SubscriptionUsername: r.SubsUsername, + SubscriptionPassword: r.SubsPassword, + Username: r.Username, + ActionsRunnerSnippet: *ghActionsRunnerSnippet, + CirrusSnippet: *cirrusSnippet, + GitLabSnippet: *gitlabSnippet} + if r.ExpandRootDisk != nil { + snippet := string(r.ExpandRootDisk[:]) + iSnippet, err := cloudinit.IndentWriteFile(&snippet) + if err != nil { + return "", err + } + udv.ExpandRootDisk = *iSnippet + } + userdata, err := file.Template(udv, templateConfig) if err != nil { return "", err } diff --git a/pkg/util/cloud-init/util.go b/pkg/util/cloud-init/util.go new file mode 100644 index 000000000..38d3d927a --- /dev/null +++ b/pkg/util/cloud-init/util.go @@ -0,0 +1,26 @@ +package cloudinit + +import ( + "fmt" + "strings" +) + +// If we add the snippet as part of a cloud init file the strategy +// would be create the file with write_files: +// i.e. +// write_files: +// +// # Cirrus service setup +// - content: | +// {{ .CirrusSnippet }} <----- 6 spaces +// +// to do so we need to indent 6 spaces each line of the snippet +func IndentWriteFile(snippet *string) (*string, error) { + lines := strings.Split(strings.TrimSpace(*snippet), "\n") + for i, line := range lines { + // Added 6 spaces before each line + lines[i] = fmt.Sprintf(" %s", line) + } + identedSnippet := strings.Join(lines, "\n") + return &identedSnippet, nil +}