diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e77e57d0e3..641306fde7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos, linux, windows] + os: [macos, linux, windows-lcow, windows-wcow] include: - os: macos runner: macos-latest @@ -25,10 +25,14 @@ jobs: runner: ubuntu-latest no_docker: "false" pack_bin: pack - - os: windows + - os: windows-lcow runner: [self-hosted, windows] no_docker: "false" pack_bin: pack.exe + - os: windows-wcow + runner: windows-latest + no_docker: "false" + pack_bin: pack.exe runs-on: ${{ matrix.runner }} env: PACK_BIN: ${{ matrix.pack_bin }} @@ -50,7 +54,7 @@ jobs: echo "::add-path::$(go env GOPATH)/bin" - name: Verify # disabled for windows due to format verification failing - if: matrix.os != 'windows' + if: ${{ !contains(matrix.os, 'windows') }} run: make verify - name: Test env: @@ -91,7 +95,8 @@ jobs: - name: Download artifacts - windows uses: actions/download-artifact@v1 with: - name: pack-windows + name: pack-windows-lcow + path: pack-windows - name: Package artifacts - macos run: | chmod +x pack-macos/pack diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index be8a0cc5df..c73c0a8448 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -300,6 +300,8 @@ func testWithoutSpecificBuilderRequirement( "pack does not support 'package-buildpack'", ) + h.SkipIf(t, dockerHostOS() == "windows", "These tests are not yet compatible with Windows-based containers") + var err error tmpDir, err = ioutil.TempDir("", "package-buildpack-tests") h.AssertNil(t, err) @@ -553,6 +555,22 @@ func testAcceptance( runImageMirror = value }) + when("creating a windows builder", func() { + it.Before(func() { + h.SkipIf(t, dockerHostOS() != "windows", "The current Docker daemon does not support Windows-based containers") + }) + + it("succeeds", func() { + builderName := createBuilder(t, runImageMirror, configDir, packCreateBuilderPath, lifecyclePath, lifecycleDescriptor) + defer h.DockerRmi(dockerCli, builderName) + + inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), builderName) + h.AssertNil(t, err) + + h.AssertEq(t, inspect.Os, "windows") + }) + }) + when("builder is created", func() { var ( builderName string @@ -560,6 +578,8 @@ func testAcceptance( ) it.Before(func() { + h.SkipIf(t, dockerHostOS() == "windows", "These tests are not yet compatible with Windows-based containers") + var err error tmpDir, err = ioutil.TempDir("", "package-buildpack-tests") h.AssertNil(t, err) @@ -1672,43 +1692,42 @@ func createBuilder(t *testing.T, runImageMirror, configDir, packPath, lifecycleP h.AssertNil(t, err) } - // CREATE PACKAGE - packageImageName := packageBuildpackAsImage(t, - packPath, - filepath.Join(configDir, "package.toml"), - lifecycleDescriptor, - []string{"simple-layers-buildpack"}, - ) - - // RENDER builder.toml - cfgData := fillTemplate(t, filepath.Join(configDir, "builder.toml"), map[string]interface{}{ - "package_name": packageImageName, - }) - err = ioutil.WriteFile(filepath.Join(tmpDir, "builder.toml"), []byte(cfgData), os.ModePerm) - h.AssertNil(t, err) - - builderConfigFile, err := os.OpenFile(filepath.Join(tmpDir, "builder.toml"), os.O_RDWR|os.O_APPEND, os.ModePerm) - h.AssertNil(t, err) + var packageImageName string + var packageId string + if dockerHostOS() != "windows" { + // CREATE PACKAGE + packageImageName = packageBuildpackAsImage(t, + packPath, + filepath.Join(configDir, "package.toml"), + lifecycleDescriptor, + []string{"simple-layers-buildpack"}, + ) - // ADD run-image-mirrors - _, err = builderConfigFile.Write([]byte(fmt.Sprintf("run-image-mirrors = [\"%s\"]\n", runImageMirror))) - h.AssertNil(t, err) + packageId = "simple/layers" + } // ADD lifecycle - _, err = builderConfigFile.Write([]byte("[lifecycle]\n")) - h.AssertNil(t, err) - + var lifecycleURI string + var lifecycleVersion string if lifecyclePath != "" { t.Logf("adding lifecycle path '%s' to builder config", lifecyclePath) - _, err = builderConfigFile.Write([]byte(fmt.Sprintf("uri = \"%s\"\n", strings.ReplaceAll(lifecyclePath, `\`, `\\`)))) - h.AssertNil(t, err) + lifecycleURI = strings.ReplaceAll(lifecyclePath, `\`, `\\`) } else { t.Logf("adding lifecycle version '%s' to builder config", lifecycleDescriptor.Info.Version.String()) - _, err = builderConfigFile.Write([]byte(fmt.Sprintf("version = \"%s\"\n", lifecycleDescriptor.Info.Version.String()))) - h.AssertNil(t, err) + lifecycleVersion = lifecycleDescriptor.Info.Version.String() } - builderConfigFile.Close() + // RENDER builder.toml + cfgData := fillTemplate(t, filepath.Join(configDir, "builder.toml"), map[string]interface{}{ + "package_image_name": packageImageName, + "package_id": packageId, + "run_image_mirror": runImageMirror, + "lifecycle_uri": lifecycleURI, + "lifecycle_version": lifecycleVersion, + }) + + err = ioutil.WriteFile(filepath.Join(tmpDir, "builder.toml"), []byte(cfgData), os.ModePerm) + h.AssertNil(t, err) // NAME BUILDER bldr := registryConfig.RepoName("test/builder-" + h.RandString(10)) @@ -1793,10 +1812,12 @@ func createStack(t *testing.T, dockerCli client.CommonAPIClient, runImageMirror t.Helper() t.Log("creating stack images...") - if err := createStackImage(dockerCli, runImage, filepath.Join("testdata", "mock_stack", "run")); err != nil { + stackBaseDir := filepath.Join("testdata", "mock_stack", dockerHostOS()) + + if err := createStackImage(dockerCli, runImage, filepath.Join(stackBaseDir, "run")); err != nil { return err } - if err := createStackImage(dockerCli, buildImage, filepath.Join("testdata", "mock_stack", "build")); err != nil { + if err := createStackImage(dockerCli, buildImage, filepath.Join(stackBaseDir, "build")); err != nil { return err } @@ -1991,6 +2012,14 @@ func fillTemplate(t *testing.T, templatePath string, data map[string]interface{} return expectedOutput.String() } +func dockerHostOS() string { + daemonInfo, err := dockerCli.Info(context.TODO()) + if err != nil { + panic(err.Error()) + } + return daemonInfo.OSType +} + // taskKey creates a key from the prefix and all arguments to be unique func taskKey(prefix string, args ...string) string { hash := sha256.New() diff --git a/acceptance/testdata/mock_stack/create-stack.sh b/acceptance/testdata/mock_stack/create-stack.sh index 2a23059433..e27cb19bf8 100755 --- a/acceptance/testdata/mock_stack/create-stack.sh +++ b/acceptance/testdata/mock_stack/create-stack.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash dir="$(cd $(dirname $0) && pwd)" +os=$(docker info --format '{{json .}}' | jq -r .OSType) -docker build --tag pack-test/build "$dir"/build -docker build --tag pack-test/run "$dir"/run +docker build --tag pack-test/build "$dir"/$os/build +docker build --tag pack-test/run "$dir"/$os/run diff --git a/acceptance/testdata/mock_stack/build/Dockerfile b/acceptance/testdata/mock_stack/linux/build/Dockerfile similarity index 100% rename from acceptance/testdata/mock_stack/build/Dockerfile rename to acceptance/testdata/mock_stack/linux/build/Dockerfile diff --git a/acceptance/testdata/mock_stack/run/Dockerfile b/acceptance/testdata/mock_stack/linux/run/Dockerfile similarity index 100% rename from acceptance/testdata/mock_stack/run/Dockerfile rename to acceptance/testdata/mock_stack/linux/run/Dockerfile diff --git a/acceptance/testdata/mock_stack/windows/build/Dockerfile b/acceptance/testdata/mock_stack/windows/build/Dockerfile new file mode 100644 index 0000000000..0c0e063e23 --- /dev/null +++ b/acceptance/testdata/mock_stack/windows/build/Dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/windows/nanoserver:1809 + +# placeholder values until correct values are deteremined +ENV CNB_USER_ID=0 +ENV CNB_GROUP_ID=0 + +USER ContainerAdministrator + +RUN net users /ADD pack /passwordreq:no /expires:never + +LABEL io.buildpacks.stack.id=pack.test.stack +LABEL io.buildpacks.stack.mixins="[\"mixinA\", \"build:mixinTwo\", \"netcat\", \"mixin3\"]" + +USER pack diff --git a/acceptance/testdata/mock_stack/windows/run/Dockerfile b/acceptance/testdata/mock_stack/windows/run/Dockerfile new file mode 100644 index 0000000000..be4328aa4e --- /dev/null +++ b/acceptance/testdata/mock_stack/windows/run/Dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/windows/nanoserver:1809 + +# placeholder values until correct values are deteremined +ENV CNB_USER_ID=0 +ENV CNB_GROUP_ID=0 + +USER ContainerAdministrator + +RUN net users /ADD pack /passwordreq:no /expires:never + +LABEL io.buildpacks.stack.id=pack.test.stack +LABEL io.buildpacks.stack.mixins="[\"mixinA\", \"netcat\", \"mixin3\"]" + +USER pack diff --git a/acceptance/testdata/pack_fixtures/.gitattributes b/acceptance/testdata/pack_fixtures/.gitattributes new file mode 100644 index 0000000000..f9e337fe21 --- /dev/null +++ b/acceptance/testdata/pack_fixtures/.gitattributes @@ -0,0 +1 @@ +*.txt text eol=lf diff --git a/acceptance/testdata/pack_fixtures/builder.toml b/acceptance/testdata/pack_fixtures/builder.toml index e895fde4c9..afd2e7d036 100644 --- a/acceptance/testdata/pack_fixtures/builder.toml +++ b/acceptance/testdata/pack_fixtures/builder.toml @@ -11,13 +11,17 @@ # noop-buildpack-2 has the same id but a different version compared to noop-buildpack uri = "noop-buildpack-2.tgz" +{{- if .package_image_name}} [[buildpacks]] - image = "{{ .package_name }}" + image = "{{.package_image_name}}" +{{- end}} [[order]] +{{- if .package_id}} [[order.group]] - id = "simple/layers" + id = "{{.package_id}}" # intentionlly missing version to test support +{{- end}} [[order.group]] id = "read/env" @@ -28,5 +32,12 @@ id = "pack.test.stack" build-image = "pack-test/build" run-image = "pack-test/run" + run-image-mirrors = ["{{.run_image_mirror}}"] -# run-image-mirror and lifecycle are appended by acceptance tests +[lifecycle] +{{- if .lifecycle_uri}} + uri = "{{.lifecycle_uri}}" +{{- end}} +{{- if .lifecycle_version}} + version = "{{.lifecycle_version}}" +{{- end}} diff --git a/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml b/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml new file mode 100644 index 0000000000..afd2e7d036 --- /dev/null +++ b/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml @@ -0,0 +1,43 @@ +[[buildpacks]] + id = "read/env" + version = "read-env-version" + uri = "read-env-buildpack.tgz" + +[[buildpacks]] + # intentionally missing id/version as they are optional + uri = "noop-buildpack.tgz" + +[[buildpacks]] + # noop-buildpack-2 has the same id but a different version compared to noop-buildpack + uri = "noop-buildpack-2.tgz" + +{{- if .package_image_name}} +[[buildpacks]] + image = "{{.package_image_name}}" +{{- end}} + +[[order]] +{{- if .package_id}} +[[order.group]] + id = "{{.package_id}}" + # intentionlly missing version to test support +{{- end}} + +[[order.group]] + id = "read/env" + version = "read-env-version" + optional = true + +[stack] + id = "pack.test.stack" + build-image = "pack-test/build" + run-image = "pack-test/run" + run-image-mirrors = ["{{.run_image_mirror}}"] + +[lifecycle] +{{- if .lifecycle_uri}} + uri = "{{.lifecycle_uri}}" +{{- end}} +{{- if .lifecycle_version}} + version = "{{.lifecycle_version}}" +{{- end}} diff --git a/build.go b/build.go index 80c5f22ea0..84b50d1a5d 100644 --- a/build.go +++ b/build.go @@ -26,6 +26,7 @@ import ( "github.com/buildpacks/pack/internal/buildpack" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" + "github.com/buildpacks/pack/internal/layer" "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/stack" "github.com/buildpacks/pack/internal/stringset" @@ -104,7 +105,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return err } - fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Buildpacks(), bldr.Order(), opts.Buildpacks, opts.NoPull, opts.Publish, opts.Registry) + fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), opts.Buildpacks, opts.NoPull, opts.Publish, opts.Registry) if err != nil { return err } @@ -396,7 +397,7 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig { // ---------- // - group: // - A -func (c *Client) processBuildpacks(ctx context.Context, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, declaredBPs []string, noPull bool, publish bool, registry string) (fetchedBPs []dist.Buildpack, order dist.Order, err error) { +func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, declaredBPs []string, noPull bool, publish bool, registry string) (fetchedBPs []dist.Buildpack, order dist.Order, err error) { order = dist.Order{{Group: []dist.BuildpackRef{}}} for _, bp := range declaredBPs { locatorType, err := buildpack.GetLocatorType(bp, builderBPs) @@ -453,7 +454,11 @@ func (c *Client) processBuildpacks(ctx context.Context, builderBPs []dist.Buildp return fetchedBPs, order, errors.Wrapf(err, "extracting buildpacks from %s", style.Symbol(bp)) } } else { - mainBP, err = dist.BuildpackFromRootBlob(blob) + layerWriterFactory, err := layer.NewWriterFactory(builderImage) + if err != nil { + return fetchedBPs, order, errors.Wrapf(err, "get tar writer factory for image %s", style.Symbol(builderImage.Name())) + } + mainBP, err = dist.BuildpackFromRootBlob(blob, layerWriterFactory) if err != nil { return fetchedBPs, order, errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bp)) } diff --git a/create_builder.go b/create_builder.go index 4bf8044748..ef08299758 100644 --- a/create_builder.go +++ b/create_builder.go @@ -14,6 +14,7 @@ import ( "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/image" + "github.com/buildpacks/pack/internal/layer" "github.com/buildpacks/pack/internal/style" ) @@ -101,7 +102,11 @@ func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) e return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(b.URI)) } - fetchedBp, err := dist.BuildpackFromRootBlob(blob) + layerWriterFactory, err := layer.NewWriterFactory(bldr.Image()) + if err != nil { + return errors.Wrapf(err, "get tar writer factory for image %s", style.Symbol(bldr.Name())) + } + fetchedBp, err := dist.BuildpackFromRootBlob(blob, layerWriterFactory) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(b.URI)) } diff --git a/go.mod b/go.mod index 6f871ae21e..625336c6fa 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/apex/log v1.1.2 - github.com/buildpacks/imgutil v0.0.0-20200313170640-a02052f47d62 - github.com/buildpacks/lifecycle v0.7.1 + github.com/buildpacks/imgutil v0.0.0-20200424215026-dfdc82949704 + github.com/buildpacks/lifecycle v0.7.2 github.com/containerd/containerd v1.3.3 // indirect github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41 // indirect github.com/dgodd/dockerdial v1.0.1 diff --git a/go.sum b/go.sum index 08e0203eeb..f55571dd7e 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,10 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/buildpacks/imgutil v0.0.0-20200313170640-a02052f47d62 h1:jo6zuwPgCWD/V5YQEgaRKeW/uxy4fhFO9FsFWmKCtdQ= github.com/buildpacks/imgutil v0.0.0-20200313170640-a02052f47d62/go.mod h1:TjPmM78urjQIiMal4T7en6iBekAPv6z1yVMZobc4Kd8= -github.com/buildpacks/lifecycle v0.7.1 h1:cfVdxGJJqAoK55kkxTA5HxHPmUNp0NuGgDzRl5f7R/E= -github.com/buildpacks/lifecycle v0.7.1/go.mod h1:k41tT3XOt7ufaMGAvOpEsXyuJpUPoo4F686Z652lU3E= +github.com/buildpacks/imgutil v0.0.0-20200424215026-dfdc82949704 h1:ppE+Fdjl4fAHb2Ik+iUPPV5mbSu2M8IM8Cz7M37Vlu0= +github.com/buildpacks/imgutil v0.0.0-20200424215026-dfdc82949704/go.mod h1:5AWk59DdFQopBvOPhpx0tRum31cIjO1Ln/kABKFOzKQ= +github.com/buildpacks/lifecycle v0.7.2 h1:FO7i2cokLNc7lcuThq/LYt1jmkB8HBrjpK+2GWWsaLI= +github.com/buildpacks/lifecycle v0.7.2/go.mod h1:k41tT3XOt7ufaMGAvOpEsXyuJpUPoo4F686Z652lU3E= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -460,8 +462,9 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/internal/archive/archive.go b/internal/archive/archive.go index 1afeeeb9f7..ef68b957f5 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -21,27 +21,51 @@ func init() { NormalizedDateTime = time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC) } +type TarWriter interface { + WriteHeader(hdr *tar.Header) error + Write(b []byte) (int, error) + Close() error +} + +type TarWriterFactory interface { + NewWriter(io.Writer) TarWriter +} + +type defaultTarWriterFactory struct{} + +func DefaultTarWriterFactory() TarWriterFactory { + return defaultTarWriterFactory{} +} + +func (defaultTarWriterFactory) NewWriter(w io.Writer) TarWriter { + return tar.NewWriter(w) +} + func ReadDirAsTar(srcDir, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) io.ReadCloser { - return GenerateTar(func(tw *tar.Writer) error { + return GenerateTar(func(tw TarWriter) error { return WriteDirToTar(tw, srcDir, basePath, uid, gid, mode, normalizeModTime, fileFilter) }) } func ReadZipAsTar(srcPath, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) io.ReadCloser { - return GenerateTar(func(tw *tar.Writer) error { + return GenerateTar(func(tw TarWriter) error { return WriteZipToTar(tw, srcPath, basePath, uid, gid, mode, normalizeModTime, fileFilter) }) } -// GenerateTar returns a reader to a tar from a generator function. Note that the -// generator will not fully execute until the reader is fully read from. Any errors -// returned by the generator will be returned when reading the reader. -func GenerateTar(gen func(*tar.Writer) error) io.ReadCloser { +func GenerateTar(genFn func(TarWriter) error) io.ReadCloser { + return GenerateTarWithWriter(genFn, DefaultTarWriterFactory()) +} + +// GenerateTarWithTar returns a reader to a tar from a generator function using a writer from the provided factory. +// Note that the generator will not fully execute until the reader is fully read from. Any errors returned by the +// generator will be returned when reading the reader. +func GenerateTarWithWriter(genFn func(TarWriter) error, twf TarWriterFactory) io.ReadCloser { errChan := make(chan error) pr, pw := io.Pipe() go func() { - tw := tar.NewWriter(pw) + tw := twf.NewWriter(pw) defer func() { if r := recover(); r != nil { tw.Close() @@ -49,7 +73,7 @@ func GenerateTar(gen func(*tar.Writer) error) io.ReadCloser { } }() - err := gen(tw) + err := genFn(tw) closeErr := tw.Close() closeErr = aggregateError(closeErr, pw.CloseWithError(err)) @@ -87,37 +111,16 @@ func aggregateError(base, addition error) error { return errors.Wrap(addition, base.Error()) } -func CreateSingleFileTarReader(path, txt string) (io.Reader, error) { - var buf bytes.Buffer +func CreateSingleFileTarReader(path, txt string) io.ReadCloser { tarBuilder := TarBuilder{} tarBuilder.AddFile(path, 0644, NormalizedDateTime, []byte(txt)) - _, err := tarBuilder.WriteTo(&buf) - if err != nil { - return nil, err - } - - return bytes.NewReader(buf.Bytes()), nil + return tarBuilder.Reader(DefaultTarWriterFactory()) } func CreateSingleFileTar(tarFile, path, txt string) error { tarBuilder := TarBuilder{} tarBuilder.AddFile(path, 0644, NormalizedDateTime, []byte(txt)) - return tarBuilder.WriteToPath(tarFile) -} - -func AddFileToTar(tw *tar.Writer, path string, txt string) error { - if err := tw.WriteHeader(&tar.Header{ - Name: path, - Size: int64(len(txt)), - Mode: 0644, - ModTime: NormalizedDateTime, - }); err != nil { - return err - } - if _, err := tw.Write([]byte(txt)); err != nil { - return err - } - return nil + return tarBuilder.WriteToPath(tarFile, DefaultTarWriterFactory()) } // ErrEntryNotExist is an error returned if an entry path doesn't exist @@ -155,7 +158,7 @@ func ReadTarEntry(rc io.Reader, entryPath string) (*tar.Header, []byte, error) { // WriteDirToTar writes the contents of a directory to a tar writer. `basePath` is the "location" in the tar the // contents will be placed. -func WriteDirToTar(tw *tar.Writer, srcDir, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) error { +func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) error { return filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error { if fileFilter != nil && !fileFilter(file) { return nil @@ -217,7 +220,7 @@ func WriteDirToTar(tw *tar.Writer, srcDir, basePath string, uid, gid int, mode i } // WriteZipToTar writes the contents of a zip file to a tar writer. -func WriteZipToTar(tw *tar.Writer, srcZip, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) error { +func WriteZipToTar(tw TarWriter, srcZip, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) error { zipReader, err := zip.OpenReader(srcZip) if err != nil { return err diff --git a/internal/archive/tar_builder.go b/internal/archive/tar_builder.go index a2bfd96237..9659d3c39d 100644 --- a/internal/archive/tar_builder.go +++ b/internal/archive/tar_builder.go @@ -42,35 +42,35 @@ func (t *TarBuilder) AddDir(path string, mode int64, modTime time.Time) { }) } -func (t *TarBuilder) Reader() io.ReadCloser { +func (t *TarBuilder) Reader(twf TarWriterFactory) io.ReadCloser { pr, pw := io.Pipe() go func() { var err error defer func() { pw.CloseWithError(err) }() - _, err = t.WriteTo(pw) + _, err = t.WriteTo(pw, twf) }() return pr } -func (t *TarBuilder) WriteToPath(path string) error { +func (t *TarBuilder) WriteToPath(path string, twf TarWriterFactory) error { fh, err := os.Create(path) if err != nil { return errors.Wrapf(err, "create file for tar: %s", style.Symbol(path)) } defer fh.Close() - _, err = t.WriteTo(fh) + _, err = t.WriteTo(fh, twf) return err } -func (t *TarBuilder) WriteTo(writer io.Writer) (int64, error) { - tw := tar.NewWriter(writer) +func (t *TarBuilder) WriteTo(w io.Writer, twf TarWriterFactory) (int64, error) { + var written int64 + tw := twf.NewWriter(w) defer tw.Close() - var written int64 for _, f := range t.files { if err := tw.WriteHeader(&tar.Header{ Typeflag: f.typeFlag, diff --git a/internal/build/phase_test.go b/internal/build/phase_test.go index 904c25a277..654d9cf40d 100644 --- a/internal/build/phase_test.go +++ b/internal/build/phase_test.go @@ -50,6 +50,10 @@ func TestPhase(t *testing.T) { dockerCli, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) h.AssertNil(t, err) + info, err := dockerCli.Info(context.TODO()) + h.AssertNil(t, err) + h.SkipIf(t, info.OSType == "windows", "These tests are not yet compatible with Windows-based containers") + repoName = "phase.test.lc-" + h.RandString(10) wd, err := os.Getwd() h.AssertNil(t, err) diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 5c6052f10b..739ea3ae74 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -22,6 +22,7 @@ import ( "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/dist" + "github.com/buildpacks/pack/internal/layer" "github.com/buildpacks/pack/internal/stack" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/logging" @@ -50,6 +51,7 @@ const ( type Builder struct { baseImageName string image imgutil.Image + layerWriterFactory archive.TarWriterFactory lifecycle Lifecycle lifecycleDescriptor LifecycleDescriptor additionalBuildpacks []dist.Buildpack @@ -87,6 +89,11 @@ func New(baseImage imgutil.Image, name string) (*Builder, error) { } func constructBuilder(img imgutil.Image, newName string, metadata Metadata) (*Builder, error) { + layerWriterFactory, err := layer.NewWriterFactory(img) + if err != nil { + return nil, err + } + uid, gid, err := userAndGroupIDs(img) if err != nil { return nil, err @@ -131,14 +138,15 @@ func constructBuilder(img imgutil.Image, newName string, metadata Metadata) (*Bu } return &Builder{ - baseImageName: baseName, - image: img, - metadata: metadata, - mixins: mixins, - order: order, - uid: uid, - gid: gid, - StackID: stackID, + baseImageName: baseName, + image: img, + layerWriterFactory: layerWriterFactory, + metadata: metadata, + mixins: mixins, + order: order, + uid: uid, + gid: gid, + StackID: stackID, lifecycleDescriptor: LifecycleDescriptor{ Info: LifecycleInfo{ Version: lifecycleVersion, @@ -485,32 +493,32 @@ func (b *Builder) defaultDirsLayer(dest string) (string, error) { } defer fh.Close() - tw := tar.NewWriter(fh) - defer tw.Close() + lw := b.layerWriterFactory.NewWriter(fh) + defer lw.Close() ts := archive.NormalizedDateTime - if err := tw.WriteHeader(b.packOwnedDir(workspaceDir, ts)); err != nil { + if err := lw.WriteHeader(b.packOwnedDir(workspaceDir, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(workspaceDir)) } - if err := tw.WriteHeader(b.packOwnedDir(layersDir, ts)); err != nil { + if err := lw.WriteHeader(b.packOwnedDir(layersDir, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(layersDir)) } - if err := tw.WriteHeader(b.rootOwnedDir(cnbDir, ts)); err != nil { + if err := lw.WriteHeader(b.rootOwnedDir(cnbDir, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(cnbDir)) } - if err := tw.WriteHeader(b.rootOwnedDir(dist.BuildpacksDir, ts)); err != nil { + if err := lw.WriteHeader(b.rootOwnedDir(dist.BuildpacksDir, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(dist.BuildpacksDir)) } - if err := tw.WriteHeader(b.rootOwnedDir(platformDir, ts)); err != nil { + if err := lw.WriteHeader(b.rootOwnedDir(platformDir, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(platformDir)) } - if err := tw.WriteHeader(b.rootOwnedDir(platformDir+"/env", ts)); err != nil { + if err := lw.WriteHeader(b.rootOwnedDir(platformDir+"/env", ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(platformDir+"/env")) } @@ -544,7 +552,7 @@ func (b *Builder) orderLayer(order dist.Order, dest string) (string, error) { } layerTar := filepath.Join(dest, "order.tar") - err = archive.CreateSingleFileTar(layerTar, orderPath, contents) + err = layer.CreateSingleFileTar(layerTar, orderPath, contents, b.layerWriterFactory) if err != nil { return "", errors.Wrapf(err, "failed to create order.toml layer tar") } @@ -570,7 +578,7 @@ func (b *Builder) stackLayer(dest string) (string, error) { } layerTar := filepath.Join(dest, "stack.tar") - err = archive.CreateSingleFileTar(layerTar, stackPath, buf.String()) + err = layer.CreateSingleFileTar(layerTar, stackPath, buf.String(), b.layerWriterFactory) if err != nil { return "", errors.Wrapf(err, "failed to create stack.toml layer tar") } @@ -578,7 +586,7 @@ func (b *Builder) stackLayer(dest string) (string, error) { return layerTar, nil } -func (b *Builder) embedLifecycleTar(tw *tar.Writer) error { +func (b *Builder) embedLifecycleTar(tw archive.TarWriter) error { var regex = regexp.MustCompile(`^[^/]+/([^/]+)$`) lr, err := b.lifecycle.Open() @@ -628,11 +636,11 @@ func (b *Builder) envLayer(dest string, env map[string]string) (string, error) { } defer fh.Close() - tw := tar.NewWriter(fh) - defer tw.Close() + lw := b.layerWriterFactory.NewWriter(fh) + defer lw.Close() for k, v := range env { - if err := tw.WriteHeader(&tar.Header{ + if err := lw.WriteHeader(&tar.Header{ Name: path.Join(platformDir, "env", k), Size: int64(len(v)), Mode: 0644, @@ -640,7 +648,7 @@ func (b *Builder) envLayer(dest string, env map[string]string) (string, error) { }); err != nil { return "", err } - if _, err := tw.Write([]byte(v)); err != nil { + if _, err := lw.Write([]byte(v)); err != nil { return "", err } } @@ -655,10 +663,10 @@ func (b *Builder) lifecycleLayer(dest string) (string, error) { } defer fh.Close() - tw := tar.NewWriter(fh) - defer tw.Close() + lw := b.layerWriterFactory.NewWriter(fh) + defer lw.Close() - if err := tw.WriteHeader(&tar.Header{ + if err := lw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, Name: lifecycleDir, Mode: 0755, @@ -667,12 +675,12 @@ func (b *Builder) lifecycleLayer(dest string) (string, error) { return "", err } - err = b.embedLifecycleTar(tw) + err = b.embedLifecycleTar(lw) if err != nil { return "", errors.Wrap(err, "embedding lifecycle tar") } - if err := tw.WriteHeader(&tar.Header{ + if err := lw.WriteHeader(&tar.Header{ Name: compatLifecycleDir, Linkname: lifecycleDir, Typeflag: tar.TypeSymlink, diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index 4a72c9285d..bf2c7a1970 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -318,11 +318,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { defer os.RemoveAll(tmpDir) layerFile := filepath.Join(tmpDir, "order.tar") - f, err := os.Create(layerFile) - h.AssertNil(t, err) - defer f.Close() - - err = archive.CreateSingleFileTar(f.Name(), "/cnb/order.toml", "some content") + err = archive.CreateSingleFileTar(layerFile, "/cnb/order.toml", "some content") h.AssertNil(t, err) h.AssertNil(t, baseImage.AddLayer(layerFile)) diff --git a/internal/cache/image_cache_test.go b/internal/cache/image_cache_test.go index 13f24cf1ac..b55c857a53 100644 --- a/internal/cache/image_cache_test.go +++ b/internal/cache/image_cache_test.go @@ -2,11 +2,12 @@ package cache_test import ( "context" - "fmt" "math/rand" "testing" "time" + "github.com/buildpacks/imgutil/local" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" @@ -120,10 +121,10 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) { when("there is a cache image", func() { it.Before(func() { - h.CreateImage(t, dockerClient, imageName, fmt.Sprintf(` -FROM busybox -LABEL repo_name_for_randomisation=%s -`, imageName)) + img, err := local.NewImage(imageName, dockerClient) + h.AssertNil(t, err) + + h.AssertNil(t, img.Save()) }) it("removes the image", func() { @@ -131,8 +132,8 @@ LABEL repo_name_for_randomisation=%s h.AssertNil(t, err) images, err := dockerClient.ImageList(context.TODO(), types.ImageListOptions{ Filters: filters.NewArgs(filters.KeyValuePair{ - Key: "label", - Value: "repo_name_for_randomisation=" + imageName, + Key: "reference", + Value: imageName, }), }) h.AssertNil(t, err) diff --git a/internal/dist/buildpack.go b/internal/dist/buildpack.go index 54751652fa..74a5d2cf5b 100644 --- a/internal/dist/buildpack.go +++ b/internal/dist/buildpack.go @@ -77,7 +77,7 @@ func BuildpackFromBlob(bpd BuildpackDescriptor, blob Blob) Buildpack { // BuildpackFromRootBlob constructs a buildpack from a blob. It is assumed that the buildpack contents reside at the // root of the blob. The constructed buildpack contents will be structured as per the distribution spec (currently // a tar with contents under '/cnbs/buildpacks/{ID}/{version}/*'). -func BuildpackFromRootBlob(blob Blob) (Buildpack, error) { +func BuildpackFromRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactory) (Buildpack, error) { bpd := BuildpackDescriptor{} rc, err := blob.Open() if err != nil { @@ -105,9 +105,12 @@ func BuildpackFromRootBlob(blob Blob) (Buildpack, error) { descriptor: bpd, Blob: &distBlob{ openFn: func() io.ReadCloser { - return archive.GenerateTar(func(tw *tar.Writer) error { - return toDistTar(tw, bpd, blob) - }) + return archive.GenerateTarWithWriter( + func(tw archive.TarWriter) error { + return toDistTar(tw, bpd, blob) + }, + layerWriterFactory, + ) }, }, }, nil @@ -121,7 +124,7 @@ func (b *distBlob) Open() (io.ReadCloser, error) { return b.openFn(), nil } -func toDistTar(tw *tar.Writer, bpd BuildpackDescriptor, blob Blob) error { +func toDistTar(tw archive.TarWriter, bpd BuildpackDescriptor, blob Blob) error { ts := archive.NormalizedDateTime if err := tw.WriteHeader(&tar.Header{ diff --git a/internal/dist/buildpack_test.go b/internal/dist/buildpack_test.go index 1465814234..702c733818 100644 --- a/internal/dist/buildpack_test.go +++ b/internal/dist/buildpack_test.go @@ -49,10 +49,11 @@ func testBuildpack(t *testing.T, when spec.G, it spec.S) { when("#BuildpackFromRootBlob", func() { it("parses the descriptor file", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.3" [buildpack] @@ -63,9 +64,11 @@ homepage = "http://geocities.com/cool-bp" [[stacks]] id = "some.stack.id" `)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) h.AssertEq(t, bp.Descriptor().API.String(), "0.3") @@ -76,10 +79,11 @@ id = "some.stack.id" }) it("translates blob to distribution format", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.3" [buildpack] @@ -90,12 +94,14 @@ version = "1.2.3" id = "some.stack.id" `)) - tarBuilder.AddDir("bin", 0700, time.Now()) - tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build", 0700, time.Now(), []byte("build-contents")) - return tarBuilder.Reader() + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -151,13 +157,16 @@ version = "1.2.3" [[stacks]] id = "some.stack.id" `)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, } - bp, err := dist.BuildpackFromRootBlob(&errorBlob{ - realBlob: realBlob, - }) + bp, err := dist.BuildpackFromRootBlob( + &errorBlob{ + realBlob: realBlob, + }, + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) bpReader, err := bp.Open() @@ -181,14 +190,17 @@ id = "some.stack.id" when("no exec bits set", func() { it("sets to 0755 if directory", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddDir("some-dir", 0600, time.Now()) - return tarBuilder.Reader() + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddDir("some-dir", 0600, time.Now()) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -203,15 +215,18 @@ id = "some.stack.id" when("no exec bits set", func() { it("sets to 0755 if 'bin/detect' or 'bin/build'", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("bin/detect", 0600, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build", 0600, time.Now(), []byte("build-contents")) - return tarBuilder.Reader() + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("bin/detect", 0600, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build", 0600, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -231,14 +246,17 @@ id = "some.stack.id" when("not directory, 'bin/detect', or 'bin/build'", func() { it("sets to 0755 if ANY exec bit is set", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("some-file", 0700, time.Now(), []byte("some-data")) - return tarBuilder.Reader() + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("some-file", 0700, time.Now(), []byte("some-data")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -253,14 +271,17 @@ id = "some.stack.id" when("not directory, 'bin/detect', or 'bin/build'", func() { it("sets to 0644 if NO exec bits set", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("some-file", 0600, time.Now(), []byte("some-data")) - return tarBuilder.Reader() + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("some-file", 0600, time.Now(), []byte("some-data")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -276,31 +297,37 @@ id = "some.stack.id" when("there is no descriptor file", func() { it("returns error", func() { - _, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - return tarBuilder.Reader() + _, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertError(t, err, "could not find entry path 'buildpack.toml'") }) }) when("there is no api field", func() { it("assumes an api version", func() { - bp, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertNil(t, err) h.AssertEq(t, bp.Descriptor().API.String(), "0.1") }) @@ -308,48 +335,55 @@ id = "some.stack.id"`)) when("there is no id", func() { it("returns error", func() { - _, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "" version = "1.2.3" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertError(t, err, "'buildpack.id' is required") }) }) when("there is no version", func() { it("returns error", func() { - _, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertError(t, err, "'buildpack.version' is required") }) }) when("both stacks and order are present", func() { it("returns error", func() { - _, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" @@ -362,26 +396,31 @@ id = "some.stack.id" id = "bp.nested" version = "bp.nested.version" `)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertError(t, err, "cannot have both 'stacks' and an 'order' defined") }) }) when("missing stacks and order", func() { it("returns error", func() { - _, err := dist.BuildpackFromRootBlob(&readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := dist.BuildpackFromRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" `)) - return tarBuilder.Reader() + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, }, - }) + archive.DefaultTarWriterFactory(), + ) h.AssertError(t, err, "must have either 'stacks' or an 'order' defined") }) }) diff --git a/internal/fakes/fake_buildpack.go b/internal/fakes/fake_buildpack.go index ea635d6515..51f0a0d41d 100644 --- a/internal/fakes/fake_buildpack.go +++ b/internal/fakes/fake_buildpack.go @@ -56,5 +56,5 @@ func (b *fakeBuildpack) Open() (io.ReadCloser, error) { tarBuilder.AddFile(bpDir+"/bin/detect", b.chmod, ts, []byte("detect-contents")) } - return tarBuilder.Reader(), nil + return tarBuilder.Reader(archive.DefaultTarWriterFactory()), nil } diff --git a/internal/fakes/fake_buildpack_blob.go b/internal/fakes/fake_buildpack_blob.go index ad43950cd4..76cc0fef4c 100644 --- a/internal/fakes/fake_buildpack_blob.go +++ b/internal/fakes/fake_buildpack_blob.go @@ -44,5 +44,5 @@ func (b *fakeBuildpackBlob) Open() (reader io.ReadCloser, err error) { tarBuilder.AddFile("bin/build", b.chmod, time.Now(), []byte("build-contents")) tarBuilder.AddFile("bin/detect", b.chmod, time.Now(), []byte("detect-contents")) - return tarBuilder.Reader(), err + return tarBuilder.Reader(archive.DefaultTarWriterFactory()), err } diff --git a/internal/fakes/fake_images.go b/internal/fakes/fake_images.go index 169bc05720..45e9063ace 100644 --- a/internal/fakes/fake_images.go +++ b/internal/fakes/fake_images.go @@ -56,7 +56,7 @@ func NewFakeBuilderImage(t *testing.T, tmpDir, name string, stackID, uid, gid st tarBuilder.AddFile("/cnb/order.toml", 0777, archive.NormalizedDateTime, orderTomlBytes.Bytes()) orderTar := filepath.Join(tmpDir, fmt.Sprintf("order.%s.toml", h.RandString(8))) - h.AssertNil(t, tarBuilder.WriteToPath(orderTar)) + h.AssertNil(t, tarBuilder.WriteToPath(orderTar, archive.DefaultTarWriterFactory())) h.AssertNil(t, fakeBuilderImage.AddLayer(orderTar)) return fakeBuilderImage diff --git a/internal/image/fetcher_test.go b/internal/image/fetcher_test.go index cdf855d1ff..39a1e3953d 100644 --- a/internal/image/fetcher_test.go +++ b/internal/image/fetcher_test.go @@ -9,6 +9,10 @@ import ( "testing" "time" + "github.com/buildpacks/imgutil/local" + "github.com/buildpacks/imgutil/remote" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/docker/docker/client" "github.com/heroku/color" "github.com/sclevine/spec" @@ -59,22 +63,15 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { when("daemon is false", func() { when("there is a remote image", func() { it.Before(func() { - h.CreateImageOnRemote( - t, - docker, - registryConfig, - repo, - "FROM scratch\nLABEL repo_name="+repoName, - ) + img, err := remote.NewImage(repoName, authn.DefaultKeychain) + h.AssertNil(t, err) + + h.AssertNil(t, img.Save()) }) it("returns the remote image", func() { - img, err := fetcher.Fetch(context.TODO(), repoName, false, false) - h.AssertNil(t, err) - - label, err := img.Label("repo_name") + _, err := fetcher.Fetch(context.TODO(), repoName, false, false) h.AssertNil(t, err) - h.AssertEq(t, label, repoName) }) }) @@ -95,12 +92,10 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { // when there's a valid local image. repoName = "invalidhost" + repoName - h.CreateImage( - t, - docker, - repoName, - "FROM scratch\nLABEL repo_name="+repoName, - ) + img, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + + h.AssertNil(t, img.Save()) }) it.After(func() { @@ -108,12 +103,8 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { }) it("returns the local image", func() { - img, err := fetcher.Fetch(context.TODO(), repoName, true, false) - h.AssertNil(t, err) - - label, err := img.Label("repo_name") + _, err := fetcher.Fetch(context.TODO(), repoName, true, false) h.AssertNil(t, err) - h.AssertEq(t, label, repoName) }) }) @@ -128,13 +119,16 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { when("pull is true", func() { when("there is a remote image", func() { it.Before(func() { - h.CreateImageOnRemote( - t, - docker, - registryConfig, - repo, - "FROM scratch\nLABEL repo_name="+repoName, - ) + // Instantiate a pull-able local image + // as opposed to a remote image so that the image + // is created with the OS of the docker daemon + img, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + defer h.DockerRmi(docker, repoName) + + h.AssertNil(t, img.Save()) + + h.AssertNil(t, h.PushImage(docker, img.Name(), registryConfig)) }) it.After(func() { @@ -142,24 +136,18 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { }) it("pull the image and return the local copy", func() { - img, err := fetcher.Fetch(context.TODO(), repoName, true, true) - h.AssertNil(t, err) - - label, err := img.Label("repo_name") + _, err := fetcher.Fetch(context.TODO(), repoName, true, true) h.AssertNil(t, err) - h.AssertEq(t, label, repoName) }) }) when("there is no remote image", func() { when("there is a local image", func() { it.Before(func() { - h.CreateImage( - t, - docker, - repoName, - "FROM scratch\nLABEL repo_name="+repoName, - ) + img, err := local.NewImage(repoName, docker) + h.AssertNil(t, err) + + h.AssertNil(t, img.Save()) }) it.After(func() { @@ -167,12 +155,8 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { }) it("returns the local image", func() { - img, err := fetcher.Fetch(context.TODO(), repoName, true, true) - h.AssertNil(t, err) - - label, err := img.Label("repo_name") + _, err := fetcher.Fetch(context.TODO(), repoName, true, true) h.AssertNil(t, err) - h.AssertEq(t, label, repoName) }) }) diff --git a/internal/layer/layer.go b/internal/layer/layer.go new file mode 100644 index 0000000000..6855185d27 --- /dev/null +++ b/internal/layer/layer.go @@ -0,0 +1,11 @@ +package layer + +import ( + "github.com/buildpacks/pack/internal/archive" +) + +func CreateSingleFileTar(tarFile, path, txt string, twf archive.TarWriterFactory) error { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile(path, 0644, archive.NormalizedDateTime, []byte(txt)) + return tarBuilder.WriteToPath(tarFile, twf) +} diff --git a/internal/layer/writer_factory.go b/internal/layer/writer_factory.go new file mode 100644 index 0000000000..4faa116531 --- /dev/null +++ b/internal/layer/writer_factory.go @@ -0,0 +1,33 @@ +package layer + +import ( + "archive/tar" + "io" + + "github.com/buildpacks/imgutil" + ilayer "github.com/buildpacks/imgutil/layer" + + "github.com/buildpacks/pack/internal/archive" +) + +type WriterFactory struct { + os string +} + +func NewWriterFactory(image imgutil.Image) (*WriterFactory, error) { + os, err := image.OS() + if err != nil { + return nil, err + } + + return &WriterFactory{os: os}, nil +} + +func (f *WriterFactory) NewWriter(fileWriter io.Writer) archive.TarWriter { + if f.os == "windows" { + return ilayer.NewWindowsWriter(fileWriter) + } + + // Linux images use tar.Writer + return tar.NewWriter(fileWriter) +} diff --git a/internal/layer/writer_factory_test.go b/internal/layer/writer_factory_test.go new file mode 100644 index 0000000000..df1074702a --- /dev/null +++ b/internal/layer/writer_factory_test.go @@ -0,0 +1,46 @@ +package layer_test + +import ( + "archive/tar" + "testing" + + "github.com/buildpacks/imgutil/fakes" + ilayer "github.com/buildpacks/imgutil/layer" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/internal/layer" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestTarWriterFactory(t *testing.T) { + spec.Run(t, "WriterFactory", testWriterFactory, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testWriterFactory(t *testing.T, when spec.G, it spec.S) { + when("#NewWriter", func() { + it("returns a regular tar writer for posix-based images", func() { + image := fakes.NewImage("fake-image", "", nil) + image.SetPlatform("linux", "", "") + factory, err := layer.NewWriterFactory(image) + h.AssertNil(t, err) + + _, ok := factory.NewWriter(nil).(*tar.Writer) + if !ok { + t.Fatal("returned writer was not a regular tar writer") + } + }) + + it("returns a Windows layer writer for Windows-based images", func() { + image := fakes.NewImage("fake-image", "", nil) + image.SetPlatform("windows", "", "") + factory, err := layer.NewWriterFactory(image) + h.AssertNil(t, err) + + _, ok := factory.NewWriter(nil).(*ilayer.WindowsWriter) + if !ok { + t.Fatal("returned writer was not a Windows layer writer") + } + }) + }) +} diff --git a/package_buildpack.go b/package_buildpack.go index 3270d4ea0f..712b7ae7fc 100644 --- a/package_buildpack.go +++ b/package_buildpack.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" pubbldpkg "github.com/buildpacks/pack/buildpackage" + "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/style" @@ -41,7 +42,7 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(bpURI)) } - bp, err := dist.BuildpackFromRootBlob(blob) + bp, err := dist.BuildpackFromRootBlob(blob, archive.DefaultTarWriterFactory()) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bpURI)) } @@ -55,7 +56,7 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti return errors.Wrapf(err, "downloading buildpack from %s", style.Symbol(dep.URI)) } - depBP, err := dist.BuildpackFromRootBlob(blob) + depBP, err := dist.BuildpackFromRootBlob(blob, archive.DefaultTarWriterFactory()) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(dep.URI)) } diff --git a/testhelpers/registry.go b/testhelpers/registry.go index d135201cc7..552ce422e0 100644 --- a/testhelpers/registry.go +++ b/testhelpers/registry.go @@ -1,7 +1,6 @@ package testhelpers import ( - "bytes" "context" "encoding/base64" "fmt" @@ -17,9 +16,10 @@ import ( dockertypes "github.com/docker/docker/api/types" dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" + "golang.org/x/crypto/bcrypt" ) -var registryContainerName = "registry:2" +var registryContainerName = "cnbs/registry:2" type TestRegistryConfig struct { runRegistryName string @@ -89,7 +89,8 @@ func startRegistry(t *testing.T, runRegistryName, username, password string) str AssertNil(t, PullImageWithAuth(dockerCli(t), registryContainerName, "")) ctx := context.Background() - htpasswdTar := generateHtpasswd(ctx, t, username, password) + htpasswdTar := generateHtpasswd(t, username, password) + defer htpasswdTar.Close() ctr, err := dockerCli(t).ContainerCreate(ctx, &dockercontainer.Config{ Image: registryContainerName, @@ -123,22 +124,11 @@ func startRegistry(t *testing.T, runRegistryName, username, password string) str return runRegistryPort } -func generateHtpasswd(ctx context.Context, t *testing.T, username string, password string) io.Reader { +func generateHtpasswd(t *testing.T, username string, password string) io.ReadCloser { // https://docs.docker.com/registry/deploying/#restricting-access - htpasswdCtr, err := dockerCli(t).ContainerCreate(ctx, &dockercontainer.Config{ - Image: registryContainerName, - Entrypoint: []string{"htpasswd", "-Bbn", username, password}, - }, &dockercontainer.HostConfig{ - AutoRemove: true, - }, nil, "") - AssertNil(t, err) - - var b bytes.Buffer - err = RunContainer(ctx, dockerCli(t), htpasswdCtr.ID, &b, &b) - AssertNil(t, err) - reader, err := archive.CreateSingleFileTarReader("/registry_test_htpasswd", b.String()) - AssertNil(t, err) - + // HTPASSWD format: https://github.com/foomo/htpasswd/blob/e3a90e78da9cff06a83a78861847aa9092cbebdd/hashing.go#L23 + passwordBytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + reader := archive.CreateSingleFileTarReader("/registry_test_htpasswd", username+":"+string(passwordBytes)) return reader } diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index d27ccec16a..fc517e92a1 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -272,8 +272,8 @@ func Eventually(t *testing.T, test func() bool, every time.Duration, timeout tim func CreateImage(t *testing.T, dockerCli client.CommonAPIClient, repoName, dockerFile string) { t.Helper() - buildContext, err := archive.CreateSingleFileTarReader("Dockerfile", dockerFile) - AssertNil(t, err) + buildContext := archive.CreateSingleFileTarReader("Dockerfile", dockerFile) + defer buildContext.Close() resp, err := dockerCli.ImageBuild(context.Background(), buildContext, dockertypes.ImageBuildOptions{ Tags: []string{repoName}, diff --git a/vendor/github.com/buildpacks/imgutil/.travis.yml b/vendor/github.com/buildpacks/imgutil/.travis.yml deleted file mode 100644 index 8eb081b7b5..0000000000 --- a/vendor/github.com/buildpacks/imgutil/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: go -go: 1.12.x -dist: trusty - -env: - global: - - GO111MODULE=on - -jobs: - include: - - os: linux - script: - - docker info - - make - -branches: - only: - - master diff --git a/vendor/github.com/buildpacks/imgutil/Makefile b/vendor/github.com/buildpacks/imgutil/Makefile index 68cf56c8bb..880c4db028 100644 --- a/vendor/github.com/buildpacks/imgutil/Makefile +++ b/vendor/github.com/buildpacks/imgutil/Makefile @@ -1,23 +1,24 @@ # Go parameters GOCMD?=go -GOTEST=$(GOCMD) test -mod=vendor PACKAGE_BASE=github.com/buildpacks/imgutil -PACKAGES:=$(shell $(GOCMD) list -mod=vendor ./... | grep -v /testdata/) -SRC:=$(shell find . -type f -name '*.go' -not -path "*/vendor/*") all: test install-goimports: @echo "> Installing goimports..." - cd tools; $(GOCMD) install -mod=vendor golang.org/x/tools/cmd/goimports + cd tools && $(GOCMD) install golang.org/x/tools/cmd/goimports format: install-goimports @echo "> Formating code..." - @goimports -l -w -local ${PACKAGE_BASE} ${SRC} + @goimports -l -w -local ${PACKAGE_BASE} . -vet: - @echo "> Vetting code..." - @$(GOCMD) vet -mod=vendor ${PACKAGES} +install-golangci-lint: + @echo "> Installing golangci-lint..." + cd tools && $(GOCMD) install github.com/golangci/golangci-lint/cmd/golangci-lint -test: format vet - $(GOTEST) -parallel=1 -count=1 -v ./... +lint: install-golangci-lint + @echo "> Linting code..." + @golangci-lint run -c golangci.yaml + +test: format lint + $(GOCMD) test -parallel=1 -count=1 -v ./... diff --git a/vendor/github.com/buildpacks/imgutil/README.md b/vendor/github.com/buildpacks/imgutil/README.md index 2caa4b9dfc..79d2eb44c2 100644 --- a/vendor/github.com/buildpacks/imgutil/README.md +++ b/vendor/github.com/buildpacks/imgutil/README.md @@ -1,6 +1,6 @@ # imgutil -[![Build Status](https://travis-ci.org/buildpack/imgutil.svg?branch=master)](https://travis-ci.org/buildpack/imgutil) +[![Build results](https://github.com/buildpacks/imgutil/workflows/test/badge.svg)](https://github.com/buildpacks/imgutil/actions) Helpful utilities for working with images @@ -16,4 +16,4 @@ To run tests: ```bash $ make test -``` \ No newline at end of file +``` diff --git a/vendor/github.com/buildpacks/imgutil/fakes/image.go b/vendor/github.com/buildpacks/imgutil/fakes/image.go index 7e6ec1e415..ea6abcae74 100644 --- a/vendor/github.com/buildpacks/imgutil/fakes/image.go +++ b/vendor/github.com/buildpacks/imgutil/fakes/image.go @@ -30,6 +30,9 @@ func NewImage(name, topLayerSha string, identifier imgutil.Identifier) *Image { prevLayersMap: map[string]string{}, createdAt: time.Now(), savedNames: map[string]bool{}, + os: "linux", + osVersion: "", + architecture: "amd64", } } @@ -42,6 +45,9 @@ type Image struct { labels map[string]string env map[string]string topLayerSha string + os string + osVersion string + architecture string identifier imgutil.Identifier name string entryPoint []string @@ -61,6 +67,18 @@ func (i *Image) Label(key string) (string, error) { return i.labels[key], nil } +func (i *Image) OS() (string, error) { + return i.os, nil +} + +func (i *Image) OSVersion() (string, error) { + return i.os, nil +} + +func (i *Image) Architecture() (string, error) { + return i.architecture, nil +} + func (i *Image) Rename(name string) { i.name = name } @@ -233,6 +251,12 @@ func (i *Image) SetIdentifier(identifier imgutil.Identifier) { i.identifier = identifier } +func (i *Image) SetPlatform(os, osVersion, architecture string) { + i.os = os + i.osVersion = osVersion + i.architecture = architecture +} + func (i *Image) Cleanup() error { return os.RemoveAll(i.layerDir) } diff --git a/vendor/github.com/buildpacks/imgutil/go.mod b/vendor/github.com/buildpacks/imgutil/go.mod index 79426435a0..a90d32015a 100644 --- a/vendor/github.com/buildpacks/imgutil/go.mod +++ b/vendor/github.com/buildpacks/imgutil/go.mod @@ -3,13 +3,13 @@ module github.com/buildpacks/imgutil require ( github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 github.com/docker/go-connections v0.4.0 - github.com/google/go-cmp v0.3.0 + github.com/google/go-cmp v0.4.0 github.com/google/go-containerregistry v0.0.0-20200311163244-4b1985e5ea21 - github.com/pkg/errors v0.8.1 - github.com/sclevine/spec v1.2.0 + github.com/pkg/errors v0.9.1 + github.com/sclevine/spec v1.4.0 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) -go 1.13 +go 1.14 diff --git a/vendor/github.com/buildpacks/imgutil/go.sum b/vendor/github.com/buildpacks/imgutil/go.sum index 87574f8e83..611db1b99b 100644 --- a/vendor/github.com/buildpacks/imgutil/go.sum +++ b/vendor/github.com/buildpacks/imgutil/go.sum @@ -122,6 +122,8 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.0.0-20200311163244-4b1985e5ea21 h1:rz5VzU1xKHR8HDtifeAJ+SPwE0v3YW0AEien/Lobgww= github.com/google/go-containerregistry v0.0.0-20200311163244-4b1985e5ea21/go.mod h1:m8YvHwSOuBCq25yrj1DaX/fIMrv6ec3CNg8jY8+5PEA= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -214,6 +216,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -233,6 +237,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -378,6 +384,8 @@ golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= diff --git a/vendor/github.com/buildpacks/imgutil/golangci.yaml b/vendor/github.com/buildpacks/imgutil/golangci.yaml new file mode 100644 index 0000000000..d951b05467 --- /dev/null +++ b/vendor/github.com/buildpacks/imgutil/golangci.yaml @@ -0,0 +1,31 @@ +run: + timeout: 6m + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - dogsled + - gocritic + - goimports + - golint + - gosimple + - govet + - ineffassign + - maligned + - misspell + - nakedret + - scopelint + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unused + - varcheck + - whitespace + +linters-settings: + goimports: + local-prefixes: github.com/buildpacks/imgutil \ No newline at end of file diff --git a/vendor/github.com/buildpacks/imgutil/image.go b/vendor/github.com/buildpacks/imgutil/image.go index fef9df1474..54fb2a0ae8 100644 --- a/vendor/github.com/buildpacks/imgutil/image.go +++ b/vendor/github.com/buildpacks/imgutil/image.go @@ -51,6 +51,9 @@ type Image interface { Delete() error CreatedAt() (time.Time, error) Identifier() (Identifier, error) + OS() (string, error) + OSVersion() (string, error) + Architecture() (string, error) } type Identifier fmt.Stringer diff --git a/vendor/github.com/buildpacks/imgutil/layer/windows_writer.go b/vendor/github.com/buildpacks/imgutil/layer/windows_writer.go new file mode 100644 index 0000000000..a33b2f38e7 --- /dev/null +++ b/vendor/github.com/buildpacks/imgutil/layer/windows_writer.go @@ -0,0 +1,102 @@ +package layer + +import ( + "archive/tar" + "io" + "path" + "strings" +) + +type WindowsWriter struct { + tarWriter *tar.Writer + writtenParentPaths map[string]bool +} + +func NewWindowsWriter(fileWriter io.Writer) *WindowsWriter { + return &WindowsWriter{ + tarWriter: tar.NewWriter(fileWriter), + writtenParentPaths: map[string]bool{}, + } +} + +func (w *WindowsWriter) Write(content []byte) (int, error) { + return w.tarWriter.Write(content) +} + +func (w *WindowsWriter) WriteHeader(header *tar.Header) error { + if err := w.initializeLayer(); err != nil { + return err + } + + header.Name = layerFilesPath(header.Name) + + err := w.writeParentPaths(header.Name) + if err != nil { + return err + } + + if header.Typeflag == tar.TypeDir { + return w.writeDirHeader(header) + } + return w.tarWriter.WriteHeader(header) +} + +func (w *WindowsWriter) Close() (err error) { + defer func() { + closeErr := w.tarWriter.Close() + if err == nil { + err = closeErr + } + }() + return w.initializeLayer() +} + +func (w *WindowsWriter) Flush() error { + return w.tarWriter.Flush() +} + +func (w *WindowsWriter) writeParentPaths(childPath string) error { + var parentDir string + for _, pathPart := range strings.Split(path.Dir(childPath), "/") { + parentDir = path.Join(parentDir, pathPart) + + if err := w.writeDirHeader(&tar.Header{ + Name: parentDir, + Typeflag: tar.TypeDir, + }); err != nil { + return err + } + } + return nil +} + +func layerFilesPath(origPath string) string { + return path.Join("Files", origPath) +} + +func (w *WindowsWriter) initializeLayer() error { + if err := w.writeDirHeader(&tar.Header{ + Name: "Files", + Typeflag: tar.TypeDir, + }); err != nil { + return err + } + if err := w.writeDirHeader(&tar.Header{ + Name: "Hives", + Typeflag: tar.TypeDir, + }); err != nil { + return err + } + return nil +} + +func (w *WindowsWriter) writeDirHeader(header *tar.Header) error { + if w.writtenParentPaths[header.Name] { + return nil + } + if err := w.tarWriter.WriteHeader(header); err != nil { + return err + } + w.writtenParentPaths[header.Name] = true + return nil +} diff --git a/vendor/github.com/buildpacks/imgutil/local/local.go b/vendor/github.com/buildpacks/imgutil/local/local.go index 62816e4a7f..48d20520f4 100644 --- a/vendor/github.com/buildpacks/imgutil/local/local.go +++ b/vendor/github.com/buildpacks/imgutil/local/local.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/jsonmessage" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/pkg/errors" @@ -26,15 +27,14 @@ import ( ) type Image struct { - repoName string - docker client.CommonAPIClient - inspect types.ImageInspect - layerPaths []string - currentTempImage string - downloadOnce *sync.Once - prevName string - prevImage *FileSystemLocalImage - easyAddLayers []string + repoName string + docker client.CommonAPIClient + inspect types.ImageInspect + layerPaths []string + downloadOnce *sync.Once + prevName string + prevImage *FileSystemLocalImage + easyAddLayers []string } type FileSystemLocalImage struct { @@ -75,7 +75,12 @@ func FromBaseImage(imageName string) ImageOption { } func NewImage(repoName string, dockerClient client.CommonAPIClient, ops ...ImageOption) (imgutil.Image, error) { - inspect := defaultInspect() + var err error + + inspect, err := defaultInspect(dockerClient) + if err != nil { + return nil, err + } image := &Image{ docker: dockerClient, @@ -85,7 +90,6 @@ func NewImage(repoName string, dockerClient client.CommonAPIClient, ops ...Image downloadOnce: &sync.Once{}, } - var err error for _, v := range ops { image, err = v(image) if err != nil { @@ -111,6 +115,18 @@ func (i *Image) Env(key string) (string, error) { return "", nil } +func (i *Image) OS() (string, error) { + return i.inspect.Os, nil +} + +func (i *Image) OSVersion() (string, error) { + return i.inspect.OsVersion, nil +} + +func (i *Image) Architecture() (string, error) { + return i.inspect.Architecture, nil +} + func (i *Image) Rename(name string) { i.easyAddLayers = nil if prevInspect, _, err := i.docker.ImageInspectWithRaw(context.TODO(), name); err == nil { @@ -341,7 +357,9 @@ func (i *Image) doSave() (types.ImageInspect, error) { if err != nil { return types.ImageInspect{}, err } - repoName := t.String() + + //returns valid 'name:tag' appending 'latest', if missing tag + repoName := t.Name() pr, pw := io.Pipe() defer pw.Close() @@ -351,10 +369,17 @@ func (i *Image) doSave() (types.ImageInspect, error) { done <- err return } - if err := ensureReaderClosed(res.Body); err != nil { - done <- errors.Wrap(err, "failed to drain and close response from docker client") + + //only return response error after response is drained and closed + responseErr := checkResponseError(res.Body) + drainCloseErr := ensureReaderClosed(res.Body) + if responseErr != nil { + done <- responseErr return } + if drainCloseErr != nil { + done <- drainCloseErr + } done <- nil }() @@ -389,7 +414,6 @@ func (i *Image) doSave() (types.ImageInspect, error) { } f.Close() layerPaths = append(layerPaths, layerName) - } manifest, err := json.Marshal([]map[string]interface{}{ @@ -410,6 +434,9 @@ func (i *Image) doSave() (types.ImageInspect, error) { tw.Close() pw.Close() err = <-done + if err != nil { + return types.ImageInspect{}, errors.Wrapf(err, "image load '%s'. first error", i.repoName) + } inspect, _, err := i.docker.ImageInspectWithRaw(context.Background(), id) if err != nil { @@ -535,7 +562,7 @@ func addFileToTar(tw *tar.Writer, name string, contents *os.File) error { if err != nil { return err } - hdr := &tar.Header{Name: name, Mode: 0644, Size: int64(fi.Size())} + hdr := &tar.Header{Name: name, Mode: 0644, Size: fi.Size()} if err := tw.WriteHeader(hdr); err != nil { return err } @@ -597,7 +624,7 @@ func inspectOptionalImage(docker client.CommonAPIClient, imageName string) (type if inspect, _, err = docker.ImageInspectWithRaw(context.Background(), imageName); err != nil { if client.IsErrNotFound(err) { - return defaultInspect(), nil + return defaultInspect(docker) } return types.ImageInspect{}, errors.Wrapf(err, "verifying image '%s'", imageName) @@ -606,17 +633,23 @@ func inspectOptionalImage(docker client.CommonAPIClient, imageName string) (type return inspect, nil } -func defaultInspect() types.ImageInspect { +func defaultInspect(docker client.CommonAPIClient) (types.ImageInspect, error) { + daemonInfo, err := docker.Info(context.Background()) + if err != nil { + return types.ImageInspect{}, err + } + return types.ImageInspect{ - Os: "linux", + Os: daemonInfo.OSType, + OsVersion: daemonInfo.OSVersion, Architecture: "amd64", Config: &container.Config{}, - } + }, nil } func v1Config(inspect types.ImageInspect) (v1.ConfigFile, error) { history := make([]v1.History, len(inspect.RootFS.Layers)) - for i, _ := range history { + for i := range history { // zero history history[i] = v1.History{ Created: v1.Time{Time: imgutil.NormalizedDateTime}, @@ -678,6 +711,7 @@ func v1Config(inspect types.ImageInspect) (v1.ConfigFile, error) { Created: v1.Time{Time: imgutil.NormalizedDateTime}, History: history, OS: inspect.Os, + OSVersion: inspect.OsVersion, RootFS: v1.RootFS{ Type: "layers", DiffIDs: diffIDs, @@ -686,6 +720,19 @@ func v1Config(inspect types.ImageInspect) (v1.ConfigFile, error) { }, nil } +func checkResponseError(r io.Reader) error { + decoder := json.NewDecoder(r) + var jsonMessage jsonmessage.JSONMessage + if err := decoder.Decode(&jsonMessage); err != nil { + return errors.Wrapf(err, "parsing daemon response") + } + + if jsonMessage.Error != nil { + return errors.Wrap(jsonMessage.Error, "embedded daemon response") + } + return nil +} + // ensureReaderClosed drains and closes and reader, returning the first error func ensureReaderClosed(r io.ReadCloser) error { _, err := io.Copy(ioutil.Discard, r) diff --git a/vendor/github.com/buildpacks/imgutil/remote/remote.go b/vendor/github.com/buildpacks/imgutil/remote/remote.go index d74c298d9f..95d24ba41f 100644 --- a/vendor/github.com/buildpacks/imgutil/remote/remote.go +++ b/vendor/github.com/buildpacks/imgutil/remote/remote.go @@ -53,6 +53,7 @@ func WithPreviousImage(imageName string) ImageOption { func FromBaseImage(imageName string) ImageOption { return func(r *Image) (*Image, error) { var err error + r.image, err = newV1Image(r.keychain, imageName) if err != nil { return nil, err @@ -88,6 +89,7 @@ func newV1Image(keychain authn.Keychain, repoName string) (v1.Image, error) { if err != nil { return nil, err } + image, err := remote.Image(ref, remote.WithAuth(auth), remote.WithTransport(http.DefaultTransport)) if err != nil { if transportErr, ok := err.(*transport.Error); ok && len(transportErr.Errors) > 0 { @@ -98,13 +100,14 @@ func newV1Image(keychain authn.Keychain, repoName string) (v1.Image, error) { } return nil, fmt.Errorf("connect to repo store '%s': %s", repoName, err.Error()) } + return image, nil } func emptyImage() (v1.Image, error) { cfg := &v1.ConfigFile{ - Architecture: "amd64", OS: "linux", + Architecture: "amd64", RootFS: v1.RootFS{ Type: "layers", DiffIDs: []v1.Hash{}, @@ -134,7 +137,6 @@ func (i *Image) Label(key string) (string, error) { } labels := cfg.Config.Labels return labels[key], nil - } func (i *Image) Env(key string) (string, error) { @@ -151,6 +153,30 @@ func (i *Image) Env(key string) (string, error) { return "", nil } +func (i *Image) OS() (string, error) { + cfg, err := i.image.ConfigFile() + if err != nil || cfg == nil || cfg.OS == "" { + return "", fmt.Errorf("failed to get OS from config file for image '%s'", i.repoName) + } + return cfg.OS, nil +} + +func (i *Image) OSVersion() (string, error) { + cfg, err := i.image.ConfigFile() + if err != nil || cfg == nil { + return "", fmt.Errorf("failed to get OSVersion from config file for image '%s'", i.repoName) + } + return cfg.OSVersion, nil +} + +func (i *Image) Architecture() (string, error) { + cfg, err := i.image.ConfigFile() + if err != nil || cfg == nil || cfg.Architecture == "" { + return "", fmt.Errorf("failed to get Architecture from config file for image '%s'", i.repoName) + } + return cfg.Architecture, nil +} + func (i *Image) Rename(name string) { i.repoName = name } @@ -165,10 +191,7 @@ func (i *Image) Found() bool { return false } _, err = remote.Image(ref, remote.WithAuth(auth), remote.WithTransport(http.DefaultTransport)) - if err != nil { - return false - } - return true + return err == nil } func (i *Image) Identifier() (imgutil.Identifier, error) { @@ -374,7 +397,7 @@ func (i *Image) Save(additionalNames ...string) error { return errors.Wrap(err, "get image layers") } cfg.History = make([]v1.History, len(layers)) - for i, _ := range cfg.History { + for i := range cfg.History { cfg.History[i] = v1.History{ Created: v1.Time{Time: imgutil.NormalizedDateTime}, } diff --git a/vendor/github.com/buildpacks/lifecycle/cmd/flags.go b/vendor/github.com/buildpacks/lifecycle/cmd/flags.go index 85c8b6de13..aa78893278 100644 --- a/vendor/github.com/buildpacks/lifecycle/cmd/flags.go +++ b/vendor/github.com/buildpacks/lifecycle/cmd/flags.go @@ -102,7 +102,7 @@ func FlagPlatformDir(dir *string) { } func DeprecatedFlagRunImage(image *string) { - flagSet.StringVar(image, "image", os.Getenv(EnvRunImage), "reference to run image") + flagSet.StringVar(image, "image", "", "reference to run image") } func FlagRunImage(image *string) { diff --git a/vendor/golang.org/x/crypto/bcrypt/base64.go b/vendor/golang.org/x/crypto/bcrypt/base64.go new file mode 100644 index 0000000000..fc31160908 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/base64.go @@ -0,0 +1,35 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +import "encoding/base64" + +const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +var bcEncoding = base64.NewEncoding(alphabet) + +func base64Encode(src []byte) []byte { + n := bcEncoding.EncodedLen(len(src)) + dst := make([]byte, n) + bcEncoding.Encode(dst, src) + for dst[n-1] == '=' { + n-- + } + return dst[:n] +} + +func base64Decode(src []byte) ([]byte, error) { + numOfEquals := 4 - (len(src) % 4) + for i := 0; i < numOfEquals; i++ { + src = append(src, '=') + } + + dst := make([]byte, bcEncoding.DecodedLen(len(src))) + n, err := bcEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + return dst[:n], nil +} diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go new file mode 100644 index 0000000000..aeb73f81a1 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go @@ -0,0 +1,295 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing +// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf +package bcrypt // import "golang.org/x/crypto/bcrypt" + +// The code is a port of Provos and Mazières's C implementation. +import ( + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "io" + "strconv" + + "golang.org/x/crypto/blowfish" +) + +const ( + MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword + MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword + DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword +) + +// The error returned from CompareHashAndPassword when a password and hash do +// not match. +var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") + +// The error returned from CompareHashAndPassword when a hash is too short to +// be a bcrypt hash. +var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") + +// The error returned from CompareHashAndPassword when a hash was created with +// a bcrypt algorithm newer than this implementation. +type HashVersionTooNewError byte + +func (hv HashVersionTooNewError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) +} + +// The error returned from CompareHashAndPassword when a hash starts with something other than '$' +type InvalidHashPrefixError byte + +func (ih InvalidHashPrefixError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) +} + +type InvalidCostError int + +func (ic InvalidCostError) Error() string { + return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) +} + +const ( + majorVersion = '2' + minorVersion = 'a' + maxSaltSize = 16 + maxCryptedHashSize = 23 + encodedSaltSize = 22 + encodedHashSize = 31 + minHashSize = 59 +) + +// magicCipherData is an IV for the 64 Blowfish encryption calls in +// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. +var magicCipherData = []byte{ + 0x4f, 0x72, 0x70, 0x68, + 0x65, 0x61, 0x6e, 0x42, + 0x65, 0x68, 0x6f, 0x6c, + 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, + 0x6f, 0x75, 0x62, 0x74, +} + +type hashed struct { + hash []byte + salt []byte + cost int // allowed range is MinCost to MaxCost + major byte + minor byte +} + +// GenerateFromPassword returns the bcrypt hash of the password at the given +// cost. If the cost given is less than MinCost, the cost will be set to +// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, +// to compare the returned hashed password with its cleartext version. +func GenerateFromPassword(password []byte, cost int) ([]byte, error) { + p, err := newFromPassword(password, cost) + if err != nil { + return nil, err + } + return p.Hash(), nil +} + +// CompareHashAndPassword compares a bcrypt hashed password with its possible +// plaintext equivalent. Returns nil on success, or an error on failure. +func CompareHashAndPassword(hashedPassword, password []byte) error { + p, err := newFromHash(hashedPassword) + if err != nil { + return err + } + + otherHash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return err + } + + otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} + if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { + return nil + } + + return ErrMismatchedHashAndPassword +} + +// Cost returns the hashing cost used to create the given hashed +// password. When, in the future, the hashing cost of a password system needs +// to be increased in order to adjust for greater computational power, this +// function allows one to establish which passwords need to be updated. +func Cost(hashedPassword []byte) (int, error) { + p, err := newFromHash(hashedPassword) + if err != nil { + return 0, err + } + return p.cost, nil +} + +func newFromPassword(password []byte, cost int) (*hashed, error) { + if cost < MinCost { + cost = DefaultCost + } + p := new(hashed) + p.major = majorVersion + p.minor = minorVersion + + err := checkCost(cost) + if err != nil { + return nil, err + } + p.cost = cost + + unencodedSalt := make([]byte, maxSaltSize) + _, err = io.ReadFull(rand.Reader, unencodedSalt) + if err != nil { + return nil, err + } + + p.salt = base64Encode(unencodedSalt) + hash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return nil, err + } + p.hash = hash + return p, err +} + +func newFromHash(hashedSecret []byte) (*hashed, error) { + if len(hashedSecret) < minHashSize { + return nil, ErrHashTooShort + } + p := new(hashed) + n, err := p.decodeVersion(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + n, err = p.decodeCost(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + + // The "+2" is here because we'll have to append at most 2 '=' to the salt + // when base64 decoding it in expensiveBlowfishSetup(). + p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) + copy(p.salt, hashedSecret[:encodedSaltSize]) + + hashedSecret = hashedSecret[encodedSaltSize:] + p.hash = make([]byte, len(hashedSecret)) + copy(p.hash, hashedSecret) + + return p, nil +} + +func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { + cipherData := make([]byte, len(magicCipherData)) + copy(cipherData, magicCipherData) + + c, err := expensiveBlowfishSetup(password, uint32(cost), salt) + if err != nil { + return nil, err + } + + for i := 0; i < 24; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) + } + } + + // Bug compatibility with C bcrypt implementations. We only encode 23 of + // the 24 bytes encrypted. + hsh := base64Encode(cipherData[:maxCryptedHashSize]) + return hsh, nil +} + +func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { + csalt, err := base64Decode(salt) + if err != nil { + return nil, err + } + + // Bug compatibility with C bcrypt implementations. They use the trailing + // NULL in the key string during expansion. + // We copy the key to prevent changing the underlying array. + ckey := append(key[:len(key):len(key)], 0) + + c, err := blowfish.NewSaltedCipher(ckey, csalt) + if err != nil { + return nil, err + } + + var i, rounds uint64 + rounds = 1 << cost + for i = 0; i < rounds; i++ { + blowfish.ExpandKey(ckey, c) + blowfish.ExpandKey(csalt, c) + } + + return c, nil +} + +func (p *hashed) Hash() []byte { + arr := make([]byte, 60) + arr[0] = '$' + arr[1] = p.major + n := 2 + if p.minor != 0 { + arr[2] = p.minor + n = 3 + } + arr[n] = '$' + n++ + copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) + n += 2 + arr[n] = '$' + n++ + copy(arr[n:], p.salt) + n += encodedSaltSize + copy(arr[n:], p.hash) + n += encodedHashSize + return arr[:n] +} + +func (p *hashed) decodeVersion(sbytes []byte) (int, error) { + if sbytes[0] != '$' { + return -1, InvalidHashPrefixError(sbytes[0]) + } + if sbytes[1] > majorVersion { + return -1, HashVersionTooNewError(sbytes[1]) + } + p.major = sbytes[1] + n := 3 + if sbytes[2] != '$' { + p.minor = sbytes[2] + n++ + } + return n, nil +} + +// sbytes should begin where decodeVersion left off. +func (p *hashed) decodeCost(sbytes []byte) (int, error) { + cost, err := strconv.Atoi(string(sbytes[0:2])) + if err != nil { + return -1, err + } + err = checkCost(cost) + if err != nil { + return -1, err + } + p.cost = cost + return 3, nil +} + +func (p *hashed) String() string { + return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) +} + +func checkCost(cost int) error { + if cost < MinCost || cost > MaxCost { + return InvalidCostError(cost) + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e90ef682fb..fb1fbe59b1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -14,7 +14,9 @@ github.com/Microsoft/hcsshim/osversion github.com/apex/log # github.com/buildpacks/imgutil v0.0.0-20200313170640-a02052f47d62 github.com/buildpacks/imgutil +github.com/buildpacks/imgutil/archive github.com/buildpacks/imgutil/fakes +github.com/buildpacks/imgutil/layer github.com/buildpacks/imgutil/local github.com/buildpacks/imgutil/remote # github.com/buildpacks/lifecycle v0.7.1