1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

cnbBuild step MVP (#3021)

* Implement cnbBuild step

Co-authored-by: Benjamin Haegenlaeuer <benjamin.haegenlaeuer@sap.com>

* Add cnbBuild groovy test

Co-authored-by: Benjamin Haegenlaeuer <benjamin.haegenlaeuer@sap.com>

* Add basic documentation template

Co-authored-by: Philipp Stehle <philipp.stehle@sap.com>

* Support specifiying name, tag and registry

Co-authored-by: Pavel Busko <pbusko@users.noreply.github.com>

Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
Co-authored-by: Philipp Stehle <philipp.stehle@sap.com>
Co-authored-by: Pavel Busko <pbusko@users.noreply.github.com>
This commit is contained in:
Haegi
2021-08-18 12:10:55 +02:00
committed by GitHub
parent d8d5f91bac
commit 3f4b32f7ba
14 changed files with 738 additions and 0 deletions

187
cmd/cnbBuild.go Normal file
View File

@@ -0,0 +1,187 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/docker"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/docker/cli/cli/config/configfile"
"github.com/pkg/errors"
)
type cnbBuildUtils interface {
command.ExecRunner
FileExists(filename string) (bool, error)
FileRead(path string) ([]byte, error)
FileWrite(path string, content []byte, perm os.FileMode) error
MkdirAll(path string, perm os.FileMode) error
Getwd() (string, error)
Glob(pattern string) (matches []string, err error)
Copy(src, dest string) (int64, error)
}
type cnbBuildUtilsBundle struct {
*command.Command
*piperutils.Files
}
func newCnbBuildUtils() cnbBuildUtils {
utils := cnbBuildUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
}
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func cnbBuild(config cnbBuildOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment) {
utils := newCnbBuildUtils()
err := runCnbBuild(&config, telemetryData, utils, commonPipelineEnvironment)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func isIgnored(find string) bool {
return strings.HasSuffix(find, "piper") || strings.Contains(find, ".pipeline")
}
func isDir(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
return false, err
}
return info.IsDir(), nil
}
func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, utils cnbBuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment) error {
var err error
dockerConfig := &configfile.ConfigFile{}
dockerConfigJSON := []byte(`{"auths":{}}`)
if len(config.DockerConfigJSON) > 0 {
dockerConfigJSON, err = utils.FileRead(config.DockerConfigJSON)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "failed to read DockerConfigJSON file '%v'", config.DockerConfigJSON)
}
}
err = json.Unmarshal(dockerConfigJSON, dockerConfig)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "failed to parse DockerConfigJSON file '%v'", config.DockerConfigJSON)
}
auth := map[string]string{}
for registry, value := range dockerConfig.AuthConfigs {
auth[registry] = fmt.Sprintf("Basic %s", value.Auth)
}
cnbRegistryAuth, err := json.Marshal(auth)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "failed to marshal DockerConfigJSON")
}
target := "/workspace"
source, err := utils.Getwd()
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "failed to get current working directory")
}
if len(config.Path) > 0 {
source = config.Path
}
sourceFiles, _ := utils.Glob(path.Join(source, "**"))
for _, sourceFile := range sourceFiles {
if !isIgnored(sourceFile) {
target := path.Join(target, strings.ReplaceAll(sourceFile, source, ""))
dir, err := isDir(sourceFile)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "Checking file info '%s' failed", target)
}
if dir {
err = utils.MkdirAll(target, os.ModePerm)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "Creating directory '%s' failed", target)
}
} else {
log.Entry().Debugf("Copying '%s' to '%s'", sourceFile, target)
_, err = utils.Copy(sourceFile, target)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "Copying '%s' to '%s' failed", sourceFile, target)
}
}
} else {
log.Entry().Debugf("Filterd out '%s'", sourceFile)
}
}
var containerImage string
var containerImageTag string
if len(config.ContainerRegistryURL) > 0 && len(config.ContainerImageName) > 0 && len(config.ContainerImageTag) > 0 {
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "failed to read registry url %s", config.ContainerRegistryURL)
}
containerImage = fmt.Sprintf("%s/%s", containerRegistry, config.ContainerImageName)
containerImageTag = strings.ReplaceAll(config.ContainerImageTag, "+", "-")
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
commonPipelineEnvironment.container.imageNameTag = containerImage
} else if len(config.ContainerImage) > 0 {
containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "invalid registry part in image %s", config.ContainerImage)
}
containerImage = config.ContainerImage
// errors are already caught with previous call to docker.ContainerRegistryFromImage
containerImageTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage)
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%s", containerRegistry)
commonPipelineEnvironment.container.imageNameTag = containerImageTag
} else {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New("either containerImage or containerImageName and containerImageTag must be present")
}
err = utils.RunExecutable("/cnb/lifecycle/detector")
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "execution of '/cnb/lifecycle/detector' failed")
}
err = utils.RunExecutable("/cnb/lifecycle/builder")
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "execution of '/cnb/lifecycle/builder' failed")
}
utils.AppendEnv([]string{fmt.Sprintf("CNB_REGISTRY_AUTH=%s", string(cnbRegistryAuth))})
err = utils.RunExecutable("/cnb/lifecycle/exporter", fmt.Sprintf("%s:%s", containerImage, containerImageTag), fmt.Sprintf("%s:latest", containerImage))
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "execution of '/cnb/lifecycle/exporter' failed")
}
return nil
}

260
cmd/cnbBuild_generated.go Normal file
View File

@@ -0,0 +1,260 @@
// Code generated by piper's step-generator. DO NOT EDIT.
package cmd
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/SAP/jenkins-library/pkg/splunk"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
type cnbBuildOptions struct {
ContainerImage string `json:"containerImage,omitempty"`
ContainerImageName string `json:"containerImageName,omitempty"`
ContainerImageTag string `json:"containerImageTag,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
Path string `json:"path,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
}
type cnbBuildCommonPipelineEnvironment struct {
container struct {
registryURL string
imageNameTag string
}
}
func (p *cnbBuildCommonPipelineEnvironment) persist(path, resourceName string) {
content := []struct {
category string
name string
value interface{}
}{
{category: "container", name: "registryUrl", value: p.container.registryURL},
{category: "container", name: "imageNameTag", value: p.container.imageNameTag},
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
log.Entry().Fatal("failed to persist Piper environment")
}
}
// CnbBuildCommand Executes a Cloud Native Buildpacks build for creating a Docker container.
func CnbBuildCommand() *cobra.Command {
const STEP_NAME = "cnbBuild"
metadata := cnbBuildMetadata()
var stepConfig cnbBuildOptions
var startTime time.Time
var commonPipelineEnvironment cnbBuildCommonPipelineEnvironment
var logCollector *log.CollectorHook
var createCnbBuildCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Executes a Cloud Native Buildpacks build for creating a Docker container.",
Long: `Executes a Cloud Native Buildpacks build for creating a Docker container.`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
startTime = time.Now()
log.SetStepName(STEP_NAME)
log.SetVerbose(GeneralConfig.Verbose)
GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
path, _ := os.Getwd()
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
log.RegisterHook(fatalHook)
err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
log.RegisterSecret(stepConfig.DockerConfigJSON)
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
log.RegisterHook(&sentryHook)
}
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID}
log.RegisterHook(logCollector)
}
return nil
},
Run: func(_ *cobra.Command, _ []string) {
telemetryData := telemetry.CustomData{}
telemetryData.ErrorCode = "1"
handler := func() {
config.RemoveVaultSecretFiles()
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetryData.ErrorCategory = log.GetErrorCategory().String()
telemetry.Send(&telemetryData)
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
splunk.Send(&telemetryData, logCollector)
}
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
splunk.Initialize(GeneralConfig.CorrelationID,
GeneralConfig.HookConfig.SplunkConfig.Dsn,
GeneralConfig.HookConfig.SplunkConfig.Token,
GeneralConfig.HookConfig.SplunkConfig.Index,
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
}
cnbBuild(stepConfig, &telemetryData, &commonPipelineEnvironment)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addCnbBuildFlags(createCnbBuildCmd, &stepConfig)
return createCnbBuildCmd
}
func addCnbBuildFlags(cmd *cobra.Command, stepConfig *cnbBuildOptions) {
cmd.Flags().StringVar(&stepConfig.ContainerImage, "containerImage", os.Getenv("PIPER_containerImage"), "Defines the full name of the Docker image to be created including registry, image name and tag like `my.docker.registry/path/myImageName:myTag`. If left empty, image will not be pushed.")
cmd.Flags().StringVar(&stepConfig.ContainerImageName, "containerImageName", os.Getenv("PIPER_containerImageName"), "Name of the container which will be built - will be used instead of parameter `containerImage`")
cmd.Flags().StringVar(&stepConfig.ContainerImageTag, "containerImageTag", os.Getenv("PIPER_containerImageTag"), "Tag of the container which will be built - will be used instead of parameter `containerImage`")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "http(s) url of the Container registry where the image should be pushed to - will be used instead of parameter `containerImage`")
cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "The path should either point to your sources or an artifact build before.")
cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).")
cmd.MarkFlagRequired("dockerConfigJSON")
}
// retrieve step metadata
func cnbBuildMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "cnbBuild",
Aliases: []config.Alias{},
Description: "Executes a Cloud Native Buildpacks build for creating a Docker container.",
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Secrets: []config.StepSecrets{
{Name: "dockerConfigJsonCredentialsId", Description: "Jenkins 'Secret file' credentials ID containing Docker config.json (with registry credential(s)). You can create it like explained in the Docker Success Center in the article about [how to generate a new auth in the config.json file](https://success.docker.com/article/generate-new-auth-in-config-json-file).", Type: "jenkins"},
},
Parameters: []config.StepParameters{
{
Name: "containerImage",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_containerImage"),
},
{
Name: "containerImageName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "dockerImageName"}},
Default: os.Getenv("PIPER_containerImageName"),
},
{
Name: "containerImageTag",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "artifactVersion",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "artifactVersion"}},
Default: os.Getenv("PIPER_containerImageTag"),
},
{
Name: "containerRegistryUrl",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "container/registryUrl",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "dockerRegistryUrl"}},
Default: os.Getenv("PIPER_containerRegistryUrl"),
},
{
Name: "path",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_path"),
},
{
Name: "dockerConfigJSON",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/dockerConfigJSON",
},
{
Name: "dockerConfigJsonCredentialsId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/docker-config", "$(vaultBasePath)/$(vaultPipelineName)/docker-config", "$(vaultBasePath)/GROUP-SECRETS/docker-config"},
Type: "vaultSecretFile",
},
},
Scope: []string{"PARAMETERS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_dockerConfigJSON"),
},
},
},
Containers: []config.Container{
{Image: "paketobuildpacks/builder:full"},
},
Outputs: config.StepOutputs{
Resources: []config.StepResources{
{
Name: "commonPipelineEnvironment",
Type: "piperEnvironment",
Parameters: []map[string]interface{}{
{"Name": "container/registryUrl"},
{"Name": "container/imageNameTag"},
},
},
},
},
},
}
return theMetaData
}

View File

@@ -0,0 +1,17 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCnbBuildCommand(t *testing.T) {
t.Parallel()
testCmd := CnbBuildCommand()
// only high level testing performed - details are tested in step generation procedure
assert.Equal(t, "cnbBuild", testCmd.Use, "command name incorrect")
}

79
cmd/cnbBuild_test.go Normal file
View File

@@ -0,0 +1,79 @@
package cmd
import (
"fmt"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/stretchr/testify/assert"
)
type cnbBuildMockUtils struct {
*mock.ExecMockRunner
*mock.FilesMock
}
func newCnbBuildTestsUtils() cnbBuildMockUtils {
utils := cnbBuildMockUtils{
ExecMockRunner: &mock.ExecMockRunner{},
FilesMock: &mock.FilesMock{},
}
return utils
}
func TestRunCnbBuild(t *testing.T) {
t.Parallel()
commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
t.Run("success case", func(t *testing.T) {
t.Parallel()
registy := "some-registry"
config := cnbBuildOptions{
ContainerImageName: "my-image",
ContainerImageTag: "0.0.1",
ContainerRegistryURL: fmt.Sprintf("https://%s", registy),
DockerConfigJSON: "/path/to/config.json",
}
utils := newCnbBuildTestsUtils()
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
err := runCnbBuild(&config, &telemetry.CustomData{}, utils, &commonPipelineEnvironment)
assert.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assert.Equal(t, "/cnb/lifecycle/detector", runner.Calls[0].Exec)
assert.Equal(t, "/cnb/lifecycle/builder", runner.Calls[1].Exec)
assert.Equal(t, "/cnb/lifecycle/exporter", runner.Calls[2].Exec)
assert.Equal(t, []string{fmt.Sprintf("%s/%s:%s", registy, config.ContainerImageName, config.ContainerImageTag), fmt.Sprintf("%s/%s:latest", registy, config.ContainerImageName)}, runner.Calls[2].Params)
})
t.Run("error case: Invalid DockerConfigJSON file", func(t *testing.T) {
t.Parallel()
config := cnbBuildOptions{
ContainerImage: "my-image",
DockerConfigJSON: "/path/to/config.json",
}
utils := newCnbBuildTestsUtils()
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":"dXNlcjpwYXNz"}}`))
err := runCnbBuild(&config, nil, utils, &commonPipelineEnvironment)
assert.EqualError(t, err, "failed to parse DockerConfigJSON file '/path/to/config.json': json: cannot unmarshal string into Go struct field ConfigFile.auths of type types.AuthConfig")
})
t.Run("error case: DockerConfigJSON file not there", func(t *testing.T) {
t.Parallel()
config := cnbBuildOptions{
ContainerImage: "my-image",
DockerConfigJSON: "not-there",
}
utils := newCnbBuildTestsUtils()
err := runCnbBuild(&config, nil, utils, &commonPipelineEnvironment)
assert.EqualError(t, err, "failed to read DockerConfigJSON file 'not-there': could not read 'not-there'")
})
}

View File

@@ -30,6 +30,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"cloudFoundryDeleteService": cloudFoundryDeleteServiceMetadata(),
"cloudFoundryDeleteSpace": cloudFoundryDeleteSpaceMetadata(),
"cloudFoundryDeploy": cloudFoundryDeployMetadata(),
"cnbBuild": cnbBuildMetadata(),
"containerExecuteStructureTests": containerExecuteStructureTestsMetadata(),
"detectExecuteScan": detectExecuteScanMetadata(),
"fortifyExecuteScan": fortifyExecuteScanMetadata(),

View File

@@ -126,6 +126,7 @@ func Execute() {
rootCmd.AddCommand(GctsCloneRepositoryCommand())
rootCmd.AddCommand(JsonApplyPatchCommand())
rootCmd.AddCommand(KanikoExecuteCommand())
rootCmd.AddCommand(CnbBuildCommand())
rootCmd.AddCommand(AbapEnvironmentAssemblePackagesCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckCVsCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckPVCommand())

View File

@@ -0,0 +1,9 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}

View File

@@ -75,6 +75,7 @@ nav:
- cloudFoundryCreateServiceKey: steps/cloudFoundryCreateServiceKey.md
- cloudFoundryDeleteService: steps/cloudFoundryDeleteService.md
- cloudFoundryDeploy: steps/cloudFoundryDeploy.md
- cnbBuild: steps/cnbBuild.md
- commonPipelineEnvironment: steps/commonPipelineEnvironment.md
- containerExecuteStructureTests: steps/containerExecuteStructureTests.md
- containerPushToRegistry: steps/containerPushToRegistry.md

1
go.mod
View File

@@ -12,6 +12,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/bmatcuk/doublestar v1.3.2
github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017
github.com/elliotchance/orderedmap v1.3.0
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/getsentry/sentry-go v0.7.0

View File

@@ -0,0 +1,24 @@
// +build integration
// can be execute with go test -tags=integration ./integration/...
package main
import (
"testing"
)
func TestNpmProject(t *testing.T) {
t.Parallel()
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:full",
User: "cnb",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
})
container.whenRunningPiperCommand("cnbBuild", "--containerImage", "not-found")
container.assertHasOutput(t, "running command: /cnb/lifecycle/detector")
container.assertHasOutput(t, "Paketo NPM Start Buildpack")
container.assertHasOutput(t, "Saving not-found")
container.assertHasOutput(t, "failed to write image to the following tags: [not-found:")
}

View File

@@ -0,0 +1,87 @@
metadata:
name: cnbBuild
description: Executes a Cloud Native Buildpacks build for creating a Docker container.
longDescription: Executes a Cloud Native Buildpacks build for creating a Docker container.
spec:
inputs:
secrets:
- name: dockerConfigJsonCredentialsId
description: Jenkins 'Secret file' credentials ID containing Docker config.json (with registry credential(s)). You can create it like explained in the Docker Success Center in the article about [how to generate a new auth in the config.json file](https://success.docker.com/article/generate-new-auth-in-config-json-file).
type: jenkins
params:
- name: containerImage
type: string
description: Defines the full name of the Docker image to be created including registry, image name and tag like `my.docker.registry/path/myImageName:myTag`. If left empty, image will not be pushed.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: containerImageName
aliases:
- name: dockerImageName
type: string
description: Name of the container which will be built - will be used instead of parameter `containerImage`
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
- name: containerImageTag
aliases:
- name: artifactVersion
type: string
description: Tag of the container which will be built - will be used instead of parameter `containerImage`
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
resourceRef:
- name: commonPipelineEnvironment
param: artifactVersion
- name: containerRegistryUrl
aliases:
- name: dockerRegistryUrl
type: string
description: http(s) url of the Container registry where the image should be pushed to - will be used instead of parameter `containerImage`
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
resourceRef:
- name: commonPipelineEnvironment
param: container/registryUrl
- name: path
type: string
description: The path should either point to your sources or an artifact build before.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: dockerConfigJSON
type: string
description: Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).
scope:
- PARAMETERS
secret: true
mandatory: true
resourceRef:
- name: commonPipelineEnvironment
param: custom/dockerConfigJSON
- name: dockerConfigJsonCredentialsId
type: secret
- type: vaultSecretFile
paths:
- $(vaultPath)/docker-config
- $(vaultBasePath)/$(vaultPipelineName)/docker-config
- $(vaultBasePath)/GROUP-SECRETS/docker-config
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: container/registryUrl
- name: container/imageNameTag
containers:
- image: "paketobuildpacks/builder:full"

View File

@@ -0,0 +1,61 @@
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
import static org.junit.Assert.assertThat
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.hasEntry
import static org.hamcrest.Matchers.allOf
public class CnbBuildTest extends BasePiperTest {
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsReadYamlRule readYamlRule = new JenkinsReadYamlRule(this)
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(stepRule)
.around(readYamlRule)
@Test
void testCallGoWrapper() {
def calledWithParameters,
calledWithStepName,
calledWithMetadata,
calledWithCredentials
helper.registerAllowedMethod(
'piperExecuteBin',
[Map, String, String, List],
{
params, stepName, metaData, creds ->
calledWithParameters = params
calledWithStepName = stepName
calledWithMetadata = metaData
calledWithCredentials = creds
}
)
stepRule.step.cnbBuild(script: nullScript, containerImage: 'foo:bar', dockerConfigJsonCredentialsId: 'DOCKER_CREDENTIALS')
assertThat(calledWithParameters.size(), is(3))
assertThat(calledWithParameters.script, is(nullScript))
assertThat(calledWithParameters.containerImage, is('foo:bar'))
assertThat(calledWithParameters.dockerConfigJsonCredentialsId, is('DOCKER_CREDENTIALS'))
assertThat(calledWithStepName, is('cnbBuild'))
assertThat(calledWithMetadata, is('metadata/cnbBuild.yaml'))
assertThat(calledWithCredentials.size(), is(1))
assertThat(calledWithCredentials[0].size(), is(3))
assertThat(calledWithCredentials[0], allOf(hasEntry('type','file'), hasEntry('id','dockerConfigJsonCredentialsId'), hasEntry('env',['PIPER_dockerConfigJSON'])))
}
}

View File

@@ -127,6 +127,7 @@ public class CommonStepsTest extends BasePiperTest{
'cloudFoundryCreateSpace', //implementing new golang pattern without fields
'cloudFoundryDeleteService', //implementing new golang pattern without fields
'cloudFoundryDeleteSpace', //implementing new golang pattern without fields
'cnbBuild', //implementing new golang pattern without fields
'durationMeasure', // only expects parameters via signature
'prepareDefaultValues', // special step (infrastructure)
'piperPipeline', // special step (infrastructure)

9
vars/cnbBuild.groovy Normal file
View File

@@ -0,0 +1,9 @@
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/cnbBuild.yaml'
void call(Map parameters = [:]) {
List credentials = [[type: 'file', id: 'dockerConfigJsonCredentialsId', env: ['PIPER_dockerConfigJSON']]]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}