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:
parent
39d5c4994e
commit
39089bed5d
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
|
@ -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...)
|
||||
}
|
||||
|
@ -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.")
|
||||
@ -117,17 +157,33 @@ func kanikoExecuteMetadata() config.StepData {
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "containerImage",
|
||||
Name: "containerImage",
|
||||
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: "container/imageNameTag",
|
||||
Param: "artifactVersion",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "containerImageNameAndTag"}},
|
||||
Aliases: []config.Alias{{Name: "artifactVersion"}},
|
||||
},
|
||||
{
|
||||
Name: "containerPreparationCommand",
|
||||
@ -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{},
|
||||
|
@ -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
41
pkg/docker/container.go
Normal 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
|
||||
}
|
107
pkg/docker/container_test.go
Normal file
107
pkg/docker/container_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -141,8 +141,6 @@ spec:
|
||||
type: piperEnvironment
|
||||
params:
|
||||
- name: mtarFilePath
|
||||
fields:
|
||||
- name: mtarFilePath
|
||||
containers:
|
||||
- image: devxci/mbtci:1.0.14.1
|
||||
imagePullPolicy: Always
|
||||
|
@ -155,8 +155,6 @@ spec:
|
||||
type: piperEnvironment
|
||||
params:
|
||||
- name: operationId
|
||||
fields:
|
||||
- name: operationId
|
||||
containers:
|
||||
- name: xs
|
||||
image: ppiper/xs-cli
|
||||
|
Loading…
Reference in New Issue
Block a user