Skip to content

Commit 44d8348

Browse files
committed
feat(snc): add --profile flag with virtualization addon support
Add a profile system to the OpenShift SNC command that allows installing addons on the cluster after deployment. The first supported profile is 'virtualization' which installs the OCP Virtualization (CNV) operator and enables nested virtualization on the compute instance.
1 parent 93189d3 commit 44d8348

8 files changed

Lines changed: 188 additions & 5 deletions

File tree

cmd/mapt/cmd/aws/services/snc.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const (
2222
pullSecretFileDesc = "file path of image pull secret (download from https://console.redhat.com/openshift/create/local)"
2323
disableClusterReadiness = "disable-cluster-readiness"
2424
disableClusterReadinessDesc = "If this flag is set it will skip the checks for the cluster readiness. In this case the kubeconfig can not be generated"
25+
26+
sncProfile = "profile"
27+
sncProfileDesc = "comma separated list of profiles to apply on the SNC cluster. Profiles available: virtualization"
2528
)
2629

2730
func GetOpenshiftSNCCmd() *cobra.Command {
@@ -51,6 +54,11 @@ func createSNC() *cobra.Command {
5154
if err := viper.BindPFlags(cmd.Flags()); err != nil {
5255
return err
5356
}
57+
profiles := viper.GetStringSlice(sncProfile)
58+
computeReq := params.ComputeRequestArgs()
59+
if sncApi.ProfilesRequireNestedVirt(profiles) {
60+
computeReq.NestedVirt = true
61+
}
5462
if _, err := openshiftsnc.Create(
5563
&maptContext.ContextArgs{
5664
Context: cmd.Context(),
@@ -62,13 +70,14 @@ func createSNC() *cobra.Command {
6270
Tags: viper.GetStringMapString(params.Tags),
6371
},
6472
&sncApi.SNCArgs{
65-
ComputeRequest: params.ComputeRequestArgs(),
73+
ComputeRequest: computeReq,
6674
Spot: params.SpotArgs(),
6775
Version: viper.GetString(ocpVersion),
6876
DisableClusterReadiness: viper.IsSet(disableClusterReadiness),
6977
Arch: viper.GetString(params.LinuxArch),
7078
PullSecretFile: viper.GetString(pullSecretFile),
71-
Timeout: viper.GetString(params.Timeout)}); err != nil {
79+
Timeout: viper.GetString(params.Timeout),
80+
Profiles: profiles}); err != nil {
7281
return err
7382
}
7483
return nil
@@ -82,6 +91,7 @@ func createSNC() *cobra.Command {
8291
flagSet.StringP(pullSecretFile, "", "", pullSecretFileDesc)
8392
flagSet.StringP(params.Timeout, "", "", params.TimeoutDesc)
8493
flagSet.StringToStringP(params.Tags, "", nil, params.TagsDesc)
94+
flagSet.StringSliceP(sncProfile, "", []string{}, sncProfileDesc)
8595
params.AddComputeRequestFlags(flagSet)
8696
params.AddSpotFlags(flagSet)
8797
c.PersistentFlags().AddFlagSet(flagSet)

docs/aws/openshift-snc.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,34 @@ After the AMI is published and accessible by the account, we can use the followi
4747

4848
After the above command succeeds the `kubeconfig` to access the deployed cluster will be available in `/tmp/snc/kubeconfig`
4949

50+
## Profiles
51+
52+
Profiles are optional addons that are installed on the SNC cluster after it is ready. Use the `--profile` flag to enable one or more profiles:
53+
54+
```
55+
mapt aws openshift-snc create \
56+
--spot \
57+
--version 4.21.0 \
58+
--project-name mapt-snc \
59+
--backed-url file:///Users/tester/workspace \
60+
--conn-details-output /tmp/snc \
61+
--pull-secret-file /Users/tester/Downloads/pull-secret \
62+
--profile virtualization
63+
```
64+
65+
Multiple profiles can be specified as a comma-separated list (e.g., `--profile virtualization,serverless`).
66+
67+
### Available profiles
68+
69+
| Profile | Description |
70+
|---------|-------------|
71+
| `virtualization` | Installs [OpenShift Virtualization](https://docs.openshift.com/container-platform/latest/virt/about_virt/about-virt.html) (CNV) on the cluster. This enables running virtual machines on the single node cluster. It also enables nested virtualization on the cloud instance. |
72+
73+
### Adding new profiles
74+
75+
To add a new profile, create the following files under `pkg/target/service/snc/`:
76+
77+
1. `profile_<name>.go` — Go file that embeds the YAML manifests and returns the list of shell commands to execute on the SNC instance
78+
2. `profile_<name>_*.yaml` — YAML manifests for the profile (Namespace, Operator, CRs, etc.)
79+
3. Register the profile name in `profiles.go` by adding it to `validProfiles` and the `ProfileCommands()` switch
80+

pkg/provider/aws/action/snc/snc.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type openshiftSNCRequest struct {
4343
timeout *string
4444
pullSecretFile *string
4545
allocationData *allocation.AllocationResult
46+
profiles []string
4647
}
4748

4849
func (r *openshiftSNCRequest) validate() error {
@@ -63,6 +64,10 @@ func Create(mCtxArgs *mc.ContextArgs, args *apiSNC.SNCArgs) (_ *apiSNC.SNCResult
6364
if err != nil {
6465
return nil, err
6566
}
67+
// Validate profiles
68+
if err := apiSNC.ValidateProfiles(args.Profiles); err != nil {
69+
return nil, err
70+
}
6671
// Compose request
6772
prefix := util.If(len(args.Prefix) > 0, args.Prefix, "main")
6873
r := openshiftSNCRequest{
@@ -72,7 +77,8 @@ func Create(mCtxArgs *mc.ContextArgs, args *apiSNC.SNCArgs) (_ *apiSNC.SNCResult
7277
disableClusterReadiness: args.DisableClusterReadiness,
7378
arch: &args.Arch,
7479
pullSecretFile: &args.PullSecretFile,
75-
timeout: &args.Timeout}
80+
timeout: &args.Timeout,
81+
profiles: args.Profiles}
7682
if args.Spot != nil {
7783
r.spot = args.Spot.Spot
7884
}
@@ -252,7 +258,7 @@ func (r *openshiftSNCRequest) deploy(ctx *pulumi.Context) error {
252258
}
253259
}
254260
// Use kubeconfig as the readiness for the cluster
255-
kubeconfig, err := kubeconfig(ctx, r.prefix, c, keyResources.PrivateKey, *r.version, r.disableClusterReadiness)
261+
kubeconfig, err := kubeconfig(ctx, r.prefix, c, keyResources.PrivateKey, *r.version, r.disableClusterReadiness, r.profiles)
256262
if err != nil {
257263
return err
258264
}
@@ -361,6 +367,7 @@ func kubeconfig(ctx *pulumi.Context,
361367
c *compute.Compute, mk *tls.PrivateKey,
362368
ocpVersion string,
363369
disableClusterReadiness bool,
370+
profiles []string,
364371
) (pulumi.StringOutput, error) {
365372
// Once the cluster setup is comleted we
366373
// get the kubeconfig file from the host running the cluster
@@ -396,13 +403,29 @@ func kubeconfig(ctx *pulumi.Context,
396403
return pulumi.StringOutput{}, err
397404
}
398405

406+
// Execute profile commands after cluster is ready
407+
var lastDep pulumi.Resource = ocpCaRotatedCmd
408+
for _, profileName := range profiles {
409+
for _, pc := range apiSNC.ProfileCommands(profileName) {
410+
profileCmd, err := c.RunCommand(ctx,
411+
pc.Command,
412+
compute.LoggingCmdStd,
413+
fmt.Sprintf("%s-%s", *prefix, pc.Name), apiSNC.OCPSNCID,
414+
mk, amiUserDefault, nil, []pulumi.Resource{lastDep})
415+
if err != nil {
416+
return pulumi.StringOutput{}, err
417+
}
418+
lastDep = profileCmd
419+
}
420+
}
421+
399422
// Get content for /opt/kubeconfig
400423
getKCCmd := ("sudo cat /opt/crc/kubeconfig")
401424
getKC, err := c.RunCommand(ctx,
402425
getKCCmd,
403426
compute.NoLoggingCmdStd,
404427
fmt.Sprintf("%s-kubeconfig", *prefix), apiSNC.OCPSNCID, mk, amiUserDefault,
405-
nil, []pulumi.Resource{ocpCaRotatedCmd})
428+
nil, []pulumi.Resource{lastDep})
406429
if err != nil {
407430
return pulumi.StringOutput{}, err
408431
}

pkg/target/service/snc/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type SNCArgs struct {
4949
PullSecretFile string
5050
Spot *spotTypes.SpotArgs
5151
Timeout string
52+
Profiles []string
5253
}
5354

5455
type SNCResults struct {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package snc
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
)
7+
8+
//go:embed profile_virtualization_operators.yaml
9+
var virtOperatorsManifest []byte
10+
11+
//go:embed profile_virtualization_hco.yaml
12+
var virtHCOManifest []byte
13+
14+
func virtualizationCommands() []ProfileCommand {
15+
kcFlag := fmt.Sprintf("--kubeconfig %s", ClientKubeconfigPath)
16+
17+
applyOperators := fmt.Sprintf(
18+
"sudo bash -c 'cat <<\"EOFMANIFEST\" | oc apply %s -f -\n%s\nEOFMANIFEST'",
19+
kcFlag, string(virtOperatorsManifest))
20+
21+
waitCSV := fmt.Sprintf(
22+
"sudo bash -c 'until oc get csv %s -n openshift-cnv -o jsonpath=\"{.items[0].status.phase}\" 2>/dev/null | grep -q Succeeded; do echo \"Waiting for CNV CSV...\"; sleep 15; done'",
23+
kcFlag)
24+
25+
applyHCO := fmt.Sprintf(
26+
"sudo bash -c 'cat <<\"EOFHCO\" | oc apply %s -f -\n%s\nEOFHCO'",
27+
kcFlag, string(virtHCOManifest))
28+
29+
waitHCO := fmt.Sprintf(
30+
"sudo bash -c 'until oc get hyperconverged kubevirt-hyperconverged %s -n openshift-cnv -o jsonpath=\"{.status.conditions[?(@.type==\\\"Available\\\")].status}\" 2>/dev/null | grep -q True; do echo \"Waiting for HyperConverged...\"; sleep 15; done'",
31+
kcFlag)
32+
33+
return []ProfileCommand{
34+
{Name: "virt-apply-operators", Command: applyOperators},
35+
{Name: "virt-wait-csv", Command: waitCSV},
36+
{Name: "virt-apply-hco", Command: applyHCO},
37+
{Name: "virt-wait-hco-ready", Command: waitHCO},
38+
}
39+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: hco.kubevirt.io/v1beta1
2+
kind: HyperConverged
3+
metadata:
4+
name: kubevirt-hyperconverged
5+
namespace: openshift-cnv
6+
spec: {}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: openshift-cnv
5+
---
6+
apiVersion: operators.coreos.com/v1
7+
kind: OperatorGroup
8+
metadata:
9+
name: kubevirt-hyperconverged-group
10+
namespace: openshift-cnv
11+
spec:
12+
targetNamespaces:
13+
- openshift-cnv
14+
---
15+
apiVersion: operators.coreos.com/v1alpha1
16+
kind: Subscription
17+
metadata:
18+
name: hco-operatorhub
19+
namespace: openshift-cnv
20+
spec:
21+
source: redhat-operators
22+
sourceNamespace: openshift-marketplace
23+
name: kubevirt-hyperconverged
24+
channel: stable
25+
installPlanApproval: Automatic

pkg/target/service/snc/profiles.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package snc
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
)
7+
8+
const (
9+
ProfileVirtualization = "virtualization"
10+
)
11+
12+
// validProfiles is the single source of truth for supported profile names.
13+
var validProfiles = []string{ProfileVirtualization}
14+
15+
// ProfileCommand holds a logical name and the shell command string
16+
// for one step of a profile's installation on the SNC instance.
17+
type ProfileCommand struct {
18+
Name string
19+
Command string
20+
}
21+
22+
// ValidateProfiles checks that all requested profiles are supported.
23+
func ValidateProfiles(profiles []string) error {
24+
for _, p := range profiles {
25+
if !slices.Contains(validProfiles, p) {
26+
return fmt.Errorf("profile %q is not supported for SNC. Supported profiles: %v", p, validProfiles)
27+
}
28+
}
29+
return nil
30+
}
31+
32+
// ProfileCommands returns an ordered list of commands to execute via SSH
33+
// for the given profile. Each command will be chained as a Pulumi
34+
// remote.Command depending on the previous one.
35+
func ProfileCommands(profile string) []ProfileCommand {
36+
switch profile {
37+
case ProfileVirtualization:
38+
return virtualizationCommands()
39+
default:
40+
return nil
41+
}
42+
}
43+
44+
// ProfilesRequireNestedVirt returns true if any of the given profiles
45+
// requires nested virtualization on the compute instance.
46+
func ProfilesRequireNestedVirt(profiles []string) bool {
47+
return slices.Contains(profiles, ProfileVirtualization)
48+
}

0 commit comments

Comments
 (0)