Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ To get the list of available tasks

Run any `-h` on any of the available tasks for more information

## MacOS support

The `aws.create-vm` task should allow you to spin up a MacOS instance using `-o macos` flag. Note that spinning such an instance is expensive because it requires a dedicated host. When you have one running please reuse it instead of creating new instances every time you need it. The cleaner will automatically get rid of the dedicated hosts.
## Troubleshooting

### Environment and configuration
Expand Down
4 changes: 2 additions & 2 deletions components/datadog/agent/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func NewHostAgent(e config.Env, host *remoteComp.Host, options ...agentparams.Op
}

func (h *HostAgent) installScriptInstallation(env config.Env, params *agentparams.Params, baseOpts ...pulumi.ResourceOption) (command.Command, error) {
installCmdStr, err := h.manager.getInstallCommand(params.Version, params.AdditionalInstallParameters)
installCmdStr, err := h.manager.getInstallCommand(params.Version, env.AgentAPIKey(), params.AdditionalInstallParameters)
if err != nil {
return nil, err
}
Expand All @@ -83,7 +83,7 @@ func (h *HostAgent) installScriptInstallation(env config.Env, params *agentparam
installCmd, err := h.Host.OS.Runner().Command(
h.namer.ResourceName("install-agent"),
&command.Args{
Create: pulumi.Sprintf(installCmdStr, env.AgentAPIKey()),
Create: installCmdStr,
}, baseOpts...)
if err != nil {
return nil, err
Expand Down
7 changes: 4 additions & 3 deletions components/datadog/agent/host_linuxos.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (am *agentLinuxManager) directInstallCommand(_ config.Env, packagePath stri
return am.targetOS.PackageManager().Ensure("./"+packagePath, nil, "", os.AllowUnsignedPackages(true), os.WithPulumiResourceOptions(opts...))
}

func (am *agentLinuxManager) getInstallCommand(version agentparams.PackageVersion, _ []string) (string, error) {
func (am *agentLinuxManager) getInstallCommand(version agentparams.PackageVersion, apiKey pulumi.StringInput, _ []string) (pulumi.StringOutput, error) {
var commandLine string
testEnvVars := []string{}

Expand Down Expand Up @@ -59,10 +59,11 @@ func (am *agentLinuxManager) getInstallCommand(version agentparams.PackageVersio

commandLine = strings.Join(testEnvVars, " ")

return fmt.Sprintf(
commandLine = fmt.Sprintf(
`for i in 1 2 3 4 5; do curl -fsSL https://s3.amazonaws.com/dd-agent/scripts/%v -o install-script.sh && break || sleep $((2**$i)); done && for i in 1 2 3; do DD_API_KEY=%%s %v DD_INSTALL_ONLY=true bash install-script.sh && exit 0 || sleep $((2**$i)); done; exit 1`,
fmt.Sprintf("install_script_agent%s.sh", version.Major),
commandLine), nil
commandLine)
return pulumi.Sprintf(commandLine, apiKey), nil
}

func (am *agentLinuxManager) getAgentConfigFolder() string {
Expand Down
78 changes: 78 additions & 0 deletions components/datadog/agent/host_macos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package agent

import (
"fmt"
"strings"

"github.com/DataDog/test-infra-definitions/common/config"
"github.com/DataDog/test-infra-definitions/components/command"
"github.com/DataDog/test-infra-definitions/components/datadog/agentparams"
remoteComp "github.com/DataDog/test-infra-definitions/components/remote"

"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type agentMacOSManager struct {
host *remoteComp.Host
}

func newMacOSManager(host *remoteComp.Host) agentOSManager {
return &agentMacOSManager{host: host}
}

// directInstallCommand expects a locally provided .dmg or .pkg uploaded to the host; it will install it with installer
func (am *agentMacOSManager) directInstallCommand(env config.Env, packagePath string, _ agentparams.PackageVersion, additionalInstallParameters []string, opts ...pulumi.ResourceOption) (command.Command, error) {

Check failure on line 24 in components/datadog/agent/host_macos.go

View workflow job for this annotation

GitHub Actions / lint-go

unused-parameter: parameter 'env' seems to be unused, consider removing or renaming it as _ (revive)
// Unsupported for now.
return nil, fmt.Errorf("installing directly from a dmg without the install script requires way too many step that would imply duplicating the install script code in there")
}

// getInstallCommand downloads appropriate pkg and installs it
func (am *agentMacOSManager) getInstallCommand(version agentparams.PackageVersion, apiKey pulumi.StringInput, _ []string) (pulumi.StringOutput, error) {
// For macOS, use the official install script which supports DD_API_KEY and version envs,
// mirroring Linux flow but using the macOS path. The script detects OS and uses pkg.
// If pipeline is specified, we cannot use public script; we assume local package will be provided in that case.

exports := []string{}
if version.Major != "" {
exports = append(exports, fmt.Sprintf("DD_AGENT_MAJOR_VERSION=%s", version.Major))
}
if version.Minor != "" {
exports = append(exports, fmt.Sprintf("DD_AGENT_MINOR_VERSION=%s", version.Minor))
}

if version.PipelineID != "" {
exports = append(exports, fmt.Sprintf("DD_REPO_URL=https://dd-agent-macostesting.s3.amazonaws.com/ci/datadog-agent/pipeline-%s-%s", version.PipelineID, am.host.OS.Descriptor().Architecture))
}

env := strings.Join(exports, " ")
// Retry curl few times
cmd := fmt.Sprintf(`for i in 1 2 3 4 5; do curl -fsSL https://install.datadoghq.com/scripts/install_mac_os.sh -o install-script.sh && break || sleep $((2**$i)); done && for i in 1 2 3; do DD_API_KEY=%%s %%s %[1]s DD_INSTALL_ONLY=true bash install-script.sh && exit 0 || sleep $((2**$i)); done; exit 1`, env)
Comment thread
KevinFairise2 marked this conversation as resolved.
// Only the systemdaemon install is supported on macOS, because single user requires to interact with the pop-up.
pulumiCmdStr := pulumi.Sprintf(cmd, apiKey, pulumi.Sprintf("DD_SYSTEMDAEMON_INSTALL=true DD_SYSTEMDAEMON_USER_GROUP=%s:staff", am.host.Username))
return pulumiCmdStr, nil
}

func (am *agentMacOSManager) getAgentConfigFolder() string {
// macOS Agent config default
return "/opt/datadog-agent/etc"
}

func (am *agentMacOSManager) restartAgentServices(transform command.Transformer, opts ...pulumi.ResourceOption) (command.Command, error) {
// On macOS, the launchd service is "com.datadoghq.agent"
cmdName := am.host.Name() + "-restart-agent"
var cmdArgs command.RunnerCommandArgs = &command.Args{
Sudo: true,
Create: pulumi.String("launchctl kickstart -k system/com.datadoghq.agent"),
}
if transform != nil {
cmdName, cmdArgs = transform(cmdName, cmdArgs)
}
return am.host.OS.Runner().Command(cmdName, cmdArgs, opts...)
}

func (am *agentMacOSManager) ensureAgentUninstalled(version agentparams.PackageVersion, opts ...pulumi.ResourceOption) (command.Command, error) {

Check failure on line 73 in components/datadog/agent/host_macos.go

View workflow job for this annotation

GitHub Actions / lint-go

unused-parameter: parameter 'version' seems to be unused, consider removing or renaming it as _ (revive)
// No-op the install script should support installing again when the agent is already installed
return am.host.OS.Runner().Command("no-op-uninstall-agent", &command.Args{
Create: pulumi.String("true"),
}, opts...)
}
6 changes: 4 additions & 2 deletions components/datadog/agent/host_os.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// internal interface to be able to provide the different OS-specific commands
type agentOSManager interface {
directInstallCommand(env config.Env, packagePath string, version agentparams.PackageVersion, additionalInstallParameters []string, opts ...pulumi.ResourceOption) (command.Command, error)
getInstallCommand(version agentparams.PackageVersion, additionalInstallParameters []string) (string, error)
getInstallCommand(version agentparams.PackageVersion, apiKey pulumi.StringInput, additionalInstallParameters []string) (pulumi.StringOutput, error)
getAgentConfigFolder() string
restartAgentServices(transform command.Transformer, opts ...pulumi.ResourceOption) (command.Command, error)
ensureAgentUninstalled(version agentparams.PackageVersion, opts ...pulumi.ResourceOption) (command.Command, error)
Expand All @@ -28,7 +28,9 @@ func getOSManager(host *remoteComp.Host) agentOSManager {
return newLinuxManager(host)
case os.WindowsFamily:
return newWindowsManager(host)
case os.MacOSFamily, os.UnknownFamily:
case os.MacOSFamily:
return newMacOSManager(host)
Comment thread
KevinFairise2 marked this conversation as resolved.
case os.UnknownFamily:
fallthrough
default:
panic(fmt.Sprintf("unsupported OS: %v", host.OS.Descriptor().Family()))
Expand Down
8 changes: 4 additions & 4 deletions components/datadog/agent/host_windowsos.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ $ErrorActionPreference = 'Stop';
return am.host.OS.Runner().Command("install-agent", &command.Args{Create: pulumi.Sprintf(cmd, env.AgentAPIKey())}, opts...)
}

func (am *agentWindowsManager) getInstallCommand(version agentparams.PackageVersion, additionalInstallParameters []string) (string, error) {
func (am *agentWindowsManager) getInstallCommand(version agentparams.PackageVersion, apiKey pulumi.StringInput, additionalInstallParameters []string) (pulumi.StringOutput, error) {
url, err := getAgentURL(version)
if err != nil {
return "", err
return pulumi.Sprintf(""), err
}

cmd := ""
Expand All @@ -73,11 +73,11 @@ for ($i=0; $i -lt 3; $i++) {
`, url, localFilename)
installPackageCommandStr, err := am.getInstallPackageCommand(localFilename, version, additionalInstallParameters)
if err != nil {
return "", err
return pulumi.Sprintf(""), err
}
cmd += installPackageCommandStr

return cmd, nil
return pulumi.Sprintf(cmd, apiKey), nil
}

func (am *agentWindowsManager) getInstallPackageCommand(filePath string, version agentparams.PackageVersion, additionalInstallParameters []string) (string, error) {
Expand Down
4 changes: 2 additions & 2 deletions components/datadog/agent/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func GetPackagePath(localPath string, flavor tifos.Flavor, agentFlavor string, a
wantedExt = ".deb"
case tifos.WindowsServer:
wantedExt = ".msi"
case tifos.MacosOS, tifos.Unknown:
fallthrough
case tifos.MacosOS:
wantedExt = ".dmg"
Comment thread
KevinFairise2 marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NIT: Could you add the fallthrough statement with tifos.Unknown? Like you did above

default:
return "", fmt.Errorf("unsupported flavor for local packages installation: %s", flavor)
}
Expand Down
4 changes: 2 additions & 2 deletions components/os/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const (
func ArchitectureFromString(archStr string) Architecture {
archStr = strings.ToLower(archStr)
switch archStr {
case "x86_64", "amd64", "": // Default architecture is AMD64
case "x86_64", "amd64", "", "x86_64_mac": // Default architecture is AMD64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: Why is the default architecture amd64? Since we have arm builds now maybe we want to change that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since this function set the default for all the instances and not only Macos I think it makes sense to keep AMD64 as the default. Changing that would probably change the default architecture for all the existing tests which would be quite a big change

return AMD64Arch
case "arm64", "aarch64":
case "arm64", "aarch64", "arm64_mac":
return ARM64Arch
default:
panic(fmt.Sprintf("unknown architecture: %s", archStr))
Expand Down
2 changes: 1 addition & 1 deletion components/os/macos_descriptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package os
// Implements commonly used descriptors for easier usage
var (
MacOSDefault = MacOSSonoma
MacOSSonoma = NewDescriptorWithArch(MacosOS, "sonoma", ARM64Arch)
MacOSSonoma = NewDescriptor(MacosOS, "sonoma")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we define other versions such as ventura etc. ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or is it an aws limitation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think other version are supported, at least sequoia exists. But we can probably add them later if they are needed

)
2 changes: 1 addition & 1 deletion components/os/macos_servicemanagers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
cmdName := s.e.CommonNamer().ResourceName("running", serviceName)
var cmdArgs command.RunnerCommandArgs = &command.Args{
Sudo: true,
Create: pulumi.String(fmt.Sprintf("launchctl stop %s && launchctl start %s", serviceName, serviceName)),
Create: pulumi.String(fmt.Sprintf("launchctl kickstart -k %s'", serviceName, serviceName)),

Check failure on line 24 in components/os/macos_servicemanagers.go

View workflow job for this annotation

GitHub Actions / build

fmt.Sprintf call needs 1 arg but has 2 args

Check failure on line 24 in components/os/macos_servicemanagers.go

View workflow job for this annotation

GitHub Actions / lint-go

printf: fmt.Sprintf call needs 1 arg but has 2 args (govet)
}

// If a transform is provided, use it to modify the command name and args
Expand Down
63 changes: 63 additions & 0 deletions resources/aws/ec2/dedicated_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ec2

import (
"fmt"

"github.com/DataDog/test-infra-definitions/common/config"
"github.com/DataDog/test-infra-definitions/common/utils"
"github.com/DataDog/test-infra-definitions/resources/aws"

"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type DedicatedHostArgs struct {
// Mandatory
InstanceType string // e.g., "mac1.metal", "mac2.metal"

// Optional
AvailabilityZone string // If not specified, will use first available zone
HostRecovery string // "on" or "off", defaults to "off"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NIT: Should we use a boolean for that?

Tags pulumi.StringMap
}

// NewDedicatedHost creates an EC2 Dedicated Host for Mac instances
func NewDedicatedHost(e aws.Environment, name string, args DedicatedHostArgs, opts ...pulumi.ResourceOption) (*ec2.DedicatedHost, error) {
if args.InstanceType == "" {
return nil, fmt.Errorf("InstanceType is required for dedicated host")
}

// Default values
if args.HostRecovery == "" {
args.HostRecovery = "off"
}

var availabilityZone pulumi.StringInput
if args.AvailabilityZone == "" {
// Use the same AZ as the first subnet
availabilityZone = e.RandomSubnets().Index(pulumi.Int(0)).ApplyT(func(subnetId string) (string, error) {
// Get subnet info to determine AZ
subnet, err := ec2.LookupSubnet(e.Ctx(), &ec2.LookupSubnetArgs{
Id: &subnetId,
}, e.WithProvider(config.ProviderAWS))
if err != nil {
return "", err
}
return subnet.AvailabilityZone, nil
}).(pulumi.StringOutput)
} else {
availabilityZone = pulumi.String(args.AvailabilityZone)
}

dedicatedHostArgs := &ec2.DedicatedHostArgs{
InstanceType: pulumi.String(args.InstanceType),
AvailabilityZone: availabilityZone,
HostRecovery: pulumi.String(args.HostRecovery),
}

return ec2.NewDedicatedHost(e.Ctx(),
e.Namer.ResourceName(name),
dedicatedHostArgs,
utils.MergeOptions(opts, e.WithProviders(config.ProviderAWS), pulumi.RetainOnDelete(true))..., // Retain on delete because deleting a dedicated host is not possible unless it lived for at least 24 hours, the cleanup will be done by test-infra-cleaner
)
}
2 changes: 2 additions & 0 deletions resources/aws/ec2/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// Optional
UserData string
HTTPTokensRequired bool
HostId pulumi.StringInput // For dedicated host tenancy

Check failure on line 26 in resources/aws/ec2/vm.go

View workflow job for this annotation

GitHub Actions / lint-go

var-naming: struct field HostId should be HostID (revive)
}

func NewInstance(e aws.Environment, name string, args InstanceArgs, opts ...pulumi.ResourceOption) (*ec2.Instance, error) {
Expand All @@ -45,6 +46,7 @@
"Name": e.Namer.DisplayName(255, pulumi.String(name)),
},
InstanceInitiatedShutdownBehavior: pulumi.String(e.DefaultShutdownBehavior()),
HostId: args.HostId,
}

if args.HTTPTokensRequired {
Expand Down
13 changes: 12 additions & 1 deletion resources/aws/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
DDInfraDefaultShutdownBehavior = "aws/defaultShutdownBehavior"
DDInfraDefaultInternalRegistry = "aws/defaultInternalRegistry"
DDInfraDefaultInternalDockerhubMirror = "aws/defaultInternalDockerhubMirror"
DDInfraUseMacosCompatibleSubnets = "aws/useMacosCompatibleSubnets"

// AWS ECS
DDInfraEcsExecKMSKeyID = "aws/ecs/execKMSKeyID"
Expand Down Expand Up @@ -222,7 +223,13 @@ func (e *Environment) DefaultVPCID() string {
}

func (e *Environment) DefaultSubnets() []string {
return e.GetStringListWithDefault(e.InfraConfig, DDInfraDefaultSubnetsParamName, e.envDefault.ddInfra.defaultSubnets)
defaultSubnets := []string{}
for _, subnet := range e.envDefault.ddInfra.defaultSubnets {
if !e.UseMacosCompatibleSubnets() || subnet.MacOSCompatible {
defaultSubnets = append(defaultSubnets, subnet.ID)
}
}
return defaultSubnets
}

func (e *Environment) DefaultFakeintakeECSArns() []string {
Expand Down Expand Up @@ -280,6 +287,10 @@ func (e *Environment) DefaultShutdownBehavior() string {
return e.GetStringWithDefault(e.InfraConfig, DDInfraDefaultShutdownBehavior, e.envDefault.ddInfra.defaultShutdownBehavior)
}

func (e *Environment) UseMacosCompatibleSubnets() bool {
return e.GetBoolWithDefault(e.InfraConfig, DDInfraUseMacosCompatibleSubnets, e.envDefault.ddInfra.useMacosCompatibleSubnets)
}

// ECS
func (e *Environment) ECSExecKMSKeyID() string {
return e.GetStringWithDefault(e.InfraConfig, DDInfraEcsExecKMSKeyID, e.envDefault.ddInfra.ecs.execKMSKeyID)
Expand Down
Loading
Loading