Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions components/kubernetes/openshift.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package kubernetes

import (
"fmt"
"os"

"github.com/DataDog/test-infra-definitions/common/config"
"github.com/DataDog/test-infra-definitions/common/utils"
"github.com/DataDog/test-infra-definitions/components"
"github.com/DataDog/test-infra-definitions/components/command"
oscomp "github.com/DataDog/test-infra-definitions/components/os"
"github.com/DataDog/test-infra-definitions/components/remote"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func NewLocalOpenShiftCluster(env config.Env, name string, opts ...pulumi.ResourceOption) (*Cluster, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the condition to be able to run that locally? Is it working on MacOS laptops?

@shaina-patel-23 shaina-patel-23 Jul 9, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a commit where I ran the function and confirmed it worked on macOS. I modeled this after the unused NewLocalKindCluster setup. In both situations the local setups aren't referenced anywhere, so I'm fine with removing it if you think that's appropriate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the NewLocalKindCluster setup is used in a provisioner on datadog-agent side: https://github.com/DataDog/datadog-agent/blob/main/test/new-e2e/pkg/provisioners/local/kubernetes/kind.go#L132
The goal is provide a local setup that allows you to iterate faster when creating a test for the first time

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. I’ve implemented functionality for passing the pull secret as a parameter for both the local cluster and cluster on GCP. However, I didn’t add an invoke command for the local cluster creation to keep consistency with other cluster types. For the datadog-agent integration, I anticipate needing to adapt the pull secret config approach. Currently, the GCP OpenShift cluster receives the pull secret through the invoke setup process, but the datadog-agent environment may require a different configuration pattern. I’d appreciate any guidance for how to handle that…

return components.NewComponent(env, name, func(clusterComp *Cluster) error {
opts = utils.MergeOptions[pulumi.ResourceOption](opts, pulumi.Parent(clusterComp))
commonEnvironment := env
runner := command.NewLocalRunner(env, command.LocalRunnerArgs{
OSCommand: command.NewUnixOSCommand(),
})

pullSecretPath := os.Getenv("PULL_SECRET_PATH")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need it to be passed as an environment variable? Can we make it a params of the function instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the setup too use function params instead of env variables by reading the pull secret path from Pulumi config in openshift.go, adding support for invoke gcp.create-openshift.go in tasks/gcp/openshift.py, and updating setup.py to optionally prompt for the path in invoke setup

if pullSecretPath == "" {
return fmt.Errorf("PULL_SECRET_PATH environment variable is not set")
}

crcSetup, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("crc-setup"), &command.Args{
Create: pulumi.String("crc setup"),
}, opts...)
if err != nil {
return err
}
startCluster, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("crc-start"), &command.Args{
Create: pulumi.Sprintf("crc start -p %s", pullSecretPath),
Delete: pulumi.String("crc stop"),
Triggers: pulumi.Array{
pulumi.String(pullSecretPath),
},
}, utils.MergeOptions(opts, utils.PulumiDependsOn(crcSetup))...)
if err != nil {
return err
}

kubeConfigCmd, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("get-kubeconfig"), &command.Args{
Create: pulumi.String("cat ~/.crc/machines/crc/kubeconfig"),
}, utils.MergeOptions(opts, utils.PulumiDependsOn(startCluster))...)
if err != nil {
return err
}

clusterComp.KubeConfig = kubeConfigCmd.StdoutOutput()
clusterComp.ClusterName = pulumi.String("openshift").ToStringOutput()
return nil
}, opts...)
}

func NewOpenShiftCluster(env config.Env, vm *remote.Host, name string, opts ...pulumi.ResourceOption) (*Cluster, error) {
pullSecretPath := os.Getenv("PULL_SECRET_PATH")
if pullSecretPath == "" {
return nil, fmt.Errorf("PULL_SECRET_PATH environment variable is not set")
}

return components.NewComponent(env, name, func(clusterComp *Cluster) error {
openShiftClusterName := env.CommonNamer().DisplayName(49)
opts = utils.MergeOptions[pulumi.ResourceOption](opts, pulumi.Parent(clusterComp))
runner := vm.OS.Runner()
commonEnvironment := env

openShiftInstallBinary, err := InstallOpenShiftBinary(env, vm, opts...)
if err != nil {
return err
}

pullSecretContent, err := utils.ReadSecretFile(pullSecretPath)
if err != nil {
return err
}
pullSecretFile, err := vm.OS.FileManager().CopyInlineFile(
pullSecretContent,
"/tmp/pull-secret.txt",
)
if err != nil {
return err
}

installLibvirt, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("install-libvirt"), &command.Args{
Create: pulumi.String(`
sudo dnf install -y libvirt NetworkManager`),
}, utils.MergeOptions(opts, utils.PulumiDependsOn(openShiftInstallBinary))...)
if err != nil {
return err
}
// To avoid the crc-daemon.service being stopped when the user session ends, we enable linger for the user
enableLinger, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("enable-linger"), &command.Args{
Create: pulumi.String("loginctl enable-linger"),
}, utils.MergeOptions(opts, utils.PulumiDependsOn(installLibvirt))...)
if err != nil {
return err
}

setupCRC, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("crc-setup"), &command.Args{
Create: pulumi.String("crc setup"),
}, utils.MergeOptions(opts, utils.PulumiDependsOn(pullSecretFile, enableLinger))...)
if err != nil {
return err
}

startCRC, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("crc-start"), &command.Args{
Create: pulumi.String(`crc start -p /tmp/pull-secret.txt`),
Delete: pulumi.String("crc stop && crc delete && crc cleanup"),
Triggers: pulumi.Array{
pulumi.String(pullSecretPath),
},
}, utils.MergeOptions(opts, utils.PulumiDependsOn(setupCRC))...)
if err != nil {
return err
}

kubeConfig, err := runner.Command(commonEnvironment.CommonNamer().ResourceName("get-kubeconfig"), &command.Args{
Create: pulumi.String("cat ~/.crc/machines/crc/kubeconfig"),
}, utils.MergeOptions(opts, utils.PulumiDependsOn(startCRC))...)
if err != nil {
return err
}

clusterComp.KubeConfig = kubeConfig.StdoutOutput()
clusterComp.ClusterName = openShiftClusterName.ToStringOutput()
return nil
}, opts...)
}

func InstallOpenShiftBinary(env config.Env, vm *remote.Host, opts ...pulumi.ResourceOption) (pulumi.Resource, error) {
openShiftArch := vm.OS.Descriptor().Architecture
if openShiftArch == oscomp.AMD64Arch {
openShiftArch = "amd64"
}
return vm.OS.Runner().Command(
env.CommonNamer().ResourceName("crc-install"),
&command.Args{
Create: pulumi.Sprintf(`curl -fsSL https://developers.redhat.com/content-gateway/file/pub/openshift-v4/clients/crc/2.52.0/crc-linux-%s.tar.xz | \
sudo tar -xJ -C /usr/local/bin --strip-components=1 crc-linux-2.52.0-%s/crc`, openShiftArch, openShiftArch),
}, opts...)
}
26 changes: 14 additions & 12 deletions registry/scenarios.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"strings"

"github.com/DataDog/test-infra-definitions/scenarios/gcp/gke"
"github.com/DataDog/test-infra-definitions/scenarios/gcp/openshiftvm"

"github.com/DataDog/test-infra-definitions/scenarios/aws/ec2"
"github.com/DataDog/test-infra-definitions/scenarios/aws/ecs"
Expand All @@ -23,18 +24,19 @@ type ScenarioRegistry map[string]pulumi.RunFunc

func Scenarios() ScenarioRegistry {
return ScenarioRegistry{
"aws/vm": ec2.VMRun,
"aws/dockervm": ec2.VMRunWithDocker,
"aws/ecs": ecs.Run,
"aws/eks": eks.Run,
"aws/installer": installer.Run,
"aws/microvms": microvms.Run,
"aws/kind": kindvm.Run,
"az/vm": computerun.VMRun,
"az/aks": aks.Run,
"gcp/vm": gcpcompute.VMRun,
"gcp/gke": gke.Run,
"localpodman/vm": localpodmanrun.VMRun,
"aws/vm": ec2.VMRun,
"aws/dockervm": ec2.VMRunWithDocker,
"aws/ecs": ecs.Run,
"aws/eks": eks.Run,
"aws/installer": installer.Run,
"aws/microvms": microvms.Run,
"aws/kind": kindvm.Run,
"az/vm": computerun.VMRun,
"az/aks": aks.Run,
"gcp/vm": gcpcompute.VMRun,
"gcp/gke": gke.Run,
"gcp/openshiftvm": openshiftvm.Run,
"localpodman/vm": localpodmanrun.VMRun,
}
}

Expand Down
9 changes: 7 additions & 2 deletions resources/gcp/compute/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ func NewLinuxInstance(e gcp.Environment, name string, imageName string, instance
Subnetwork: pulumi.String(e.DefaultSubnet()),
},
},
Name: e.Namer.DisplayName(63, pulumi.String(name)),
MachineType: pulumi.String(instanceType),
Name: e.Namer.DisplayName(63, pulumi.String(name)),
MachineType: pulumi.String(instanceType),
AllowStoppingForUpdate: pulumi.Bool(true),
AdvancedMachineFeatures: &compute.InstanceAdvancedMachineFeaturesArgs{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these ones supported for all the machine types? I know that on AWS you can only enable nested virtualization on specific machines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On GCP, nested virtualization is only supported on specific machine types like N1, N2, etc., and not on E2 or shared-core instances. I chose n2-standard-8 to meet vCPU and memory needs for CRC. Also, it also supports nested virtualization, which is needed to run CRC inside a VM on GCP.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case what happens if we set instanceType to a type that does not support nested virtualization? Your change enforce the NestedVirtualization to be enabled all the time, so I am afraid it will break when some instance type are used. Maybe we should make it configurable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve addressed the nested virt concern by making the feature configurable. EnableNestedVirtualization now defaults to false for all GCP VM instances, with the OpenShift scenario setting it to true. I also removed the AllowStoppingForUpdate setting as I had added it only for testing. Also dropped the new WithInstanceType function I introduced in vmargs.go after realizing I could leverage the existing ddinfra:gcp/defaultInstanceType configuration.

EnableNestedVirtualization: pulumi.Bool(true),
},
Tags: pulumi.StringArray{
pulumi.String("appgate-gateway"),
pulumi.String("nat-us-central1"),
Expand All @@ -36,6 +40,7 @@ func NewLinuxInstance(e gcp.Environment, name string, imageName string, instance
Labels: pulumi.StringMap{
"my_label": pulumi.String("value"),
},
Size: pulumi.Int(100),
},
},
Metadata: pulumi.StringMap{
Expand Down
12 changes: 12 additions & 0 deletions scenarios/gcp/compute/os_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type imageResolveFunc func(e gcp.Environment, osInfo os.Descriptor) (string, err

var imageResolvers = map[os.Flavor]imageResolveFunc{
os.Ubuntu: resolveUbuntuImage,
os.RedHat: resolveRhelImage,
}

func resolveUbuntuImage(_ gcp.Environment, osInfo os.Descriptor) (string, error) {
Expand All @@ -23,3 +24,14 @@ func resolveUbuntuImage(_ gcp.Environment, osInfo os.Descriptor) (string, error)
return "", nil
}
}
func resolveRhelImage(_ gcp.Environment, osInfo os.Descriptor) (string, error) {
if osInfo.Version == "" {
osInfo.Version = os.RedHatDefault.Version
}
switch osInfo.Version {
case os.RedHat9.Version:
return "rhel-9-v20250611", nil
}

return "", nil
}
24 changes: 24 additions & 0 deletions scenarios/gcp/compute/vmargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package compute

import (
"github.com/DataDog/test-infra-definitions/common"
"github.com/DataDog/test-infra-definitions/common/utils"
"github.com/DataDog/test-infra-definitions/components/os"
)

Expand All @@ -18,3 +19,26 @@ func newParams(options ...VMOption) (*vmArgs, error) {

return common.ApplyOption(vmArgs, options)
}

// WithOS sets the OS
// Version defaults to latest
func WithOS(osDesc os.Descriptor) VMOption {
return WithOSArch(osDesc, osDesc.Architecture)
}

// WithArch set the architecture and the operating system.
// Version defaults to latest
func WithOSArch(osDesc os.Descriptor, arch os.Architecture) VMOption {
return func(p *vmArgs) error {
p.osInfo = utils.Pointer(osDesc.WithArch(arch))
return nil
}
}

// WithInstanceType set the instance type
func WithInstanceType(instanceType string) VMOption {
return func(p *vmArgs) error {
p.instanceType = instanceType
return nil
}
}
36 changes: 36 additions & 0 deletions scenarios/gcp/openshiftvm/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package openshiftvm

import (
localKubernetes "github.com/DataDog/test-infra-definitions/components/kubernetes"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not alias it localKubernetes here, because it actually create the openshift cluster in a remote VM on GCP

"github.com/DataDog/test-infra-definitions/components/os"
resGcp "github.com/DataDog/test-infra-definitions/resources/gcp"
"github.com/DataDog/test-infra-definitions/scenarios/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func Run(ctx *pulumi.Context) error {
gcpEnv, err := resGcp.NewEnvironment(ctx)
if err != nil {
return err
}

osDesc := os.DescriptorFromString("redhat:9", os.RedHat9)
vm, err := compute.NewVM(gcpEnv, "openshift",
compute.WithOS(osDesc),
compute.WithInstanceType("n2-standard-8"))
if err != nil {
return err
}
if err := vm.Export(ctx, nil); err != nil {
return err
}

openshiftCluster, err := localKubernetes.NewOpenShiftCluster(&gcpEnv, vm, "openshift")
if err != nil {
return err
}
if err := openshiftCluster.Export(ctx, nil); err != nil {
return err
}
return openshiftCluster.Export(ctx, nil)
}
Loading