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 ADO pipeline preview run feature and test #9462

Merged
merged 29 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c63528c
Add sign-only mode.
dekiel Aug 29, 2023
16e5bd8
POC of image building in ADO.
dekiel Sep 28, 2023
d3e9b47
go mod tidy
dekiel Sep 28, 2023
cb2f7a2
Use default build config.
dekiel Nov 15, 2023
c200c1d
POC of image building in ADO.
dekiel Sep 28, 2023
b5b9cd8
Use default build config.
dekiel Nov 15, 2023
f84740f
fix linter errors
dekiel Nov 15, 2023
4b65cbe
rendertemplates
dekiel Nov 15, 2023
b3ea48d
Move ado code to separate pacakge and make it testable.
dekiel Nov 21, 2023
77d242a
Move build in ado code to separate package. This can be tested indepe…
dekiel Nov 23, 2023
c7ce0a9
Tests.
dekiel Nov 29, 2023
5b9201e
go mod tidy
dekiel Nov 29, 2023
d0cbeb8
Added building image-builder image to the prowjob.
dekiel Nov 29, 2023
9552a0c
Use go buildpack. Chainguard offers free access to the latest tag only.
dekiel Nov 30, 2023
b426632
Moved Dockerfile back to original location. images directory is not s…
dekiel Nov 30, 2023
b85baaf
Unexport options fields.
dekiel Dec 1, 2023
3555772
Remove testing prowjob.
dekiel Dec 4, 2023
fbc83fb
Add preview run flag.
dekiel Dec 4, 2023
32ae425
Add ADO pipeline preview run feature and test
dekiel Dec 4, 2023
de2bb8a
package not used in image-builder
dekiel Dec 5, 2023
e5dc1c2
File committed by mistake.
dekiel Dec 5, 2023
2eaba3b
go mod tidy
dekiel Dec 5, 2023
2c26d41
Pull number should be expect only for presubmit job types.
dekiel Dec 5, 2023
b85ab4c
Print exit code in new line.
dekiel Dec 5, 2023
aeab1a6
Align with naming standard.
dekiel Dec 6, 2023
f9a1ec0
Add error handling for nil final yaml in ADO pipeline preview run
dekiel Dec 6, 2023
a4dfb4e
Add preview run option for Azure DevOps pipelines
dekiel Dec 7, 2023
cdc5468
Review comments.
dekiel Dec 7, 2023
ef98fc8
Added missing negation.
dekiel Dec 7, 2023
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
64 changes: 52 additions & 12 deletions cmd/image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ type options struct {
platforms sets.Strings
exportTags bool
// signOnly only sign images. No build will be performed.
signOnly bool
imagesToSign sets.Strings
buildInADO bool
parseTagsOnly bool
signOnly bool
imagesToSign sets.Strings
buildInADO bool
adoPreviewRun bool
adoPreviewRunYamlPath string
parseTagsOnly bool
}

const (
Expand Down Expand Up @@ -201,10 +203,12 @@ func prepareADOTemplateParameters(options options) (adopipelines.OCIImageBuilder
}

number, present := os.LookupEnv("PULL_NUMBER")
if !present {
if jobType == "presubmit" && !present {
dekiel marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("PULL_NUMBER environment variable is not set, please set it to valid pull request number")
}
templateParameters.SetPullNumber(number)
if present {
templateParameters.SetPullNumber(number)
}

baseSHA, present := os.LookupEnv("PULL_BASE_SHA")
if !present {
Expand Down Expand Up @@ -254,22 +258,43 @@ func buildInADO(o options) error {
if err != nil {
return fmt.Errorf("build in ADO failed, failed preparing ADO template parameters, err: %s", err)
}
fmt.Printf("Using TemplateParameters: %+v\n", templateParameters)

fmt.Println("Running ADO pipeline.")
adoClient := adopipelines.NewClient(o.AdoConfig.ADOOrganizationURL, adoPAT)

var opts []adopipelines.RunPipelineArgsOptions
if o.adoPreviewRun {
fmt.Println("Running in preview mode.")
opts = append(opts, adopipelines.PipelinePreviewRun(o.adoPreviewRunYamlPath))
}

fmt.Println("Preparing ADO pipeline run arguments.")
runPipelineArgs, err := adopipelines.NewRunPipelineArgs(templateParameters, o.AdoConfig.GetADOConfig(), opts...)
if err != nil {
return fmt.Errorf("build in ADO failed, failed creating ADO pipeline run args, err: %s", err)
}

fmt.Println("Triggering ADO build pipeline")
ctx := context.Background()
pipelineRun, err := adopipelines.Run(ctx, adoClient, templateParameters, o.AdoConfig.GetADOConfig())
pipelineRun, err := adoClient.RunPipeline(ctx, runPipelineArgs)
if err != nil {
return fmt.Errorf("build in ADO failed, failed running ADO pipeline, err: %s", err)
}

if o.adoPreviewRun {
if pipelineRun.FinalYaml != nil {
fmt.Printf("ADO pipeline preview run final yaml\n: %s", *pipelineRun.FinalYaml)
} else {
fmt.Println("ADO pipeline preview run final yaml is empty")
}
return nil
}

pipelineRunResult, err := adopipelines.GetRunResult(ctx, adoClient, o.AdoConfig.GetADOConfig(), pipelineRun.Id, 30*time.Second)
if err != nil {
return fmt.Errorf("build in ADO failed, failed getting ADO pipeline run result, err: %s", err)
}
fmt.Printf("ADO pipeline run finished with status: %s", *pipelineRunResult)
fmt.Printf("ADO pipeline run finished with status: %s\n", *pipelineRunResult)

fmt.Println("Getting ADO pipeline run logs.")
adoBuildClient, err := adopipelines.NewBuildClient(o.AdoConfig.ADOOrganizationURL, adoPAT)
Expand Down Expand Up @@ -565,13 +590,25 @@ func validateOptions(o options) error {
}

if o.envFile != "" && o.buildInADO {
errs = append(errs, fmt.Errorf("envFile flag is not supported when running in ADO"))
errs = append(errs, fmt.Errorf("env-file flag is not supported when running in ADO"))
}

if o.variant != "" && o.buildInADO {
errs = append(errs, fmt.Errorf("variant flag is not supported when running in ADO"))
}

if o.adoPreviewRun && !o.buildInADO {
errs = append(errs, fmt.Errorf("ado-preview-run flag is not supported when running locally"))
}

if o.adoPreviewRun && o.adoPreviewRunYamlPath == "" {
errs = append(errs, fmt.Errorf("ado-preview-run-yaml-path flag is missing, please provide path to yaml file with ADO pipeline definition"))
}

if o.adoPreviewRunYamlPath != "" && !o.adoPreviewRun {
errs = append(errs, fmt.Errorf("ado-preview-run-yaml-path flag is provided, but adoPreviewRun flag is not set to true"))
}

return errutil.NewAggregate(errs)
}

Expand Down Expand Up @@ -643,6 +680,8 @@ func (o *options) gatherOptions(flagSet *flag.FlagSet) *flag.FlagSet {
flagSet.BoolVar(&o.signOnly, "sign-only", false, "Only sign the image, do not build it")
flagSet.Var(&o.imagesToSign, "images-to-sign", "Comma-separated list of images to sign. Only used when sign-only flag is set")
flagSet.BoolVar(&o.buildInADO, "build-in-ado", false, "Build in Azure DevOps pipeline environment")
flagSet.BoolVar(&o.adoPreviewRun, "ado-preview-run", false, "Trigger ADO pipeline in preview mode")
flagSet.StringVar(&o.adoPreviewRunYamlPath, "ado-preview-run-yaml-path", "", "Path to yaml file with ADO pipeline definition to be used in preview mode")
flagSet.BoolVar(&o.parseTagsOnly, "parse-tags-only", false, "Only parse tags and print them to stdout")

return flagSet
Expand Down Expand Up @@ -715,15 +754,16 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("%s", jsonTags)
fmt.Printf("%s\n", jsonTags)
os.Exit(0)
}
if o.buildInADO {
err = buildInADO(o)
if err != nil {
fmt.Printf("Image build failed with error: %s", err)
fmt.Printf("Image build failed with error: %s\n", err)
os.Exit(1)
}
os.Exit(0)
}
err = buildLocally(o)
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions cmd/image-builder/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,62 @@ type mockSigner struct {
func (m *mockSigner) Sign(images []string) error {
return m.signFunc(images)
}

// TODO: add tests for functions related to execution in ado.
// Test copied from pkg/azuredevops/pipelines/pipelines_test.go, rewrite to run it here.
// Describe("Run", func() {
// var (
// templateParams map[string]string
// runPipelineArgs adoPipelines.RunPipelineArgs
// )
//
// BeforeEach(func() {
// templateParams = map[string]string{"param1": "value1", "param2": "value2"}
// runPipelineArgs = adoPipelines.RunPipelineArgs{
// Project: &adoConfig.ADOProjectName,
// PipelineId: &adoConfig.ADOPipelineID,
// RunParameters: &adoPipelines.RunPipelineParameters{
// PreviewRun: ptr.To(false),
// TemplateParameters: &templateParams,
// },
// PipelineVersion: &adoConfig.ADOPipelineVersion,
// }
// })
//
// It("should run the pipeline", func() {
// mockRun := &adoPipelines.Run{Id: ptr.To(123)}
// mockADOClient.On("RunPipeline", ctx, runPipelineArgs).Return(mockRun, nil)
//
// run, err := pipelines.Run(ctx, mockADOClient, templateParams, adoConfig)
// Expect(err).ToNot(HaveOccurred())
// Expect(run.Id).To(Equal(ptr.To(123)))
// mockADOClient.AssertCalled(t, "RunPipeline", ctx, runPipelineArgs)
// mockADOClient.AssertNumberOfCalls(t, "RunPipeline", 1)
// mockADOClient.AssertExpectations(GinkgoT())
// })
//
// It("should handle ADO client error", func() {
// mockADOClient.On("RunPipeline", ctx, runPipelineArgs).Return(nil, fmt.Errorf("ADO client error"))
//
// _, err := pipelines.Run(ctx, mockADOClient, templateParams, adoConfig)
// Expect(err).To(HaveOccurred())
// mockADOClient.AssertCalled(t, "RunPipeline", ctx, runPipelineArgs)
// mockADOClient.AssertNumberOfCalls(t, "RunPipeline", 1)
// mockADOClient.AssertExpectations(GinkgoT())
// })
//
// It("should run the pipeline in preview mode", func() {
// finalYaml := "pipeline:\n stages:\n - stage: Build\n jobs:\n - job: Build\n steps:\n - script: echo Hello, world!\n displayName: 'Run a one-line script'"
// runPipelineArgs.RunParameters.PreviewRun = ptr.To(true)
// mockRun := &adoPipelines.Run{Id: ptr.To(123), FinalYaml: &finalYaml}
// mockADOClient.On("RunPipeline", ctx, runPipelineArgs).Return(mockRun, nil)
//
// run, err := pipelines.Run(ctx, mockADOClient, templateParams, adoConfig, pipelines.PipelinePreviewRun)
// Expect(err).ToNot(HaveOccurred())
// Expect(run.Id).To(Equal(ptr.To(123)))
// Expect(run.FinalYaml).To(Equal(&finalYaml))
// mockADOClient.AssertCalled(t, "RunPipeline", ctx, runPipelineArgs)
// mockADOClient.AssertNumberOfCalls(t, "RunPipeline", 1)
// mockADOClient.AssertExpectations(GinkgoT())
// })
// })
28 changes: 25 additions & 3 deletions pkg/azuredevops/pipelines/pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"time"

adov7 "github.com/microsoft/azure-devops-go-api/azuredevops/v7"
Expand Down Expand Up @@ -109,7 +110,7 @@ func GetRunLogs(ctx context.Context, buildClient BuildClient, httpClient HTTPCli
return string(body), nil
}

func Run(ctx context.Context, adoClient Client, templateParameters map[string]string, adoConfig Config) (*pipelines.Run, error) {
func NewRunPipelineArgs(templateParameters map[string]string, adoConfig Config, pipelineRunArgs ...RunPipelineArgsOptions) (pipelines.RunPipelineArgs, error) {
adoRunPipelineArgs := pipelines.RunPipelineArgs{
Project: &adoConfig.ADOProjectName,
PipelineId: &adoConfig.ADOPipelineID,
Expand All @@ -121,7 +122,28 @@ func Run(ctx context.Context, adoClient Client, templateParameters map[string]st
if adoConfig.ADOPipelineVersion != 0 {
adoRunPipelineArgs.PipelineVersion = &adoConfig.ADOPipelineVersion
}
for _, arg := range pipelineRunArgs {
err := arg(&adoRunPipelineArgs)
if err != nil {
return pipelines.RunPipelineArgs{}, fmt.Errorf("failed setting pipeline run args, err: %w", err)
}
}
// TODO: use structured logging with debug severity
fmt.Printf("Using TemplateParameters: %+v\n", adoRunPipelineArgs.RunParameters.TemplateParameters)
return adoClient.RunPipeline(ctx, adoRunPipelineArgs)
// fmt.Printf("Using TemplateParameters: %+v\n", adoRunPipelineArgs.RunParameters.TemplateParameters)
return adoRunPipelineArgs, nil
}

type RunPipelineArgsOptions func(*pipelines.RunPipelineArgs) error

func PipelinePreviewRun(overrideYamlPath string) func(args *pipelines.RunPipelineArgs) error {
return func(args *pipelines.RunPipelineArgs) error {
args.RunParameters.PreviewRun = ptr.To(true)
data, err := os.ReadFile(overrideYamlPath)
if err != nil {
return fmt.Errorf("failed reading override yaml file, err: %w", err)
}
overrideYaml := string(data)
args.RunParameters.YamlOverride = &overrideYaml
return nil
}
}
Loading