From 37a21f98ae9478f81cd587a38d520cde1ea06bb2 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Thu, 14 Sep 2023 19:46:40 +0100 Subject: [PATCH 1/5] Fix issue where filepath.Walk does not respect symlinks --- .gitignore | 1 + examples/build/build.hcl | 4 +- .../terraform/workspace/.terraform.lock.hcl | 41 ++++++ go.mod | 6 +- go.sum | 12 +- pkg/clients/tar/tar.go | 5 +- pkg/config/resources/build/provider.go | 3 +- pkg/utils/dirhash/hash.go | 137 ++++++++++++++++++ pkg/utils/utils.go | 2 +- 9 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 examples/terraform/workspace/.terraform.lock.hcl create mode 100644 pkg/utils/dirhash/hash.go diff --git a/.gitignore b/.gitignore index dd106058..af59c89c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bin/ dist/ .vscode .envrc +.terraform \ No newline at end of file diff --git a/examples/build/build.hcl b/examples/build/build.hcl index 60591aca..fa4a39b8 100644 --- a/examples/build/build.hcl +++ b/examples/build/build.hcl @@ -3,11 +3,11 @@ variable "container_enabled" { } variable "nomad_enabled" { - default = true + default = false } variable "kubernetes_enabled" { - default = true + default = false } // use a random ingress by default diff --git a/examples/terraform/workspace/.terraform.lock.hcl b/examples/terraform/workspace/.terraform.lock.hcl new file mode 100644 index 00000000..f5eac8ad --- /dev/null +++ b/examples/terraform/workspace/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} + +provider "registry.terraform.io/hashicorp/vault" { + version = "3.19.0" + constraints = "3.19.0" + hashes = [ + "h1:PRAF8HdRK4hziAEjbWFRMjVftXU7QEyF3xMGXQELS4o=", + "zh:2571ca03777afcb70d51d7f591bfa0bf770bbcc93bb87563ec295b17a9b21947", + "zh:25d096ab433e4eb5ed0f17f82beb661681c6abac93df35c15d5adf26da822709", + "zh:33776aa50d597448539251b4b706b4dbaf599ebee47a4244361400f93cd10986", + "zh:4d9d1fe67c0a7d2097f3d67fa867fd34dec6d880fe4d76e93bb38ccf74a2fadf", + "zh:5c2310226e1be065e75b7cf29633fd175a2180f5d9d9fb6f587e6b3d6ffb328d", + "zh:66b0e3e2819bb34fd592bb436391b08cb34f345832f4db1057b0169872537931", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:85f44baf07eae1c9087c9ddb867d561d0fb6adf8f081558587c71bb99aba034f", + "zh:92cffaeebf531b27f5f1d798dca04813ae1541381e67cffe7157facf84a8f9c7", + "zh:c2964602306935ddd2d005b38207b57a919c1574737c3daff82e058a16439d7a", + "zh:cd7fe84a7030b59bc4457a251ec97e0286956a0c3fb489323affcb306116fdd1", + "zh:f68303735e4a2ac0e9122feb65f338d99bee835a4309ca34dac77879c8bfd637", + ] +} diff --git a/go.mod b/go.mod index 0c261257..39899b55 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/cucumber/godog v0.12.4 github.com/docker/docker v24.0.5+incompatible github.com/docker/go-connections v0.4.0 + github.com/facebookgo/symwalk v0.0.0-20150726040526-42004b9f3222 github.com/fatih/color v1.15.0 github.com/go-chi/chi v1.5.4 github.com/go-chi/cors v1.2.1 @@ -44,7 +45,6 @@ require ( github.com/stretchr/testify v1.8.4 github.com/zclconf/go-cty v1.12.0 golang.org/x/crypto v0.9.0 - golang.org/x/mod v0.9.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/grpc v1.55.0 helm.sh/helm/v3 v3.8.2 @@ -98,6 +98,10 @@ require ( github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect + github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect + github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect + github.com/facebookgo/testname v0.0.0-20150612200628-5443337c3a12 // indirect github.com/flytam/filenamify v1.1.1 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect diff --git a/go.sum b/go.sum index 5b8b5915..d2f78db2 100644 --- a/go.sum +++ b/go.sum @@ -607,6 +607,16 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/facebookgo/symwalk v0.0.0-20150726040526-42004b9f3222 h1:ivxAxcE9py2xLAqpcEwN7sN711aLfEWgh3cY0aha7uY= +github.com/facebookgo/symwalk v0.0.0-20150726040526-42004b9f3222/go.mod h1:PgrCjL2+FgkITqxQI+erRTONtAv4JkpOzun5ozKW/Jg= +github.com/facebookgo/testname v0.0.0-20150612200628-5443337c3a12 h1:pKeuUgeuL6jk/FpxSr0ZVL1XEiOmrcWBvB2rKXu0mMI= +github.com/facebookgo/testname v0.0.0-20150612200628-5443337c3a12/go.mod h1:IYed2VYeQcs7JTN6KiVXjaz6Rv/Qz092Wjc6o5bCJ9I= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -1608,8 +1618,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/clients/tar/tar.go b/pkg/clients/tar/tar.go index 176ff94f..8a7cdb5f 100644 --- a/pkg/clients/tar/tar.go +++ b/pkg/clients/tar/tar.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/facebookgo/symwalk" ) type TarGzOptions struct { @@ -45,7 +47,8 @@ func (tg *TarGz) Compress(buf io.Writer, options *TarGzOptions, src ...string) e } // walk through every file in the folder - filepath.Walk(path, func(file string, fi os.FileInfo, err error) error { + // Go's filepath walk does not represent symlinks + symwalk.Walk(path, func(file string, fi os.FileInfo, err error) error { // generate tar header header, err := tar.FileInfoHeader(fi, strings.Replace(file, topLevel, "", -1)) if err != nil { diff --git a/pkg/config/resources/build/provider.go b/pkg/config/resources/build/provider.go index 87e943d8..29e296de 100644 --- a/pkg/config/resources/build/provider.go +++ b/pkg/config/resources/build/provider.go @@ -9,7 +9,6 @@ import ( "github.com/jumppad-labs/jumppad/pkg/clients/container/types" "github.com/jumppad-labs/jumppad/pkg/clients/logger" "github.com/jumppad-labs/jumppad/pkg/utils" - "golang.org/x/mod/sumdb/dirhash" "golang.org/x/xerrors" ) @@ -41,7 +40,7 @@ func (b *Provider) Init(cfg htypes.Resource, l logger.Logger) error { func (b *Provider) Create() error { // calculate the hash - hash, err := dirhash.HashDir(b.config.Container.Context, "", dirhash.DefaultHash) + hash, err := utils.HashDir(b.config.Container.Context) if err != nil { return xerrors.Errorf("unable to hash directory: %w", err) } diff --git a/pkg/utils/dirhash/hash.go b/pkg/utils/dirhash/hash.go new file mode 100644 index 00000000..ace7154e --- /dev/null +++ b/pkg/utils/dirhash/hash.go @@ -0,0 +1,137 @@ +// Copyright 2018 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 dirhash defines hashes over directory trees. +// These hashes are recorded in go.sum files and in the Go checksum database, +// to allow verifying that a newly-downloaded module has the expected content. +package dirhash + +import ( + "archive/zip" + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/facebookgo/symwalk" +) + +// DefaultHash is the default hash function used in new go.sum entries. +var DefaultHash Hash = Hash1 + +// A Hash is a directory hash function. +// It accepts a list of files along with a function that opens the content of each file. +// It opens, reads, hashes, and closes each file and returns the overall directory hash. +type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) + +// Hash1 is the "h1:" directory hash function, using SHA-256. +// +// Hash1 is "h1:" followed by the base64-encoded SHA-256 hash of a summary +// prepared as if by the Unix command: +// +// sha256sum $(find . -type f | sort) | sha256sum +// +// More precisely, the hashed summary contains a single line for each file in the list, +// ordered by sort.Strings applied to the file names, where each line consists of +// the hexadecimal SHA-256 hash of the file content, +// two spaces (U+0020), the file name, and a newline (U+000A). +// +// File names with newlines (U+000A) are disallowed. +func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { + h := sha256.New() + files = append([]string(nil), files...) + sort.Strings(files) + for _, file := range files { + if strings.Contains(file, "\n") { + return "", errors.New("dirhash: filenames with newlines are not supported") + } + r, err := open(file) + if err != nil { + return "", err + } + hf := sha256.New() + _, err = io.Copy(hf, r) + r.Close() + if err != nil { + return "", err + } + fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) + } + return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil +} + +// HashDir returns the hash of the local file system directory dir, +// replacing the directory name itself with prefix in the file names +// used in the hash function. +func HashDir(dir, prefix string, hash Hash) (string, error) { + files, err := DirFiles(dir, prefix) + if err != nil { + return "", err + } + osOpen := func(name string) (io.ReadCloser, error) { + return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) + } + return hash(files, osOpen) +} + +// DirFiles returns the list of files in the tree rooted at dir, +// replacing the directory name dir with prefix in each name. +// The resulting names always use forward slashes. +func DirFiles(dir, prefix string) ([]string, error) { + var files []string + dir = filepath.Clean(dir) + err := symwalk.Walk(dir, func(file string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } else if file == dir { + return fmt.Errorf("%s is not a directory", dir) + } + + rel := file + if dir != "." { + rel = file[len(dir)+1:] + } + f := filepath.Join(prefix, rel) + files = append(files, filepath.ToSlash(f)) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +// HashZip returns the hash of the file content in the named zip file. +// Only the file names and their contents are included in the hash: +// the exact zip file format encoding, compression method, +// per-file modification times, and other metadata are ignored. +func HashZip(zipfile string, hash Hash) (string, error) { + z, err := zip.OpenReader(zipfile) + if err != nil { + return "", err + } + defer z.Close() + var files []string + zfiles := make(map[string]*zip.File) + for _, file := range z.File { + files = append(files, file.Name) + zfiles[file.Name] = file + } + zipOpen := func(name string) (io.ReadCloser, error) { + f := zfiles[name] + if f == nil { + return nil, fmt.Errorf("file %q not found in zip", name) // should never happen + } + return f.Open() + } + return hash(files, zipOpen) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 038cf138..b06fd3dc 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -17,7 +17,7 @@ import ( "strings" "sync" - "golang.org/x/mod/sumdb/dirhash" + "github.com/jumppad-labs/jumppad/pkg/utils/dirhash" ) // EnsureAbsolute ensure that the given path is either absolute or From f8f41fd501d0e459b363a73d8f99611b8d539585 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 15 Sep 2023 07:08:52 +0100 Subject: [PATCH 2/5] Run podman functional tests in debug mode --- .github/workflows/build_and_deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index 9d543191..a579c365 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -242,7 +242,7 @@ jobs: run: | jumppad check jumppad purge - jumppad test ${{matrix.folder}} + LOG_LEVEL=debug jumppad test ${{matrix.folder}} env: DOCKER_HOST: "unix:///run/podman/podman.sock" From 0e3766f6d47a8824a8a8a095920e1aab563f0896 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 15 Sep 2023 08:33:12 +0100 Subject: [PATCH 3/5] Add capability to ignore files when taring a list of files or building an image --- .github/workflows/build_and_deploy.yaml | 2 +- examples/build/build.hcl | 1 + go.mod | 1 + go.sum | 2 + pkg/clients/container/docker_tasks.go | 4 +- pkg/clients/container/types/types.go | 7 +- pkg/clients/tar/tar.go | 31 +++- pkg/clients/tar/tar_test.go | 39 +++-- pkg/config/resources/build/provider.go | 5 +- pkg/config/resources/build/resource.go | 5 +- .../dirhash/example_dir/simple/README.md | 16 +++ .../dirhash/example_dir/simple/blueprint.hcl | 29 ++++ pkg/utils/dirhash/example_dir/simple/conf.txt | 1 + .../example_dir/simple/consul_config/.keep | 0 .../dirhash/example_dir/simple/container.hcl | 133 ++++++++++++++++++ .../dirhash/example_dir/simple/defaults.vars | 1 + .../dirhash/example_dir/simple/network.hcl | 3 + .../dirhash/example_dir/simple/output.hcl | 4 + .../example_dir/simple/test/container.feature | 34 +++++ .../example_dir/simple/test/vars.feature | 20 +++ .../dirhash/example_dir/simple/variables.hcl | 4 + pkg/utils/dirhash/example_dir/symlink/simple | 1 + pkg/utils/dirhash/hash.go | 36 ++++- pkg/utils/dirhash/hash_test.go | 29 ++++ pkg/utils/utils.go | 6 +- 25 files changed, 388 insertions(+), 26 deletions(-) create mode 100644 pkg/utils/dirhash/example_dir/simple/README.md create mode 100644 pkg/utils/dirhash/example_dir/simple/blueprint.hcl create mode 100644 pkg/utils/dirhash/example_dir/simple/conf.txt create mode 100644 pkg/utils/dirhash/example_dir/simple/consul_config/.keep create mode 100644 pkg/utils/dirhash/example_dir/simple/container.hcl create mode 100644 pkg/utils/dirhash/example_dir/simple/defaults.vars create mode 100644 pkg/utils/dirhash/example_dir/simple/network.hcl create mode 100644 pkg/utils/dirhash/example_dir/simple/output.hcl create mode 100644 pkg/utils/dirhash/example_dir/simple/test/container.feature create mode 100644 pkg/utils/dirhash/example_dir/simple/test/vars.feature create mode 100644 pkg/utils/dirhash/example_dir/simple/variables.hcl create mode 120000 pkg/utils/dirhash/example_dir/symlink/simple create mode 100644 pkg/utils/dirhash/hash_test.go diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index a579c365..9d543191 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -242,7 +242,7 @@ jobs: run: | jumppad check jumppad purge - LOG_LEVEL=debug jumppad test ${{matrix.folder}} + jumppad test ${{matrix.folder}} env: DOCKER_HOST: "unix:///run/podman/podman.sock" diff --git a/examples/build/build.hcl b/examples/build/build.hcl index fa4a39b8..f8850759 100644 --- a/examples/build/build.hcl +++ b/examples/build/build.hcl @@ -27,6 +27,7 @@ resource "build" "app" { container { dockerfile = "Dockerfile" context = "./src" + ignore = ["**/.terraform"] } output { diff --git a/go.mod b/go.mod index 39899b55..3a60bb3c 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc3 github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 + github.com/ryanuber/go-glob v1.0.0 github.com/sethvargo/go-retry v0.2.4 github.com/shipyard-run/gohup v0.2.2 github.com/shipyard-run/version-manager v0.0.5 diff --git a/go.sum b/go.sum index d2f78db2..edb39724 100644 --- a/go.sum +++ b/go.sum @@ -1342,6 +1342,8 @@ github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNl github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= diff --git a/pkg/clients/container/docker_tasks.go b/pkg/clients/container/docker_tasks.go index 41600612..482f7d3e 100644 --- a/pkg/clients/container/docker_tasks.go +++ b/pkg/clients/container/docker_tasks.go @@ -544,7 +544,7 @@ func (d *DockerTasks) RemoveImage(id string) error { func (d *DockerTasks) BuildContainer(config *dtypes.Build, force bool) (string, error) { // get the checksum for the id - cs, err := utils.HashDir(config.Context) + cs, err := utils.HashDir(config.Context, config.Ignore...) if err != nil { return "", err } @@ -596,7 +596,7 @@ func (d *DockerTasks) BuildContainer(config *dtypes.Build, force bool) (string, } var buf bytes.Buffer - d.tg.Compress(&buf, &ctar.TarGzOptions{OmitRoot: true}, config.Context) + d.tg.Compress(&buf, &ctar.TarGzOptions{OmitRoot: true}, []string{config.Context}, config.Ignore...) resp, err := d.c.ImageBuild(context.Background(), &buf, buildOpts) if err != nil { diff --git a/pkg/clients/container/types/types.go b/pkg/clients/container/types/types.go index 5d503a8f..db9ac1ec 100644 --- a/pkg/clients/container/types/types.go +++ b/pkg/clients/container/types/types.go @@ -84,7 +84,8 @@ type Image struct { type Build struct { Name string - DockerFile string - Context string - Args map[string]string + DockerFile string // Name of the Dockerfile to use, must be in context + Context string // Context to copy to the build process + Ignore []string // globbed list of files to ignore in the context, same as .dockerignore + Args map[string]string // Arguments to pass to the build process } diff --git a/pkg/clients/tar/tar.go b/pkg/clients/tar/tar.go index 8a7cdb5f..e217893a 100644 --- a/pkg/clients/tar/tar.go +++ b/pkg/clients/tar/tar.go @@ -10,21 +10,26 @@ import ( "strings" "github.com/facebookgo/symwalk" + + "github.com/ryanuber/go-glob" ) type TarGzOptions struct { // OmitRoot when set to true ignores the top level directory in the tar archive // only adding sub directories and files. + // /folder/foo/bar/test.txt -> /test.txt + // /folder/foo/bar/baz/* -> /baz OmitRoot bool } type TarGz struct { } -// /folder/foo/bar/test.txt -> /test.txt -// /folder/foo/bar/baz/* -> /baz +// Compress compresses the src directory into a tar.gz file +// optionally a list of globs to be ignored can be passed +func (tg *TarGz) Compress(buf io.Writer, options *TarGzOptions, src []string, ignore ...string) error { + var ignoredDirectories []string -func (tg *TarGz) Compress(buf io.Writer, options *TarGzOptions, src ...string) error { if options == nil { options = &TarGzOptions{} } @@ -49,6 +54,26 @@ func (tg *TarGz) Compress(buf io.Writer, options *TarGzOptions, src ...string) e // walk through every file in the folder // Go's filepath walk does not represent symlinks symwalk.Walk(path, func(file string, fi os.FileInfo, err error) error { + // check if the file is in the ignore list + for _, i := range ignore { + ignore := glob.Glob(i, file) + if ignore { + // if we are ignoring a directory, we need to also ignore any children + // that are in this directory + if fi.IsDir() { + ignoredDirectories = append(ignoredDirectories, file) + } + + return nil + } + } + + for _, i := range ignoredDirectories { + if strings.HasPrefix(file, i) { + return nil + } + } + // generate tar header header, err := tar.FileInfoHeader(fi, strings.Replace(file, topLevel, "", -1)) if err != nil { diff --git a/pkg/clients/tar/tar_test.go b/pkg/clients/tar/tar_test.go index 7082e211..2bf96ad4 100644 --- a/pkg/clients/tar/tar_test.go +++ b/pkg/clients/tar/tar_test.go @@ -53,11 +53,11 @@ func TestTarWithRootFolder(t *testing.T) { tg := &TarGz{} // compress the directory - err := tg.Compress(buf, nil, in) + err := tg.Compress(buf, nil, []string{in}) require.NoError(t, err) // test the output - err = tg.Uncompress(buf, out) + err = tg.Uncompress(buf, true, out) require.NoError(t, err) require.FileExists(t, filepath.Join(out, "/in/test1.txt")) @@ -78,11 +78,11 @@ func TestTarOmmitingRoot(t *testing.T) { opts := TarGzOptions{OmitRoot: true} // compress the directory - err := tg.Compress(buf, &opts, in) + err := tg.Compress(buf, &opts, []string{in}) require.NoError(t, err) // test the output - err = tg.Uncompress(buf, out) + err = tg.Uncompress(buf, true, out) require.NoError(t, err) require.FileExists(t, filepath.Join(out, "/test1.txt")) @@ -102,11 +102,11 @@ func TestTarIndividualFiles(t *testing.T) { tg := &TarGz{} opts := TarGzOptions{OmitRoot: true} - err := tg.Compress(buf, &opts, filepath.Join(in, "test1.txt"), filepath.Join(in, "test2.txt")) + err := tg.Compress(buf, &opts, []string{filepath.Join(in, "test1.txt"), filepath.Join(in, "test2.txt")}) require.NoError(t, err) // test the output - err = tg.Uncompress(buf, out) + err = tg.Uncompress(buf, true, out) require.NoError(t, err) require.FileExists(t, filepath.Join(out, "/test1.txt")) @@ -130,11 +130,11 @@ func TestTarDirAndIndividualFile(t *testing.T) { tg := &TarGz{} opts := TarGzOptions{OmitRoot: true} - err = tg.Compress(buf, &opts, in, filepath.Join(dir, "solo.txt")) + err = tg.Compress(buf, &opts, []string{in, filepath.Join(dir, "solo.txt")}) require.NoError(t, err) // test the output - err = tg.Uncompress(buf, out) + err = tg.Uncompress(buf, true, out) require.NoError(t, err) require.FileExists(t, filepath.Join(out, "/test1.txt")) @@ -142,3 +142,26 @@ func TestTarDirAndIndividualFile(t *testing.T) { require.FileExists(t, filepath.Join(out, "/solo.txt")) require.FileExists(t, filepath.Join(out, "/sub/test3.txt")) } + +func TestTarDirIgnoringFiles(t *testing.T) { + dir := setupTarTests(t) + + in := filepath.Join(dir, "in") + out := filepath.Join(dir, "out") + + buf := bytes.NewBuffer(nil) + + tg := &TarGz{} + opts := TarGzOptions{OmitRoot: true} + + err := tg.Compress(buf, &opts, []string{in}, "**/test1.txt", "**/sub") + require.NoError(t, err) + + // test the output + err = tg.Uncompress(buf, true, out) + require.NoError(t, err) + + require.NoFileExists(t, filepath.Join(out, "/test1.txt")) + require.FileExists(t, filepath.Join(out, "/test2.txt")) + require.NoFileExists(t, filepath.Join(out, "/sub/test3.txt")) +} diff --git a/pkg/config/resources/build/provider.go b/pkg/config/resources/build/provider.go index 29e296de..52b19b5c 100644 --- a/pkg/config/resources/build/provider.go +++ b/pkg/config/resources/build/provider.go @@ -40,7 +40,7 @@ func (b *Provider) Init(cfg htypes.Resource, l logger.Logger) error { func (b *Provider) Create() error { // calculate the hash - hash, err := utils.HashDir(b.config.Container.Context) + hash, err := utils.HashDir(b.config.Container.Context, b.config.Container.Ignore...) if err != nil { return xerrors.Errorf("unable to hash directory: %w", err) } @@ -63,6 +63,7 @@ func (b *Provider) Create() error { Name: b.config.Name, DockerFile: b.config.Container.DockerFile, Context: b.config.Container.Context, + Ignore: b.config.Container.Ignore, Args: b.config.Container.Args, } @@ -143,7 +144,7 @@ func (b *Provider) Changed() (bool, error) { } func (b *Provider) hasChanged() (bool, error) { - hash, err := utils.HashDir(b.config.Container.Context) + hash, err := utils.HashDir(b.config.Container.Context, b.config.Container.Ignore...) if err != nil { return false, xerrors.Errorf("unable to hash directory: %w", err) } diff --git a/pkg/config/resources/build/resource.go b/pkg/config/resources/build/resource.go index 10803a85..e7e7fbf9 100644 --- a/pkg/config/resources/build/resource.go +++ b/pkg/config/resources/build/resource.go @@ -30,12 +30,13 @@ type Build struct { type BuildContainer struct { DockerFile string `hcl:"dockerfile,optional" json:"dockerfile,omitempty"` // Location of build file inside build context defaults to ./Dockerfile Context string `hcl:"context" json:"context"` // Path to build context + Ignore []string `hcl:"ignore,optional" json:"ignore,omitempty"` // Files to ignore in the build context, this is the same as .dockerignore Args map[string]string `hcl:"args,optional" json:"args,omitempty"` // Build args to pass to the container } type Output struct { - Source string `hcl:"source" json"source"` // Source file or directory in container - Destination string `hcl:"destination" json"destination"` // Destination for copied file or directory + Source string `hcl:"source" json:"source"` // Source file or directory in container + Destination string `hcl:"destination" json:"destination"` // Destination for copied file or directory } func (b *Build) Process() error { diff --git a/pkg/utils/dirhash/example_dir/simple/README.md b/pkg/utils/dirhash/example_dir/simple/README.md new file mode 100644 index 00000000..34d64923 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/README.md @@ -0,0 +1,16 @@ +--- +title: Single Container Example +author: Nic Jackson +slug: container +browser_windows: http://consul-http.ingress.jumppad.run:8500 +env: + - SOMETHING=else +--- + +# Single Container + +This blueprint shows how you can create a single container with jumppad + +```shell +curl http://consul-http.ingress.jumppad.dev:8500 +``` \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/blueprint.hcl b/pkg/utils/dirhash/example_dir/simple/blueprint.hcl new file mode 100644 index 00000000..bde94133 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/blueprint.hcl @@ -0,0 +1,29 @@ +resource "blueprint" "container" { + title = "Simple container example" + author = "Nic Jackson" + slug = "container" + description = <<-EOF + This is the description for the blueprint, it can contain + markdown such as: + + ### Column headings + And things like bulleted lists + + * one + * two + * three + + #### Example Usage + It is also possible to incude code blocks + + ```hcl + module "one" { + source = "./subfolder" + + variables = { + nodes = 1 + } + } + ``` + EOF +} \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/conf.txt b/pkg/utils/dirhash/example_dir/simple/conf.txt new file mode 100644 index 00000000..0e731391 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/conf.txt @@ -0,0 +1 @@ +this is the contents of a file \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/consul_config/.keep b/pkg/utils/dirhash/example_dir/simple/consul_config/.keep new file mode 100644 index 00000000..e69de29b diff --git a/pkg/utils/dirhash/example_dir/simple/container.hcl b/pkg/utils/dirhash/example_dir/simple/container.hcl new file mode 100644 index 00000000..4c7b4292 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/container.hcl @@ -0,0 +1,133 @@ +variable "consul_version" { + default = "1.10.6" +} + +variable "envoy_version" { + default = "1.18.4" +} + +resource "template" "consul_config" { + + source = <<-EOF + data_dir = "{{ data_dir }}" + log_level = "DEBUG" + + datacenter = "dc1" + primary_datacenter = "dc1" + + server = true + + bootstrap_expect = 1 + ui = true + + bind_addr = "0.0.0.0" + client_addr = "0.0.0.0" + advertise_addr = "10.6.0.200" + + ports { + grpc = 8502 + } + + connect { + enabled = true + } + EOF + + destination = "${data("config")}/consul.hcl" + + variables = { + data_dir = "/tmp" + } +} + +resource "container" "consul_disabled" { + disabled = true + + image { + name = "consul:${variable.consul_version}" + } +} + +resource "container" "consul" { + image { + name = "consul:${variable.consul_version}" + } + + command = ["consul", "agent", "-config-file", "/config/config.hcl"] + + volume { + source = "./" + destination = "/files" + } + + volume { + source = resource.template.consul_config.destination + destination = "/config/config.hcl" + } + + network { + id = resource.network.consul.id + ip_address = "10.8.0.200" // optional + aliases = ["myalias"] + } + + environment = { + something = variable.something + foo = env("BAH") + file = file("./conf.txt") + abc = "123" + SHIPYARD_FOLDER = jumppad() + HOME_FOLDER = home() + } + + resources { + # Max CPU to consume, 1000 is one core, default unlimited + cpu = 200 + # Pin container to specified CPU cores, default all cores + cpu_pin = [0, 1] + # max memory in MB to consume, default unlimited + memory = 1024 + } + + port_range { + range = "8500-8502" + enable_host = true + } + + health_check { + timeout = "30s" + + http { + address = "http://localhost:8500" + success_codes = [200] + } + + tcp { + address = "localhost:8500" + } + + exec { + script = <<-EOF + #!/bin/sh -e + + ls -las + EOF + } + } + +} + +resource "sidecar" "envoy" { + target = resource.container.consul + + image { + name = "envoyproxy/envoy:v${variable.envoy_version}" + } + + command = ["tail", "-f", "/dev/null"] + + volume { + source = data("config") + destination = "/config" + } +} \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/defaults.vars b/pkg/utils/dirhash/example_dir/simple/defaults.vars new file mode 100644 index 00000000..b10b5385 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/defaults.vars @@ -0,0 +1 @@ +something = "blah blah" \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/network.hcl b/pkg/utils/dirhash/example_dir/simple/network.hcl new file mode 100644 index 00000000..3481ef8d --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/network.hcl @@ -0,0 +1,3 @@ +resource "network" "consul" { + subnet = "10.8.0.0/16" +} \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/output.hcl b/pkg/utils/dirhash/example_dir/simple/output.hcl new file mode 100644 index 00000000..79f107e5 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/output.hcl @@ -0,0 +1,4 @@ +output "consul_http_addr" { + description = "HTTP address for the consul server" + value = "http://${resource.container.consul.container_name}:8500" +} \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/test/container.feature b/pkg/utils/dirhash/example_dir/simple/test/container.feature new file mode 100644 index 00000000..680b6c75 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/test/container.feature @@ -0,0 +1,34 @@ +Feature: Docker Container + In order to test jumppad creates containers correctly + I should apply a blueprint which defines a simple container setup + and test the resources are created correctly + +Scenario: Single Container from Local Blueprint + Given I have a running blueprint + Then the following resources should be running + | name | + | resource.network.consul | + | resource.container.consul | + | resource.sidecar.envoy | + And the info "{.NetworkSettings.Ports['8501/tcp']}" for the running container "resource.container.consul" should exist + And the info "{.NetworkSettings.Ports['8500/tcp'][0].HostPort}" for the running container "resource.container.consul" should equal "8500" + And the info "{.NetworkSettings.Ports['8500/tcp'][0].HostPort}" for the running container "resource.container.consul" should contain "85" + And a HTTP call to "http://consul.container.shipyard.run:8500/v1/status/leader" should result in status 200 + +Scenario: Single Container from Local Blueprint with multiple runs + Given the environment variable "CONSUL_VERSION" has a value "" + And the environment variable "ENVOY_VERSION" has a value "" + And I have a running blueprint + Then the following resources should be running + | name | + | resource.network.consul | + | resource.container.consul | + | resource.sidecar.envoy | + And a HTTP call to "http://consul.container.shipyard.run:8500/v1/status/leader" should result in status 200 + And the response body should contain "10.6.0.200" + When I run the command "curl -k http://consul.container.shipyard.run:8500/v1/status/leader" + Then I expect the exit code to be 0 + Examples: + | consul | envoy | + | 1.11.1 | 1.18.4 | + | 1.10.6 | 1.18.4 | diff --git a/pkg/utils/dirhash/example_dir/simple/test/vars.feature b/pkg/utils/dirhash/example_dir/simple/test/vars.feature new file mode 100644 index 00000000..ff7df136 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/test/vars.feature @@ -0,0 +1,20 @@ +Feature: Docker Container + In order to test jumppad creates containers correctly using variables + I should apply a blueprint which defines a simple container setup + and test the resources are created correctly + +Scenario: Single Container with jumppad Variables + Given the following environment variables are set + | key | value | + | BAH | bah | + And the following jumppad variables are set + | key | value | + | something | set by test | + And I have a running blueprint + Then the following resources should be running + | name | + | resource.network.consul | + | resource.container.consul | + | resource.sidecar.envoy | + And the info "{.Config.Env}" for the running container "resource.container.consul" should contain "something=set by test" + And the info "{.Config.Env}" for the running container "resource.container.consul" should contain "foo=bah" \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/simple/variables.hcl b/pkg/utils/dirhash/example_dir/simple/variables.hcl new file mode 100644 index 00000000..17930b19 --- /dev/null +++ b/pkg/utils/dirhash/example_dir/simple/variables.hcl @@ -0,0 +1,4 @@ +variable "number_of_nodes" { + default = 1 + description = "Controls the number of nodes for the Consul server" +} \ No newline at end of file diff --git a/pkg/utils/dirhash/example_dir/symlink/simple b/pkg/utils/dirhash/example_dir/symlink/simple new file mode 120000 index 00000000..266a1dcd --- /dev/null +++ b/pkg/utils/dirhash/example_dir/symlink/simple @@ -0,0 +1 @@ +../simple \ No newline at end of file diff --git a/pkg/utils/dirhash/hash.go b/pkg/utils/dirhash/hash.go index ace7154e..6edb9bf8 100644 --- a/pkg/utils/dirhash/hash.go +++ b/pkg/utils/dirhash/hash.go @@ -1,3 +1,7 @@ +// This fle has been cloned and mofiied from the go source code +// to allow for the use of a custom ignore list and to replace go's standard +// file walker with the facebookgo/symwalk package + // Copyright 2018 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. @@ -20,6 +24,7 @@ import ( "strings" "github.com/facebookgo/symwalk" + "github.com/ryanuber/go-glob" ) // DefaultHash is the default hash function used in new go.sum entries. @@ -69,8 +74,9 @@ func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, er // HashDir returns the hash of the local file system directory dir, // replacing the directory name itself with prefix in the file names // used in the hash function. -func HashDir(dir, prefix string, hash Hash) (string, error) { - files, err := DirFiles(dir, prefix) +// optionally a list of files to ignore can be provided as a globbed list +func HashDir(dir, prefix string, hash Hash, ignore ...string) (string, error) { + files, err := DirFiles(dir, prefix, ignore...) if err != nil { return "", err } @@ -83,19 +89,43 @@ func HashDir(dir, prefix string, hash Hash) (string, error) { // DirFiles returns the list of files in the tree rooted at dir, // replacing the directory name dir with prefix in each name. // The resulting names always use forward slashes. -func DirFiles(dir, prefix string) ([]string, error) { +// A globbed list of files to ignore can be provided as a variadic argument +func DirFiles(dir, prefix string, ignore ...string) ([]string, error) { + var ignoredDirectories []string var files []string dir = filepath.Clean(dir) err := symwalk.Walk(dir, func(file string, info os.FileInfo, err error) error { if err != nil { return err } + + // check that the result is not in the ignore list + for _, i := range ignore { + ignore := glob.Glob(i, file) + if ignore { + // if we are ignoring a directory, we need to also ignore any children + // that are in this directory + if info.IsDir() { + ignoredDirectories = append(ignoredDirectories, file) + } + + return nil + } + } + if info.IsDir() { return nil } else if file == dir { return fmt.Errorf("%s is not a directory", dir) } + // check that the file is not in the ignored directories + for _, i := range ignoredDirectories { + if strings.HasPrefix(file, i) { + return nil + } + } + rel := file if dir != "." { rel = file[len(dir)+1:] diff --git a/pkg/utils/dirhash/hash_test.go b/pkg/utils/dirhash/hash_test.go new file mode 100644 index 00000000..bd5c8b04 --- /dev/null +++ b/pkg/utils/dirhash/hash_test.go @@ -0,0 +1,29 @@ +package dirhash + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGeneratesTheCorrectHashFromADirectory(t *testing.T) { + h, err := HashDir("./example_dir/simple", "", DefaultHash) + require.NoError(t, err) + + require.Equal(t, "h1:UuBnfJ+yMhiUs/lxAbzwgaPHLNFXwbJLxEbzZevtxEc=", h) +} + +func TestGeneratesTheCorrectHashFromADirectoryWithIgnore(t *testing.T) { + h, err := HashDir("./example_dir/simple", "", DefaultHash, "**/consul_config") + require.NoError(t, err) + + require.Equal(t, "h1:o3BPjmvmay4mdDA7rkU4Le34uvkRHuCPlW5MJ9p4T2s=", h) +} + +func TestGeneratesTheCorrectHashFromADirectoryContainingSymlinks(t *testing.T) { + h, err := HashDir("./example_dir/symlink", "", DefaultHash) + require.NoError(t, err) + + require.Equal(t, "h1:rVNDC8+SpMX32ggfqjNmud8EFVMphVqVLs0x+LcWLTA=", h) + +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index b06fd3dc..5d5af919 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -501,8 +501,10 @@ func SubnetIPs(subnet string) ([]string, error) { } // HashDir generates a hash of the given directory -func HashDir(dir string) (string, error) { - return dirhash.HashDir(dir, "", dirhash.DefaultHash) +// optionally a list of arguments to be ignored can be passed +// these arguments are expresed as a glob pattern +func HashDir(dir string, ignore ...string) (string, error) { + return dirhash.HashDir(dir, "", dirhash.DefaultHash, ignore...) } // HashFile returns a sha256 hash of the given file From b100bde002927c062bacc5a6cdd931907ec4f390 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 15 Sep 2023 08:40:53 +0100 Subject: [PATCH 4/5] remove nomad and k8s tests from podman, verified locally --- .github/workflows/build_and_deploy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index 9d543191..5042954c 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -181,9 +181,9 @@ jobs: folder: [ './examples/container', # './examples/build', - # './examples/docs', - './examples/single_k3s_cluster', - './examples/nomad', + './examples/docs', + # './examples/single_k3s_cluster', + # './examples/nomad', # './examples/local_exec', ] From e4cb9a6b150221d59f0b4002365b35029e9a3263 Mon Sep 17 00:00:00 2001 From: Nic Jackson Date: Fri, 15 Sep 2023 09:28:46 +0100 Subject: [PATCH 5/5] Update changelog --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 30256e86..7d7cfa27 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,10 @@ # Change Log +## version v0.5.47 +* Fix isuse where filepath.Walk does not respect symlinks +* Add `ignore` parameter to `build` resource to allow ignoring of files and folders + for Docker builds. + ## version v0.5.43 * Add ability to set datacenter for Nomad clusters * Fix permissions to use octet value when using data_with_permissions