1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

kanikoExecute: improve user experience (#2141)

* kanikoExecute: improve user experience

* ensure proper tags

* update permissions

in case a container runs with a different user
we need to make sure that the orchestrator user
can work on the file

* update permissions

* ensure availablility of directories on Jenkins

* (fix) clean up tmp dir in test

* add resilience for incorrect step yaml

* incorporate PR feedback
This commit is contained in:
Oliver Nocon 2020-10-14 11:13:08 +02:00 committed by GitHub
parent 39d5c4994e
commit 39089bed5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 415 additions and 23 deletions

View File

@ -4,6 +4,8 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
@ -64,6 +66,12 @@ func generateConfig() error {
return errors.Wrap(err, "metadata: read failed")
}
// prepare output resource directories:
// this is needed in order to have proper directory permissions in case
// resources written inside a container image with a different user
// Remark: This is so far only relevant for Jenkins environments where getConfig is executed
prepareOutputEnvironment(metadata.Spec.Outputs.Resources, GeneralConfig.EnvRootPath)
resourceParams := metadata.GetResourceParameters(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
projectConfigFile := getProjectConfigFile(GeneralConfig.CustomConfig)
@ -153,3 +161,21 @@ func applyContextConditions(metadata config.StepData, stepConfig *config.StepCon
//ToDo: remove all unnecessary sub maps?
// e.g. extract delete() from applyContainerConditions - loop over all stepConfig.Config[param.Value] and remove ...
}
func prepareOutputEnvironment(outputResources []config.StepResources, envRootPath string) {
for _, oResource := range outputResources {
for _, oParam := range oResource.Parameters {
paramPath := path.Join(envRootPath, oResource.Name, fmt.Sprint(oParam["name"]))
if oParam["fields"] != nil {
paramFields, ok := oParam["fields"].([]map[string]string)
if ok && len(paramFields) > 0 {
paramPath = path.Join(paramPath, paramFields[0]["name"])
}
}
if _, err := os.Stat(filepath.Dir(paramPath)); os.IsNotExist(err) {
log.Entry().Debugf("Creating directory: %v", filepath.Dir(paramPath))
os.MkdirAll(filepath.Dir(paramPath), 0777)
}
}
}
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
@ -11,6 +13,7 @@ import (
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func configOpenFileMock(name string) (io.ReadCloser, error) {
@ -257,3 +260,53 @@ func TestApplyContextConditions(t *testing.T) {
})
}
}
func TestPrepareOutputEnvironment(t *testing.T) {
outputResources := []config.StepResources{
{
Name: "commonPipelineEnvironment",
Type: "piperEnvironment",
Parameters: []map[string]interface{}{
{"name": "param0"},
{"name": "path1/param1"},
{"name": "path2/param2"},
},
},
{
Name: "influx",
Type: "influx",
Parameters: []map[string]interface{}{
{
"name": "measurement0",
"fields": []map[string]string{
{"name": "influx0_0"},
{"name": "influx0_1"},
},
},
{
"name": "measurement1",
"fields": []map[string]string{
{"name": "influx1_0"},
{"name": "influx1_1"},
},
},
},
},
}
dir, tempDirErr := ioutil.TempDir("", "")
defer os.RemoveAll(dir)
require.NoError(t, tempDirErr)
require.DirExists(t, dir, "Failed to create temporary directory")
prepareOutputEnvironment(outputResources, dir)
assert.DirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path1"))
assert.DirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path2"))
assert.DirExists(t, filepath.Join(dir, "influx", "measurement0"))
assert.DirExists(t, filepath.Join(dir, "influx", "measurement1"))
assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "param0"))
assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path1", "param1"))
assert.NoDirExists(t, filepath.Join(dir, "commonPipelineEnvironment", "path2", "param2"))
assert.NoDirExists(t, filepath.Join(dir, "influx", "measurement0", "influx0_0"))
assert.NoDirExists(t, filepath.Join(dir, "influx", "measurement1", "influx0_1"))
}

View File

@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"io/ioutil"
"net/http"
"os"
@ -10,12 +11,13 @@ import (
"github.com/pkg/errors"
"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"
)
func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData) {
func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) {
// for command execution use Command
c := command.Command{
ErrorCategoryMapping: map[string][]string{
@ -33,13 +35,13 @@ func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomD
fileUtils := &piperutils.Files{}
err := runKanikoExecute(&config, telemetryData, &c, client, fileUtils)
err := runKanikoExecute(&config, telemetryData, commonPipelineEnvironment, &c, client, fileUtils)
if err != nil {
log.Entry().WithError(err).Fatal("Kaniko execution failed")
}
}
func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error {
func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error {
// backward compatibility for parameter ContainerBuildOptions
if len(config.ContainerBuildOptions) > 0 {
config.BuildOptions = strings.Split(config.ContainerBuildOptions, " ")
@ -63,7 +65,25 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus
if !piperutils.ContainsString(config.BuildOptions, "--destination") {
dest := []string{"--no-push"}
if len(config.ContainerImage) > 0 {
containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage)
if err != nil {
return errors.Wrapf(err, "invalid registry part in image %v", config.ContainerImage)
}
// errors are already caught with previous call to docker.ContainerRegistryFromImage
containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage)
dest = []string{"--destination", config.ContainerImage}
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry)
commonPipelineEnvironment.container.imageNameTag = containerImageNameTag
}
if len(config.ContainerRegistryURL) > 0 && len(config.ContainerImageName) > 0 && len(config.ContainerImageTag) > 0 {
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
if err != nil {
return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL)
}
containerImageTag := fmt.Sprintf("%v:%v", config.ContainerImageName, strings.ReplaceAll(config.ContainerImageTag, "+", "-"))
dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageTag)}
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
commonPipelineEnvironment.container.imageNameTag = containerImageTag
}
config.BuildOptions = append(config.BuildOptions, dest...)
}

View File

@ -5,10 +5,12 @@ 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/telemetry"
"github.com/spf13/cobra"
)
@ -17,12 +19,45 @@ type kanikoExecuteOptions struct {
BuildOptions []string `json:"buildOptions,omitempty"`
ContainerBuildOptions string `json:"containerBuildOptions,omitempty"`
ContainerImage string `json:"containerImage,omitempty"`
ContainerImageName string `json:"containerImageName,omitempty"`
ContainerImageTag string `json:"containerImageTag,omitempty"`
ContainerPreparationCommand string `json:"containerPreparationCommand,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
DockerfilePath string `json:"dockerfilePath,omitempty"`
}
type kanikoExecuteCommonPipelineEnvironment struct {
container struct {
registryURL string
imageNameTag string
}
}
func (p *kanikoExecuteCommonPipelineEnvironment) 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")
}
}
// KanikoExecuteCommand Executes a [Kaniko](https://github.com/GoogleContainerTools/kaniko) build for creating a Docker container.
func KanikoExecuteCommand() *cobra.Command {
const STEP_NAME = "kanikoExecute"
@ -30,6 +65,7 @@ func KanikoExecuteCommand() *cobra.Command {
metadata := kanikoExecuteMetadata()
var stepConfig kanikoExecuteOptions
var startTime time.Time
var commonPipelineEnvironment kanikoExecuteCommonPipelineEnvironment
var createKanikoExecuteCmd = &cobra.Command{
Use: STEP_NAME,
@ -62,6 +98,7 @@ func KanikoExecuteCommand() *cobra.Command {
telemetryData := telemetry.CustomData{}
telemetryData.ErrorCode = "1"
handler := func() {
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetryData.ErrorCategory = log.GetErrorCategory().String()
telemetry.Send(&telemetryData)
@ -69,7 +106,7 @@ func KanikoExecuteCommand() *cobra.Command {
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
kanikoExecute(stepConfig, &telemetryData)
kanikoExecute(stepConfig, &telemetryData, &commonPipelineEnvironment)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
@ -83,7 +120,10 @@ func addKanikoExecuteFlags(cmd *cobra.Command, stepConfig *kanikoExecuteOptions)
cmd.Flags().StringSliceVar(&stepConfig.BuildOptions, "buildOptions", []string{`--skip-tls-verify-pull`}, "Defines a list of build options for the [kaniko](https://github.com/GoogleContainerTools/kaniko) build.")
cmd.Flags().StringVar(&stepConfig.ContainerBuildOptions, "containerBuildOptions", os.Getenv("PIPER_containerBuildOptions"), "Deprected, please use buildOptions. Defines the build options for the [kaniko](https://github.com/GoogleContainerTools/kaniko) build.")
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.ContainerPreparationCommand, "containerPreparationCommand", `rm -f /kaniko/.docker/config.json`, "Defines the command to prepare the Kaniko container. By default the contained credentials are removed in order to allow anonymous access to container registries.")
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().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.")
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.Flags().StringVar(&stepConfig.DockerfilePath, "dockerfilePath", `Dockerfile`, "Defines the location of the Dockerfile relative to the Jenkins workspace.")
@ -118,17 +158,33 @@ func kanikoExecuteMetadata() config.StepData {
},
{
Name: "containerImage",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "container/imageNameTag",
},
},
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "containerImageNameAndTag"}},
},
{
Name: "containerImageName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "dockerImageName"}},
},
{
Name: "containerImageTag",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "artifactVersion",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "artifactVersion"}},
},
{
Name: "containerPreparationCommand",
ResourceRef: []config.ResourceReference{},
@ -137,6 +193,19 @@ func kanikoExecuteMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{},
},
{
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"}},
},
{
Name: "customTlsCertificateLinks",
ResourceRef: []config.ResourceReference{},

View File

@ -84,6 +84,8 @@ func (f *kanikoFileMock) Glob(pattern string) (matches []string, err error) {
func TestRunKanikoExecute(t *testing.T) {
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
t.Run("success case", func(t *testing.T) {
config := &kanikoExecuteOptions{
BuildOptions: []string{"--skip-tls-verify-pull"},
@ -104,7 +106,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileWriteContent: map[string]string{},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.NoError(t, err)
@ -120,6 +122,44 @@ func TestRunKanikoExecute(t *testing.T) {
})
t.Run("success case - image params", func(t *testing.T) {
config := &kanikoExecuteOptions{
BuildOptions: []string{"--skip-tls-verify-pull"},
ContainerImageName: "myImage",
ContainerImageTag: "1.2.3-a+x",
ContainerRegistryURL: "https://my.registry.com:50000",
ContainerPreparationCommand: "rm -f /kaniko/.docker/config.json",
CustomTLSCertificateLinks: []string{"https://test.url/cert.crt"},
DockerfilePath: "Dockerfile",
DockerConfigJSON: "path/to/docker/config.json",
}
runner := &mock.ExecMockRunner{}
certClient := &kanikoMockClient{
responseBody: "testCert",
}
fileUtils := &kanikoFileMock{
fileReadContent: map[string]string{"path/to/docker/config.json": `{"auths":{"custom":"test"}}`},
fileWriteContent: map[string]string{},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.NoError(t, err)
assert.Equal(t, "rm", runner.Calls[0].Exec)
assert.Equal(t, []string{"-f", "/kaniko/.docker/config.json"}, runner.Calls[0].Params)
assert.Equal(t, config.CustomTLSCertificateLinks, certClient.urlsCalled)
assert.Equal(t, `{"auths":{"custom":"test"}}`, fileUtils.fileWriteContent["/kaniko/.docker/config.json"])
assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec)
cwd, _ := os.Getwd()
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, runner.Calls[1].Params)
})
t.Run("success case - no push, no docker config.json", func(t *testing.T) {
config := &kanikoExecuteOptions{
ContainerBuildOptions: "--skip-tls-verify-pull",
@ -137,7 +177,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileWriteContent: map[string]string{},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.NoError(t, err)
@ -167,7 +207,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileWriteContent: map[string]string{},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.NoError(t, err)
cwd, _ := os.Getwd()
@ -186,7 +226,7 @@ func TestRunKanikoExecute(t *testing.T) {
certClient := &kanikoMockClient{}
fileUtils := &kanikoFileMock{}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.EqualError(t, err, "failed to initialize Kaniko container: rm failed")
})
@ -203,7 +243,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileWriteContent: map[string]string{},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.EqualError(t, err, "execution of '/kaniko/executor' failed: kaniko run failed")
})
@ -219,7 +259,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileReadErr: map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.EqualError(t, err, "failed to update certificates: failed to load file '/kaniko/ssl/certs/ca-certificates.crt': read error")
})
@ -237,7 +277,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileReadErr: map[string]error{"path/to/docker/config.json": fmt.Errorf("read error")},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.EqualError(t, err, "failed to read file 'path/to/docker/config.json': read error")
})
@ -255,7 +295,7 @@ func TestRunKanikoExecute(t *testing.T) {
fileWriteErr: map[string]error{"/kaniko/.docker/config.json": fmt.Errorf("write error")},
}
err := runKanikoExecute(config, &telemetry.CustomData{}, runner, certClient, fileUtils)
err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils)
assert.EqualError(t, err, "failed to write file '/kaniko/.docker/config.json': write error")
})

41
pkg/docker/container.go Normal file
View File

@ -0,0 +1,41 @@
package docker
import (
"fmt"
"net/url"
"strings"
containerName "github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
)
// ContainerRegistryFromURL provides the registry part of a complete registry url including the port
func ContainerRegistryFromURL(registryURL string) (string, error) {
u, err := url.ParseRequestURI(registryURL)
if err != nil {
return "", errors.Wrap(err, "invalid registry url")
}
if len(u.Host) == 0 {
return "", fmt.Errorf("invalid registry url")
}
return u.Host, nil
}
// ContainerRegistryFromImage provides the registry part of a full image name
func ContainerRegistryFromImage(fullImage string) (string, error) {
ref, err := containerName.ParseReference(strings.ToLower(fullImage))
if err != nil {
return "", errors.Wrap(err, "failed to parse image name")
}
return ref.Context().RegistryStr(), nil
}
// ContainerImageNameTagFromImage provides the name & tag part of a full image name
func ContainerImageNameTagFromImage(fullImage string) (string, error) {
ref, err := containerName.ParseReference(strings.ToLower(fullImage))
if err != nil {
return "", errors.Wrap(err, "failed to parse image name")
}
registryOnly := fmt.Sprintf("%v/", ref.Context().RegistryStr())
return strings.ReplaceAll(fullImage, registryOnly, ""), nil
}

View File

@ -0,0 +1,107 @@
package docker
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContainerRegistryFromURL(t *testing.T) {
tt := []struct {
url string
expected string
expectedError string
}{
{url: "", expected: "", expectedError: "invalid registry url"},
{url: "invalidUrl", expected: "", expectedError: "invalid registry url"},
{url: "no.protocol.com", expected: "", expectedError: "invalid registry url"},
{url: "no.protocol.com:50000", expected: "", expectedError: "invalid registry url"},
{url: "no.protocol.com:50000/my/path/to/image", expected: "", expectedError: "invalid registry url"},
{url: "no.protocol.com:50000/my/path/to/image:withTag", expected: "", expectedError: "invalid registry url"},
{url: "no.protocol.com:50000/my/path/to/image@withDigest", expected: "", expectedError: "invalid registry url"},
{url: "https://my.registry.com", expected: "my.registry.com"},
{url: "https://my.registry.com:50000", expected: "my.registry.com:50000"},
{url: "https://my.registry.com:50000/", expected: "my.registry.com:50000"},
{url: "https://my.registry.com:50000/my/path/to/image:withTag", expected: "my.registry.com:50000"},
{url: "https://my.registry.com:50000/my/path/to/image@withDigest", expected: "my.registry.com:50000"},
}
for _, test := range tt {
t.Run(test.url, func(t *testing.T) {
got, err := ContainerRegistryFromURL(test.url)
if len(test.expectedError) > 0 {
assert.Contains(t, fmt.Sprint(err), test.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expected, got)
})
}
}
func TestContainerRegistryFromImage(t *testing.T) {
tt := []struct {
image string
expected string
expectedError string
}{
{image: "", expected: "", expectedError: "failed to parse image name"},
{image: "onlyImage", expected: "index.docker.io"},
{image: "onlyimage", expected: "index.docker.io"},
{image: "onlyimage:withTag", expected: "index.docker.io"},
{image: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "index.docker.io"},
{image: "path/to/image", expected: "index.docker.io"},
{image: "my.registry.com/onlyimage", expected: "my.registry.com"},
{image: "my.registry.com:50000/onlyimage", expected: "my.registry.com:50000"},
{image: "my.registry.com:50000/onlyimage:withTag", expected: "my.registry.com:50000"},
{image: "my.registry.com:50000/onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "my.registry.com:50000"},
{image: "my.registry.com:50000/path/to/image:withTag", expected: "my.registry.com:50000"},
{image: "my.registry.com:50000/path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "my.registry.com:50000"},
}
for _, test := range tt {
t.Run(test.image, func(t *testing.T) {
got, err := ContainerRegistryFromImage(test.image)
if len(test.expectedError) > 0 {
assert.Contains(t, fmt.Sprint(err), test.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expected, got)
})
}
}
func TestContainerImageNameTagFromImage(t *testing.T) {
tt := []struct {
image string
expected string
expectedError string
}{
{image: "", expected: "", expectedError: "failed to parse image name"},
{image: "onlyImage", expected: "onlyImage"},
{image: "onlyimage", expected: "onlyimage"},
{image: "onlyimage:withTag", expected: "onlyimage:withTag"},
{image: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"},
{image: "path/to/image", expected: "path/to/image"},
{image: "my.registry.com/onlyimage", expected: "onlyimage"},
{image: "my.registry.com:50000/onlyimage", expected: "onlyimage"},
{image: "my.registry.com:50000/onlyimage:withTag", expected: "onlyimage:withTag"},
{image: "my.registry.com:50000/onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "onlyimage@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"},
{image: "my.registry.com:50000/path/to/image:withTag", expected: "path/to/image:withTag"},
{image: "my.registry.com:50000/path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205", expected: "path/to/image@sha256:152f65865ae43b143b1e42dacdb5e9c473dd70b3adc5b79af7cf585cc8605205"},
}
for _, test := range tt {
t.Run(test.image, func(t *testing.T) {
got, err := ContainerImageNameTagFromImage(test.image)
if len(test.expectedError) > 0 {
assert.Contains(t, fmt.Sprint(err), test.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expected, got)
})
}
}

View File

@ -55,13 +55,13 @@ func writeToDisk(filename string, data []byte) error {
if _, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
log.Entry().Debugf("Creating directory: %v", filepath.Dir(filename))
os.MkdirAll(filepath.Dir(filename), 0755)
os.MkdirAll(filepath.Dir(filename), 0777)
}
//ToDo: make sure to not overwrite file but rather add another file? Create error if already existing?
if len(data) > 0 {
log.Entry().Debugf("Writing file to disk: %v", filename)
return ioutil.WriteFile(filename, data, 0755)
return ioutil.WriteFile(filename, data, 0766)
}
return nil
}

View File

@ -35,9 +35,29 @@ spec:
- 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: container/imageNameTag
param: artifactVersion
- name: containerPreparationCommand
type: string
description: Defines the command to prepare the Kaniko container. By default the contained credentials are removed in order to allow anonymous access to container registries.
@ -46,6 +66,19 @@ spec:
- STAGES
- STEPS
default: rm -f /kaniko/.docker/config.json
- 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: customTlsCertificateLinks
type: "[]string"
description: List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.
@ -75,6 +108,13 @@ spec:
- STAGES
- STEPS
default: Dockerfile
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: container/registryUrl
- name: container/imageNameTag
containers:
- image: gcr.io/kaniko-project/executor:debug
command:

View File

@ -141,8 +141,6 @@ spec:
type: piperEnvironment
params:
- name: mtarFilePath
fields:
- name: mtarFilePath
containers:
- image: devxci/mbtci:1.0.14.1
imagePullPolicy: Always

View File

@ -155,8 +155,6 @@ spec:
type: piperEnvironment
params:
- name: operationId
fields:
- name: operationId
containers:
- name: xs
image: ppiper/xs-cli