Skip to content

Commit

Permalink
Add E2E tests for Domain Controllers installation (#22507)
Browse files Browse the repository at this point in the history
Add E2E tests for Domain Controllers installation
  • Loading branch information
julien-lebot authored Feb 27, 2024
1 parent 7b675e6 commit c41a677
Show file tree
Hide file tree
Showing 32 changed files with 881 additions and 20 deletions.
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ workflow:
- tools/windows/DatadogAgentInstaller/**/*
- .gitlab/new-e2e_testing/windows.yml
- test/new-e2e/tests/windows/install-test/**/*
- test/new-e2e/tests/windows/domain-test/**/*
- tasks/msi.py
compare_to: main # TODO: use a variable, when this is supported https://gitlab.com/gitlab-org/gitlab/-/issues/369916

Expand Down
1 change: 1 addition & 0 deletions .gitlab/e2e_test_junit_upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ e2e_installer_test_junit_upload:
- new-e2e-agent-platform-install-script-upgrade7-ubuntu-iot-agent-x86_64
- new-e2e-windows-agent-msi-windows-server-a6-x86_64
- new-e2e-windows-agent-msi-windows-server-a7-x86_64
- new-e2e-windows-agent-domain-tests-a7-x86_64

e2e_pre_test_junit_upload:
extends: .e2e_test_junit_template
Expand Down
27 changes: 27 additions & 0 deletions .gitlab/new-e2e_testing/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
- export LAST_STABLE_VERSION=$(invoke release.get-release-json-value "last_stable::$AGENT_MAJOR_VERSION")
- !reference [.new_e2e_template, script]

.new-e2e_windows_domain_test:
variables:
TARGETS: ./tests/windows/domain-test
TEAM: windows-agent
before_script:
# WINDOWS_AGENT_VERSION is used to verify the installed agent version
# Must run before new_e2e_template changes the aws profile
- export WINDOWS_AGENT_VERSION=$(invoke agent.version --major-version $AGENT_MAJOR_VERSION)
- !reference [.new_e2e_template, before_script]
script:
# LAST_STABLE_VERSION is used for upgrade test
- export LAST_STABLE_VERSION=$(invoke release.get-release-json-value "last_stable::$AGENT_MAJOR_VERSION")
- !reference [.new_e2e_template, script]

.new-e2e_windows_installer_tests:
parallel:
matrix:
Expand Down Expand Up @@ -63,6 +77,19 @@ new-e2e-windows-agent-msi-windows-server-a7-x86_64:
- !reference [.on_deploy_a7]
- !reference [.on_windows_installer_changes_or_manual]

new-e2e-windows-agent-domain-tests-a7-x86_64:
stage: kitchen_testing
variables:
WINDOWS_AGENT_ARCH: "x86_64"
extends:
- .new_e2e_template
- .new-e2e_windows_domain_test
- .new-e2e_agent_a7
needs: ["deploy_windows_testing-a7"]
rules:
- !reference [.on_deploy_a7]
- !reference [.on_windows_installer_changes_or_manual]

## single test for PRs
## skipped if the full tests are running
new-e2e-windows-agent-msi-upgrade-windows-server-a7-x86_64:
Expand Down
1 change: 1 addition & 0 deletions test/new-e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
github.com/kr/pretty v0.3.1
github.com/pkg/sftp v1.13.6
github.com/pulumi/pulumi/sdk/v3 v3.99.0
github.com/pulumiverse/pulumi-time/sdk v0.0.0-20231010123146-089d7304da13
github.com/samber/lo v1.38.1
github.com/sethvargo/go-retry v0.2.4
github.com/stretchr/testify v1.8.5-0.20231013065317-89920137cdfa
Expand Down
2 changes: 2 additions & 0 deletions test/new-e2e/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions test/new-e2e/pkg/environments/activedirectory/active_directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package activedirectory contains the code necessary to create an Active Directory environment for e2e tests
package activedirectory

import (
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/components"
"github.com/DataDog/test-infra-definitions/common/config"
)

// Env represents an Active Directory environment for an e2e test
type Env struct {
DomainControllerHost *components.RemoteHost
DomainController *RemoteActiveDirectory
FakeIntake *components.FakeIntake
Environment *config.CommonEnvironment
}

// RemoteActiveDirectory represents an Active Directory domain setup on a remote host
type RemoteActiveDirectory struct {
Output
}
112 changes: 112 additions & 0 deletions test/new-e2e/pkg/environments/activedirectory/component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package activedirectory

import (
"github.com/DataDog/datadog-agent/test/new-e2e/tests/windows/common/powershell"
"github.com/DataDog/test-infra-definitions/common"
"github.com/DataDog/test-infra-definitions/common/config"
"github.com/DataDog/test-infra-definitions/common/namer"
infraComponents "github.com/DataDog/test-infra-definitions/components"
"github.com/DataDog/test-infra-definitions/components/command"
"github.com/DataDog/test-infra-definitions/components/remote"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumiverse/pulumi-time/sdk/go/time"
)

// Output is an object that models the output of the resource creation
// from the Component.
// See https://www.pulumi.com/docs/concepts/resources/components/#registering-component-outputs
type Output struct {
infraComponents.JSONImporter
}

// Component is an Active Directory domain component.
// See https://www.pulumi.com/docs/concepts/resources/components/
type Component struct {
pulumi.ResourceState
infraComponents.Component
namer namer.Namer
host *remote.Host
}

// Export registers a key and value pair with the current context's stack.
func (dc *Component) Export(ctx *pulumi.Context, out *Output) error {
return infraComponents.Export(ctx, dc, out)
}

// NewActiveDirectory creates a new instance of an Active Directory domain deployment
func NewActiveDirectory(ctx *pulumi.Context, e *config.CommonEnvironment, host *remote.Host, options ...Option) (*Component, error) {
params, err := common.ApplyOption(&Configuration{
// JL: Should we set sensible defaults here ?
}, options)
if err != nil {
return nil, err
}

domainControllerComp, err := infraComponents.NewComponent(*e, host.Name(), func(comp *Component) error {
comp.namer = e.CommonNamer.WithPrefix(comp.Name())
comp.host = host

installForestCmd, err := host.OS.Runner().Command(comp.namer.ResourceName("install-forest"), &command.Args{
Create: pulumi.String(powershell.PsHost().
AddActiveDirectoryDomainServicesWindowsFeature().
ImportActiveDirectoryDomainServicesModule().
InstallADDSForest(params.DomainName, params.DomainPassword).
Compile()),
// JL: I hesitated to provide a Delete function but Uninstall-ADDSDomainController looks
// non-trivial to call, and I couldn't test it.
}, pulumi.Parent(comp))
if err != nil {
return err
}

timeProvider, err := time.NewProvider(ctx, comp.namer.ResourceName("time-provider"), &time.ProviderArgs{}, pulumi.DeletedWith(host))
if err != nil {
return err
}

waitForRebootCmd, err := time.NewSleep(ctx, comp.namer.ResourceName("wait-for-host-to-reboot"), &time.SleepArgs{
CreateDuration: pulumi.String("30s"),
}, pulumi.Provider(timeProvider), pulumi.DependsOn([]pulumi.Resource{
installForestCmd,
}))
if err != nil {
return err
}

ensureAdwsStartedCmd, err := host.OS.Runner().Command(comp.namer.ResourceName("ensure-adws-started"), &command.Args{
Create: pulumi.String(powershell.PsHost().WaitForServiceStatus("ADWS", "Running").Compile()),
}, pulumi.DependsOn([]pulumi.Resource{
waitForRebootCmd,
}))
if err != nil {
return err
}

if len(params.DomainUsers) > 0 {
cmdHost := powershell.PsHost()
for _, user := range params.DomainUsers {
cmdHost.AddActiveDirectoryUser(user.Username, user.Password)
}
_, err := host.OS.Runner().Command(comp.namer.ResourceName("create-domain-users"), &command.Args{
Create: pulumi.String(cmdHost.Compile()),
}, pulumi.DependsOn([]pulumi.Resource{
ensureAdwsStartedCmd,
}))
if err != nil {
return err
}
}

return nil
}, pulumi.Parent(host), pulumi.DeletedWith(host))
if err != nil {
return nil, err
}

return domainControllerComp, nil
}
56 changes: 56 additions & 0 deletions test/new-e2e/pkg/environments/activedirectory/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package activedirectory

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

// DomainUser represents an Active Directory user
type DomainUser struct {
Username string
Password string
}

// Configuration represents the Active Directory configuration (domain name, password, users etc...)
type Configuration struct {
DomainName string
DomainPassword string
DomainUsers []DomainUser
ResourceOptions []pulumi.ResourceOption
}

// Option is an optional function parameter type for Configuration options
type Option = func(*Configuration) error

// WithDomainName specifies the fully qualified domain name (FQDN) for the root domain in the forest.
func WithDomainName(domainName string) func(*Configuration) error {
return func(p *Configuration) error {
p.DomainName = domainName
return nil
}
}

// WithDomainPassword specifies the password for the administrator account when the computer is started in Safe Mode or
// a variant of Safe Mode, such as Directory Services Restore Mode.
// You must supply a password that meets the password complexity rules of the domain and the password cannot be blank.
func WithDomainPassword(domainPassword string) func(*Configuration) error {
return func(p *Configuration) error {
p.DomainPassword = domainPassword
return nil
}
}

// WithDomainUser adds a user in Active Directory.
func WithDomainUser(username, password string) func(params *Configuration) error {
return func(p *Configuration) error {
p.DomainUsers = append(p.DomainUsers, DomainUser{
Username: username,
Password: password,
})
return nil
}
}
88 changes: 88 additions & 0 deletions test/new-e2e/pkg/environments/activedirectory/provisioner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package activedirectory

import (
"fmt"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e"
"github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/optional"
"github.com/DataDog/test-infra-definitions/components/os"
"github.com/DataDog/test-infra-definitions/resources/aws"
"github.com/DataDog/test-infra-definitions/scenarios/aws/ec2"
"github.com/DataDog/test-infra-definitions/scenarios/aws/fakeintake"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

const (
provisionerBaseID = "aws-ec2vm-active-directory-"
defaultVMName = "dcvm"
)

// GetProvisionerParams return ProvisionerParams from options opts setup
func GetProvisionerParams(opts ...ProvisionerOption) *ProvisionerParams {
params := newProvisionerParams()
err := optional.ApplyOptions(params, opts)
if err != nil {
panic(fmt.Errorf("unable to apply ProvisionerOption, err: %w", err))
}
return params
}

// Run deploys a environment given a pulumi.Context
func Run(ctx *pulumi.Context, env *Env, params *ProvisionerParams) error {
awsEnv, err := aws.NewEnvironment(ctx)
if err != nil {
return err
}

env.Environment = awsEnv.CommonEnvironment

// JL: should the ec2 VM be customizable by the user?
vm, err := ec2.NewVM(awsEnv, params.name, ec2.WithOS(os.WindowsDefault))
if err != nil {
return err
}
err = vm.Export(ctx, &env.DomainControllerHost.HostOutput)
if err != nil {
return err
}

domainController, err := NewActiveDirectory(ctx, awsEnv.CommonEnvironment, vm, params.activeDirectoryOptions...)
if err != nil {
return err
}
err = domainController.Export(ctx, &env.DomainController.Output)
if err != nil {
return err
}

// JL: can params.fakeintakeOptions be nil, and how should we handle it?
fakeIntake, err := fakeintake.NewECSFargateInstance(awsEnv, params.name, params.fakeintakeOptions...)
if err != nil {
return err
}
err = fakeIntake.Export(ctx, &env.FakeIntake.FakeintakeOutput)
if err != nil {
return err
}

return nil
}

// Provisioner creates an Active Directory environment on a given VM.
func Provisioner(opts ...ProvisionerOption) e2e.TypedProvisioner[Env] {
// We need to build params here to be able to use params.name in the provisioner name
params := GetProvisionerParams(opts...)

provisioner := e2e.NewTypedPulumiProvisioner[Env](provisionerBaseID+params.name, func(ctx *pulumi.Context, env *Env) error {
// We ALWAYS need to make a deep copy of `params`, as the provisioner can be called multiple times.
// and it's easy to forget about it, leading to hard to debug issues.
params := GetProvisionerParams(opts...)
return Run(ctx, env, params)
}, nil)

return provisioner
}
Loading

0 comments on commit c41a677

Please sign in to comment.