1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-06 04:13:55 +02:00

feat(cnbBuild): cache buildpacks during multi-image build (#3635)

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Johannes Dillmann <j.dillmann@sap.com>
This commit is contained in:
Pavel Busko 2022-03-30 13:58:16 +02:00 committed by GitHub
parent f4f11dba7f
commit 1f750af16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 126 additions and 82 deletions

View File

@ -11,7 +11,7 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry" "github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/fake"
) )

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/fake"
piperDocker "github.com/SAP/jenkins-library/pkg/docker" piperDocker "github.com/SAP/jenkins-library/pkg/docker"
@ -298,3 +298,8 @@ func (c *dockerClientMock) DownloadImage(imageSource, filePath string) (v1.Image
func (c *dockerClientMock) DownloadImageContent(imageSource, filePath string) (v1.Image, error) { func (c *dockerClientMock) DownloadImageContent(imageSource, filePath string) (v1.Image, error) {
return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath) return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath)
} }
// GetRemoteImageInfo return remote image information
func (c *dockerClientMock) GetRemoteImageInfo(imageSoure string) (v1.Image, error) {
return &fake.FakeImage{}, nil
}

View File

@ -17,11 +17,10 @@ import (
piperHttp "github.com/SAP/jenkins-library/pkg/http" piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/mock" "github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/protecode" "github.com/SAP/jenkins-library/pkg/protecode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/fake"
) )

View File

@ -14,7 +14,10 @@ import (
"github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go"
) )
var registryURL = "localhost:5000" const (
registryURL = "localhost:5000"
baseBuilder = "paketobuildpacks/builder:0.2.17-base"
)
func setupDockerRegistry(t *testing.T, ctx context.Context) testcontainers.Container { func setupDockerRegistry(t *testing.T, ctx context.Context) testcontainers.Container {
reqRegistry := testcontainers.ContainerRequest{ reqRegistry := testcontainers.ContainerRequest{
@ -38,14 +41,14 @@ func TestNpmProject(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata"}, TestDir: []string{"testdata"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
}) })
container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata"}, TestDir: []string{"testdata"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -79,7 +82,7 @@ func TestProjectDescriptor(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration", "project"}, TestDir: []string{"testdata", "TestCnbIntegration", "project"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -109,7 +112,7 @@ func TestZipPath(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration", "zip"}, TestDir: []string{"testdata", "TestCnbIntegration", "zip"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -133,7 +136,7 @@ func TestNonZipPath(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"}, TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -152,7 +155,7 @@ func TestNpmCustomBuildpacksFullProject(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"}, TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -161,7 +164,7 @@ func TestNpmCustomBuildpacksFullProject(t *testing.T) {
container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--buildpacks", "gcr.io/paketo-buildpacks/nodejs:0.14.0", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL) container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--buildpacks", "gcr.io/paketo-buildpacks/nodejs:0.14.0", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL)
container.assertHasOutput(t, "Setting custom buildpacks: '[gcr.io/paketo-buildpacks/nodejs:0.14.0]'") container.assertHasOutput(t, "Setting custom buildpacks: '[gcr.io/paketo-buildpacks/nodejs:0.14.0]'")
container.assertHasOutput(t, "Downloading buildpack 'gcr.io/paketo-buildpacks/nodejs:0.14.0' to /tmp/nodejs") container.assertHasOutput(t, "Downloading buildpack 'gcr.io/paketo-buildpacks/nodejs:0.14.0' to /tmp/buildpacks_cache/sha256:")
container.assertHasOutput(t, "running command: /cnb/lifecycle/creator") container.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
container.assertHasOutput(t, "Paketo NPM Start Buildpack") container.assertHasOutput(t, "Paketo NPM Start Buildpack")
container.assertHasOutput(t, fmt.Sprintf("Saving %s/not-found:0.0.1", registryURL)) container.assertHasOutput(t, fmt.Sprintf("Saving %s/not-found:0.0.1", registryURL))
@ -186,7 +189,7 @@ func TestNpmCustomBuildpacksBuildpacklessProject(t *testing.T) {
container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--buildpacks", "gcr.io/paketo-buildpacks/nodejs:0.14.0", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL) container.whenRunningPiperCommand("cnbBuild", "--noTelemetry", "--verbose", "--buildpacks", "gcr.io/paketo-buildpacks/nodejs:0.14.0", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL)
container.assertHasOutput(t, "Setting custom buildpacks: '[gcr.io/paketo-buildpacks/nodejs:0.14.0]'") container.assertHasOutput(t, "Setting custom buildpacks: '[gcr.io/paketo-buildpacks/nodejs:0.14.0]'")
container.assertHasOutput(t, "Downloading buildpack 'gcr.io/paketo-buildpacks/nodejs:0.14.0' to /tmp/nodejs") container.assertHasOutput(t, "Downloading buildpack 'gcr.io/paketo-buildpacks/nodejs:0.14.0' to /tmp/buildpacks_cache/sha256:")
container.assertHasOutput(t, "running command: /cnb/lifecycle/creator") container.assertHasOutput(t, "running command: /cnb/lifecycle/creator")
container.assertHasOutput(t, "Paketo NPM Start Buildpack") container.assertHasOutput(t, "Paketo NPM Start Buildpack")
container.assertHasOutput(t, fmt.Sprintf("Saving %s/not-found:0.0.1", registryURL)) container.assertHasOutput(t, fmt.Sprintf("Saving %s/not-found:0.0.1", registryURL))
@ -215,7 +218,7 @@ func TestBindings(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata"}, TestDir: []string{"testdata"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -236,7 +239,7 @@ func TestMultiImage(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration"}, TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -248,6 +251,8 @@ func TestMultiImage(t *testing.T) {
container.assertHasOutput(t, "Saving localhost:5000/io-buildpacks-my-app:latest...") container.assertHasOutput(t, "Saving localhost:5000/io-buildpacks-my-app:latest...")
container.assertHasOutput(t, "Previous image with name \"localhost:5000/go-app:v1.0.0\" not found") container.assertHasOutput(t, "Previous image with name \"localhost:5000/go-app:v1.0.0\" not found")
container.assertHasOutput(t, "Saving localhost:5000/go-app:v1.0.0...") container.assertHasOutput(t, "Saving localhost:5000/go-app:v1.0.0...")
container.assertHasOutput(t, "Using cached buildpack")
container.assertHasOutput(t, "Saving localhost:5000/my-app2:latest...")
container.terminate(t) container.terminate(t)
} }
@ -258,7 +263,7 @@ func TestPreserveFiles(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration"}, TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
@ -277,7 +282,7 @@ func TestPreserveFilesIgnored(t *testing.T) {
defer registryContainer.Terminate(ctx) defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{ container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:0.1.342-full", Image: baseBuilder,
User: "cnb", User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration"}, TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()), Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),

View File

@ -10,3 +10,5 @@ steps:
- containerImageName: go-app - containerImageName: go-app
containerImageTag: v1.0.0 containerImageTag: v1.0.0
path: zip/go.zip path: zip/go.zip
- path: project
containerImageName: my-app2

View File

@ -3,14 +3,15 @@ package cnbutils
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors"
) )
const bpCacheDir = "/tmp/buildpacks_cache"
type BuildPackMetadata struct { type BuildPackMetadata struct {
ID string `toml:"id,omitempty" json:"id,omitempty" yaml:"id,omitempty"` ID string `toml:"id,omitempty" json:"id,omitempty" yaml:"id,omitempty"`
Name string `toml:"name,omitempty" json:"name,omitempty" yaml:"name,omitempty"` Name string `toml:"name,omitempty" json:"name,omitempty" yaml:"name,omitempty"`
@ -37,33 +38,57 @@ func DownloadBuildpacks(path string, bpacks []string, dockerCreds string, utils
Utils: utils, Utils: utils,
} }
err := utils.MkdirAll(bpCacheDir, os.ModePerm)
if err != nil {
return Order{}, errors.Wrap(err, "failed to create temp directory for buildpack cache")
}
for _, bpack := range bpacks { for _, bpack := range bpacks {
var bpackMeta BuildPackMetadata var bpackMeta BuildPackMetadata
tempDir, err := utils.TempDir("", filepath.Base(bpack)) imageInfo, err := utils.GetRemoteImageInfo(bpack)
if err != nil { if err != nil {
return Order{}, fmt.Errorf("failed to create temp directory, error: %s", err.Error()) return Order{}, errors.Wrap(err, "failed to get remote image info of buildpack")
} }
defer utils.RemoveAll(tempDir) hash, err := imageInfo.Digest()
log.Entry().Infof("Downloading buildpack '%s' to %s", bpack, tempDir)
img, err := utils.DownloadImageContent(bpack, tempDir)
if err != nil { if err != nil {
return Order{}, fmt.Errorf("failed download buildpack image '%s', error: %s", bpack, err.Error()) return Order{}, errors.Wrap(err, "failed to get image digest")
}
cacheDir := filepath.Join(bpCacheDir, hash.String())
cacheExists, err := utils.DirExists(cacheDir)
if err != nil {
return Order{}, errors.Wrapf(err, "failed to check if cache dir '%s' exists", cacheDir)
} }
imgConf, err := img.ConfigFile() if cacheExists {
log.Entry().Infof("Using cached buildpack '%s'", bpack)
} else {
err := utils.MkdirAll(cacheDir, os.ModePerm)
if err != nil {
return Order{}, errors.Wrap(err, "failed to create temp directory for buildpack cache")
}
log.Entry().Infof("Downloading buildpack '%s' to %s", bpack, cacheDir)
img, err := utils.DownloadImageContent(bpack, cacheDir)
if err != nil {
return Order{}, errors.Wrapf(err, "failed download buildpack image '%s'", bpack)
}
imageInfo = img
}
imgConf, err := imageInfo.ConfigFile()
if err != nil { if err != nil {
return Order{}, fmt.Errorf("failed to read '%s' image config, error: %s", bpack, err.Error()) return Order{}, errors.Wrapf(err, "failed to read '%s' image config", bpack)
} }
err = json.Unmarshal([]byte(imgConf.Config.Labels["io.buildpacks.buildpackage.metadata"]), &bpackMeta) err = json.Unmarshal([]byte(imgConf.Config.Labels["io.buildpacks.buildpackage.metadata"]), &bpackMeta)
if err != nil { if err != nil {
return Order{}, fmt.Errorf("failed unmarshal '%s' image label, error: %s", bpack, err.Error()) return Order{}, errors.Wrapf(err, "failed unmarshal '%s' image label", bpack)
} }
log.Entry().Debugf("Buildpack metadata: '%v'", bpackMeta) log.Entry().Debugf("Buildpack metadata: '%v'", bpackMeta)
orderEntry.Group = append(orderEntry.Group, bpackMeta) orderEntry.Group = append(orderEntry.Group, bpackMeta)
err = copyBuildPack(filepath.Join(tempDir, "cnb/buildpacks"), path, utils) err = CopyProject(filepath.Join(cacheDir, "cnb/buildpacks"), path, nil, nil, utils)
if err != nil { if err != nil {
return Order{}, err return Order{}, err
} }
@ -73,38 +98,3 @@ func DownloadBuildpacks(path string, bpacks []string, dockerCreds string, utils
return order, nil return order, nil
} }
func copyBuildPack(src, dst string, utils BuildUtils) error {
buildpacks, err := utils.Glob(filepath.Join(src, "*"))
if err != nil {
return fmt.Errorf("failed to read directory: %s, error: %s", src, err.Error())
}
for _, buildpack := range buildpacks {
versions, err := utils.Glob(filepath.Join(buildpack, "*"))
if err != nil {
return fmt.Errorf("failed to read directory: %s, error: %s", buildpack, err.Error())
}
for _, srcVersionPath := range versions {
destVersionPath := filepath.Join(dst, strings.ReplaceAll(srcVersionPath, src, ""))
exists, err := utils.FileExists(destVersionPath)
if err != nil {
return fmt.Errorf("failed to check if directory exists: '%s', error: '%s'", destVersionPath, err.Error())
}
if exists {
utils.RemoveAll(destVersionPath)
}
if err := utils.MkdirAll(filepath.Dir(destVersionPath), 0755); err != nil {
return fmt.Errorf("failed to create directory: '%s', error: '%s'", filepath.Dir(destVersionPath), err.Error())
}
err = utils.FileRename(srcVersionPath, destVersionPath)
if err != nil {
return fmt.Errorf("failed to move '%s' to '%s', error: %s", srcVersionPath, destVersionPath, err.Error())
}
}
}
return nil
}

View File

@ -14,11 +14,9 @@ func TestBuildpackDownload(t *testing.T) {
FilesMock: &mock.FilesMock{}, FilesMock: &mock.FilesMock{},
} }
t.Run("successfully downloads a buildpack", func(t *testing.T) { t.Run("it creates an order object", func(t *testing.T) {
mockUtils.AddDir("/tmp/testtest") order, err := cnbutils.DownloadBuildpacks("/destination", []string{"buildpack"}, "/tmp/config.json", mockUtils)
_, err := cnbutils.DownloadBuildpacks("/test", []string{"test"}, "/test/config.json", mockUtils)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, mockUtils.HasRemovedFile("/tmp/testtest")) assert.Equal(t, 1, len(order.Order))
}) })
} }

View File

@ -44,3 +44,17 @@ func (c *MockUtils) DownloadImage(src, dst string) (v1.Image, error) {
func (c *MockUtils) GetImageSource() (string, error) { func (c *MockUtils) GetImageSource() (string, error) {
return "imageSource", nil return "imageSource", nil
} }
func (c *MockUtils) GetRemoteImageInfo(imageSource string) (v1.Image, error) {
fakeImage := fakeImage.FakeImage{}
fakeImage.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: map[string]string{
"io.buildpacks.buildpackage.metadata": "{\"id\": \"testbuildpack\", \"version\": \"0.0.1\"}",
},
},
}, nil)
fakeImage.DigestReturns(v1.Hash{}, nil)
return &fakeImage, nil
}

View File

@ -3,7 +3,6 @@ package cnbutils
import ( import (
"fmt" "fmt"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/platform"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
) )
@ -23,8 +22,6 @@ func DigestFromReport(utils BuildUtils) (string, error) {
return "", err return "", err
} }
log.Entry().Debugf("Image report: %#v\n", report)
if report.Image.Digest == "" { if report.Image.Digest == "" {
return "", fmt.Errorf("image digest is empty") return "", fmt.Errorf("image digest is empty")
} }

View File

@ -11,11 +11,14 @@ import (
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils" "github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/pkg/errors"
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd" cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
) )
// AuthEntry defines base64 encoded username:password required inside a Docker config.json // AuthEntry defines base64 encoded username:password required inside a Docker config.json
@ -94,6 +97,7 @@ type ClientOptions struct {
type Download interface { type Download interface {
DownloadImage(imageSource, targetFile string) (v1.Image, error) DownloadImage(imageSource, targetFile string) (v1.Image, error)
DownloadImageContent(imageSource, targetDir string) (v1.Image, error) DownloadImageContent(imageSource, targetDir string) (v1.Image, error)
GetRemoteImageInfo(string) (v1.Image, error)
} }
// SetOptions sets options used for the docker client // SetOptions sets options used for the docker client
@ -103,7 +107,7 @@ func (c *Client) SetOptions(options ClientOptions) {
c.localPath = options.LocalPath c.localPath = options.LocalPath
} }
//DownloadImageToPath downloads the image content into the given targetDir. Returns with an error if the targetDir doesnt exist //DownloadImageContent downloads the image content into the given targetDir. Returns with an error if the targetDir doesnt exist
func (c *Client) DownloadImageContent(imageSource, targetDir string) (v1.Image, error) { func (c *Client) DownloadImageContent(imageSource, targetDir string) (v1.Image, error) {
if fileInfo, err := os.Stat(targetDir); err != nil { if fileInfo, err := os.Stat(targetDir); err != nil {
return nil, err return nil, err
@ -178,6 +182,16 @@ func (c *Client) DownloadImage(imageSource, targetFile string) (v1.Image, error)
return img, nil return img, nil
} }
// GetRemoteImageInfo retrieves information about the image (e.g. digest) without actually downoading it
func (c *Client) GetRemoteImageInfo(imageSource string) (v1.Image, error) {
ref, err := c.getImageRef(imageSource)
if err != nil {
return nil, errors.Wrap(err, "parsing image reference")
}
return remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
}
func (c *Client) getImageRef(image string) (name.Reference, error) { func (c *Client) getImageRef(image string) (name.Reference, error) {
opts := []name.Option{} opts := []name.Option{}

View File

@ -2,17 +2,20 @@ package mock
import ( import (
"fmt" "fmt"
"github.com/google/go-containerregistry/pkg/v1"
v1 "github.com/google/go-containerregistry/pkg/v1"
) )
// DownloadMock . // DownloadMock .
type DownloadMock struct { type DownloadMock struct {
FilePath string FilePath string
ImageRef string ImageRef string
RegistryURL string RemoteImageRef string
RegistryURL string
ReturnImage v1.Image ReturnImage v1.Image
ReturnError string RemoteImageInfo v1.Image
ReturnError string
Stub func(imageRef, targetDir string) (v1.Image, error) Stub func(imageRef, targetDir string) (v1.Image, error)
} }
@ -42,3 +45,14 @@ func (c *DownloadMock) DownloadImageContent(imageRef, targetFile string) (v1.Ima
} }
return c.ReturnImage, nil return c.ReturnImage, nil
} }
// GetRemoteImageInfo .
func (c *DownloadMock) GetRemoteImageInfo(imageRef string) (v1.Image, error) {
c.RemoteImageRef = imageRef
if len(c.ReturnError) > 0 {
return nil, fmt.Errorf(c.ReturnError)
}
return c.RemoteImageInfo, nil
}

View File

@ -115,6 +115,12 @@ func (f Files) Copy(src, dst string) (int64, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
stats, err := os.Stat(src)
if err != nil {
return 0, err
}
os.Chmod(dst, stats.Mode())
defer func() { _ = destination.Close() }() defer func() { _ = destination.Close() }()
nBytes, err := CopyData(destination, source) nBytes, err := CopyData(destination, source)
return nBytes, err return nBytes, err
@ -236,7 +242,7 @@ func Untar(src string, dest string, stripComponentLevel int) error {
defer file.Close() defer file.Close()
if err != nil { if err != nil {
fmt.Errorf("unable to open src: %v", err) return fmt.Errorf("unable to open src: %v", err)
} }
if b, err := isFileGzipped(src); err == nil && b { if b, err := isFileGzipped(src); err == nil && b {