Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to generate buildkite pipelines from integration testing framework #5391

Merged
merged 32 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e9548d6
Working on buildkite integration runner.
blakerouse Aug 30, 2024
3eadef2
Use correct image for upload pipeline.
blakerouse Aug 31, 2024
bbcd41c
Output pipeline for debug.
blakerouse Aug 31, 2024
d77c35b
Merge branch 'main' into integration-buildkite
blakerouse Sep 10, 2024
07b355b
Use script to perform pipeline work.
blakerouse Sep 10, 2024
9e6d99d
Remove todo code.
blakerouse Sep 10, 2024
ad8e658
Merge remote-tracking branch 'upstream/main' into integration-buildkite
blakerouse Sep 13, 2024
8296324
Fix YAML structure.
blakerouse Sep 13, 2024
9eaecd2
Top-level steps.
blakerouse Sep 16, 2024
f9ae937
Make agents an array.
blakerouse Sep 16, 2024
73b86c2
Try YAML v3.
blakerouse Sep 16, 2024
9e7fc99
Switch to JSON.
blakerouse Sep 16, 2024
50878e6
Add json struct tags.
blakerouse Sep 16, 2024
9ccdb9b
Revert JSON and add debug
pazone Sep 17, 2024
dd735d6
Renamed the yaml file
pazone Sep 17, 2024
b4727c1
Direct pipe upload
pazone Sep 17, 2024
b9cd6bb
Direct pipe upload
pazone Sep 17, 2024
22ed6d4
Direct file output from go
pazone Sep 17, 2024
5c4fd37
Merge remote-tracking branch 'origin/integration-buildkite' into inte…
blakerouse Sep 19, 2024
5d93a99
Cleanups.
blakerouse Sep 20, 2024
cee4744
mage check
blakerouse Sep 20, 2024
53d6f66
Merge remote-tracking branch 'upstream/main' into integration-buildkite
blakerouse Sep 20, 2024
e847415
Cleanup for merge.
blakerouse Sep 20, 2024
f5650f8
Refactor all of pkg/testing.
blakerouse Sep 20, 2024
8a1d146
Merge remote-tracking branch 'upstream/main' into integration-buildkite
blakerouse Sep 20, 2024
73f921a
Rename.
blakerouse Sep 20, 2024
4689f18
Cleanup imports.
blakerouse Sep 20, 2024
9cb959d
Fix bad merge.
blakerouse Sep 20, 2024
d96f51a
Merge remote-tracking branch 'upstream/main' into integration-buildkite
blakerouse Sep 24, 2024
3ce2196
Fix licensing and imports.
blakerouse Sep 24, 2024
26c72ac
Merge branch 'main' into integration-buildkite
blakerouse Sep 27, 2024
40006ae
Merge branch 'main' into integration-buildkite
blakerouse Sep 30, 2024
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: 1 addition & 2 deletions .buildkite/integration.pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ steps:
files: "build/TEST-*.xml"
format: "junit"
branches: "main"
debug: true
debug: true

- label: "Serverless Beats Tests"
depends_on:
Expand All @@ -103,4 +103,3 @@ steps:
- github_commit_status:
context: "buildkite/elastic-agent-extended-testing - Serverless Beats Tests"


86 changes: 77 additions & 9 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/elastic/elastic-agent/pkg/testing/ess"
"github.com/elastic/elastic-agent/pkg/testing/kubernetes/kind"
"github.com/elastic/elastic-agent/pkg/testing/multipass"
"github.com/elastic/elastic-agent/pkg/testing/null"
"github.com/elastic/elastic-agent/pkg/testing/ogc"
"github.com/elastic/elastic-agent/pkg/testing/runner"
"github.com/elastic/elastic-agent/pkg/testing/tools/git"
Expand Down Expand Up @@ -2452,6 +2453,64 @@ func (Integration) TestOnRemote(ctx context.Context) error {
return nil
}

func (Integration) Buildkite() error {
goTestFlags := os.Getenv("GOTEST_FLAGS")
batches, err := define.DetermineBatches("testing/integration", goTestFlags, "integration")
if err != nil {
return fmt.Errorf("failed to determine batches: %w", err)
}
agentVersion, agentStackVersion, err := getTestRunnerVersions()
if err != nil {
return fmt.Errorf("failed to get agent versions: %w", err)
}
goVersion, err := mage.DefaultBeatBuildVariableSources.GetGoVersion()
if err != nil {
return fmt.Errorf("failed to get go versions: %w", err)
}

cfg := runner.Config{
AgentVersion: agentVersion,
StackVersion: agentStackVersion,
GOVersion: goVersion,
BuildDir: filepath.Join("build", "distributions"),
RepoDir: ".",
StateDir: ".integration-cache",
Platforms: testPlatforms(),
Packages: testPackages(),
Groups: testGroups(),
Matrix: false,
VerboseMode: mg.Verbose(),
TestFlags: goTestFlags,
}
r, err := runner.NewRunner(cfg, null.NewInstanceProvisioner(), null.NewStackProvisioner(), batches...)
if err != nil {
return fmt.Errorf("failed to create runner: %w", err)
}

steps, err := r.Buildkite()
Copy link
Member

@pchila pchila Sep 20, 2024

Choose a reason for hiding this comment

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

Why is Buildkite() a method of a runner ?
I understand that there's some convenient code already there but is generating buildkite steps a responsibility of a runner ?
Should an integration test runner know about Buildkite and its step definition?

This looks like a code smell as the runner is probably doing too much
Perhaps the runner logic should be broken up in smaller independent pieces ?

Edit:
What about extracting the batching functionality and dumping the batches in a structured manner so that the buildkite steps can be built from that ?
That should be something that enables the dynamic creation of buildkite steps without introducing tight coupling between integration test runners and buildkite

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer it this way otherwise I would have to expose more internals of the runner for the buildkite runner. Generating buildkite is a unique way of running the testing runner, but either way its still the runner performing the work its just being done through buildkite.

My overall goal of this PR was just to show that it can be done and not something that needed to be hard coded in YAML. The actually work of making this work will be done by @pazone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pazone Okay, I just went ahead and refactored the runner to allow me to pull it out and not create the runner as you requested. Should be good now.

if err != nil {
return fmt.Errorf("error generating buildkite steps: %w", err)
}

// write output to steps.yaml
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error getting current working directory: %w", err)
}
ymlFilePath := filepath.Join(cwd, "steps.yml")
file, err := os.Create(ymlFilePath)
if err != nil {
return fmt.Errorf("error creating file: %w", err)
}
defer file.Close()
if _, err := file.WriteString(steps); err != nil {
return fmt.Errorf("error writing to file: %w", err)
}

fmt.Printf(">>> Generated buildkite steps written to: %s\n", ymlFilePath)
return nil
}

func integRunner(ctx context.Context, matrix bool, singleTest string) error {
if _, ok := ctx.Deadline(); !ok {
// If the context doesn't have a timeout (usually via the mage -t option), give it one.
Expand Down Expand Up @@ -2522,18 +2581,14 @@ func integRunnerOnce(ctx context.Context, matrix bool, singleTest string) (int,
return results.Failures, nil
}

func createTestRunner(matrix bool, singleTest string, goTestFlags string, batches ...define.Batch) (*runner.Runner, error) {
goVersion, err := mage.DefaultBeatBuildVariableSources.GetGoVersion()
if err != nil {
return nil, err
}

func getTestRunnerVersions() (string, string, error) {
var err error
agentStackVersion := os.Getenv("AGENT_STACK_VERSION")
agentVersion := os.Getenv("AGENT_VERSION")
if agentVersion == "" {
agentVersion, err = mage.DefaultBeatBuildVariableSources.GetBeatVersion()
if err != nil {
return nil, err
return "", "", err
}
if agentStackVersion == "" {
// always use snapshot for stack version
Expand All @@ -2549,6 +2604,21 @@ func createTestRunner(matrix bool, singleTest string, goTestFlags string, batche
if agentStackVersion == "" {
agentStackVersion = agentVersion
}

return agentVersion, agentStackVersion, nil
}

func createTestRunner(matrix bool, singleTest string, goTestFlags string, batches ...define.Batch) (*runner.Runner, error) {
goVersion, err := mage.DefaultBeatBuildVariableSources.GetGoVersion()
if err != nil {
return nil, err
}

agentVersion, agentStackVersion, err := getTestRunnerVersions()
if err != nil {
return nil, err
}

agentBuildDir := os.Getenv("AGENT_BUILD_DIR")
if agentBuildDir == "" {
agentBuildDir = filepath.Join("build", "distributions")
Expand Down Expand Up @@ -2604,7 +2674,6 @@ func createTestRunner(matrix bool, singleTest string, goTestFlags string, batche
default:
return nil, fmt.Errorf("INSTANCE_PROVISIONER environment variable must be one of 'ogc' or 'multipass', not %s", instanceProvisionerMode)
}
fmt.Printf(">>>> Using %s instance provisioner\n", instanceProvisionerMode)

email, err := ogcCfg.ClientEmail()
if err != nil {
Expand Down Expand Up @@ -2639,7 +2708,6 @@ func createTestRunner(matrix bool, singleTest string, goTestFlags string, batche
ess.ProvisionerServerless,
stackProvisionerMode)
}
fmt.Printf(">>>> Using %s stack provisioner\n", stackProvisionerMode)

timestamp := timestampEnabled()

Expand Down
25 changes: 25 additions & 0 deletions pkg/testing/buildkite/steps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package buildkite

type StepAgent struct {
Provider string `json:"provider,omitempty" yaml:"provider,omitempty"`
ImageProject string `json:"imageProject,omitempty" yaml:"imageProject,omitempty"`
MachineType string `json:"machineType,omitempty" yaml:"machineType,omitempty"`
Image string `json:"image,omitempty" yaml:"image,omitempty"`
}

type Step struct {
Key string `json:"key,omitempty" yaml:"key,omitempty"`
Label string `json:"label,omitempty" yaml:"label,omitempty"`
Command string `json:"command,omitempty" yaml:"command,omitempty"`
Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"`
ArtifactPaths []string `json:"artifact_paths,omitempty" yaml:"artifact_paths,omitempty"`
Agents []StepAgent `json:"agents,omitempty" yaml:"agents,omitempty"`
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on,omitempty"`
AllowDependencyFailure bool `json:"allow_dependency_failure,omitempty" yaml:"allow_dependency_failure,omitempty"`
Steps []Step `json:"steps,omitempty" yaml:"steps,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
51 changes: 51 additions & 0 deletions pkg/testing/null/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package null

import (
"context"
"fmt"

"github.com/elastic/elastic-agent/pkg/testing/define"
"github.com/elastic/elastic-agent/pkg/testing/runner"
)

const (
Name = "null"
)

type instanceProvisioner struct {
logger runner.Logger
}

// NewInstanceProvisioner creates the null provisioner
func NewInstanceProvisioner() runner.InstanceProvisioner {
return &instanceProvisioner{}
}

func (p *instanceProvisioner) Name() string {
return Name
}

func (p *instanceProvisioner) SetLogger(l runner.Logger) {
p.logger = l
}

func (p *instanceProvisioner) Type() runner.ProvisionerType {
return runner.ProvisionerTypeVM
}

func (p *instanceProvisioner) Supported(os define.OS) bool {
return true
}

func (p *instanceProvisioner) Provision(ctx context.Context, cfg runner.Config, batches []runner.OSBatch) ([]runner.Instance, error) {
return nil, fmt.Errorf("null provisioner cannot provision")
}

func (p *instanceProvisioner) Clean(ctx context.Context, _ runner.Config, instances []runner.Instance) error {
// nothing to clean
return nil
}
43 changes: 43 additions & 0 deletions pkg/testing/null/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package null

import (
"context"
"fmt"

"github.com/elastic/elastic-agent/pkg/testing/runner"
)

type stackProvisioner struct {
log runner.Logger
}

// NewStackProvisioner creates a new null stack provisioner
func NewStackProvisioner() runner.StackProvisioner {
return &stackProvisioner{}
}

func (prov *stackProvisioner) Name() string {
return Name
}

func (prov *stackProvisioner) SetLogger(l runner.Logger) {
prov.log = l
}

func (prov *stackProvisioner) Create(ctx context.Context, request runner.StackRequest) (runner.Stack, error) {
return runner.Stack{}, fmt.Errorf("null provisioner cannot provision")
}

func (prov *stackProvisioner) WaitForReady(ctx context.Context, stack runner.Stack) (runner.Stack, error) {
// nothing to wait for
return runner.Stack{}, nil
}

func (prov *stackProvisioner) Delete(ctx context.Context, stack runner.Stack) error {
// nothing to delete
return nil
}
Loading