Merge pull request #2781 from hashicorp/f-2678-getter-mode

Add support for go-getter modes
This commit is contained in:
Michael Schurter 2017-07-06 11:06:40 -07:00 committed by GitHub
commit 08b452adf5
21 changed files with 400 additions and 49 deletions

View file

@ -19,6 +19,8 @@ IMPROVEMENTS:
[GH-2610]
* client: Fingerprint all routable addresses on an interface including IPv6
addresses [GH-2536]
* client/artifact: Allow specifying a go-getter mode [GH-2781]
* client/artifact: Support non-Amazon S3-compatible sources [GH-2781]
* client/template: Support reading env vars from templates [GH-2654]
* config: Support Unix socket addresses for Consul [GH-2622]
* discovery: Advertise driver-specified IP address and port [GH-2709]

View file

@ -2,6 +2,9 @@ package api
import (
"fmt"
"path"
"path/filepath"
"strings"
"time"
@ -323,12 +326,30 @@ func (t *Task) Canonicalize(tg *TaskGroup, job *Job) {
type TaskArtifact struct {
GetterSource *string `mapstructure:"source"`
GetterOptions map[string]string `mapstructure:"options"`
GetterMode *string `mapstructure:"mode"`
RelativeDest *string `mapstructure:"destination"`
}
func (a *TaskArtifact) Canonicalize() {
if a.GetterMode == nil {
a.GetterMode = helper.StringToPtr("any")
}
if a.GetterSource == nil {
// Shouldn't be possible, but we don't want to panic
a.GetterSource = helper.StringToPtr("")
}
if a.RelativeDest == nil {
a.RelativeDest = helper.StringToPtr("local/")
switch *a.GetterMode {
case "file":
// File mode should default to local/filename
dest := *a.GetterSource
dest = path.Base(dest)
dest = filepath.Join("local", dest)
a.RelativeDest = &dest
default:
// Default to a directory
a.RelativeDest = helper.StringToPtr("local/")
}
}
}

View file

@ -219,3 +219,17 @@ func TestTask_Constrain(t *testing.T) {
t.Fatalf("expect: %#v, got: %#v", expect, task.Constraints)
}
}
func TestTask_Artifact(t *testing.T) {
a := TaskArtifact{
GetterSource: helper.StringToPtr("http://localhost/foo.txt"),
GetterMode: helper.StringToPtr("file"),
}
a.Canonicalize()
if *a.GetterMode != "file" {
t.Errorf("expected file but found %q", *a.GetterMode)
}
if *a.RelativeDest != "local/foo.txt" {
t.Errorf("expected local/foo.txt but found %q", *a.RelativeDest)
}
}

View file

@ -33,7 +33,7 @@ type EnvReplacer interface {
}
// getClient returns a client that is suitable for Nomad downloading artifacts.
func getClient(src, dst string) *gg.Client {
func getClient(src string, mode gg.ClientMode, dst string) *gg.Client {
lock.Lock()
defer lock.Unlock()
@ -50,7 +50,7 @@ func getClient(src, dst string) *gg.Client {
return &gg.Client{
Src: src,
Dst: dst,
Mode: gg.ClientModeAny,
Mode: mode,
Getters: getters,
}
}
@ -97,7 +97,17 @@ func GetArtifact(taskEnv EnvReplacer, artifact *structs.TaskArtifact, taskDir st
// Download the artifact
dest := filepath.Join(taskDir, artifact.RelativeDest)
if err := getClient(url, dest).Get(); err != nil {
// Convert from string getter mode to go-getter const
mode := gg.ClientModeAny
switch artifact.GetterMode {
case structs.GetterModeFile:
mode = gg.ClientModeFile
case structs.GetterModeDir:
mode = gg.ClientModeDir
}
if err := getClient(url, mode, dest).Get(); err != nil {
return newGetError(url, err, true)
}

View file

@ -654,6 +654,7 @@ func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
structsTask.Artifacts[k] = &structs.TaskArtifact{
GetterSource: *ta.GetterSource,
GetterOptions: ta.GetterOptions,
GetterMode: *ta.GetterMode,
RelativeDest: *ta.RelativeDest,
}
}

View file

@ -4,6 +4,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
@ -12,6 +13,7 @@ import (
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/kr/pretty"
)
func TestHTTP_JobsList(t *testing.T) {
@ -993,6 +995,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
GetterOptions: map[string]string{
"a": "b",
},
GetterMode: helper.StringToPtr("dir"),
RelativeDest: helper.StringToPtr("dest"),
},
},
@ -1178,6 +1181,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
GetterOptions: map[string]string{
"a": "b",
},
GetterMode: "dir",
RelativeDest: "dest",
},
},
@ -1213,7 +1217,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
structsJob := ApiJobToStructJob(apiJob)
if !reflect.DeepEqual(expected, structsJob) {
t.Fatalf("bad %#v", structsJob)
if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
}
}

View file

@ -788,6 +788,7 @@ func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error {
valid := []string{
"source",
"options",
"mode",
"destination",
}
if err := checkHCLKeys(o.Val, valid); err != nil {

View file

@ -166,6 +166,7 @@ func TestParse(t *testing.T) {
GetterOptions: map[string]string{
"checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c",
},
GetterMode: helper.StringToPtr("file"),
},
},
Vault: &api.Vault{

View file

@ -140,6 +140,7 @@ job "binstore-storagelocker" {
artifact {
source = "http://bar.com/artifact"
destination = "test/foo/"
mode = "file"
options {
checksum = "md5:ff1cc0d3432dad54d607c1505fb7245c"

View file

@ -2469,6 +2469,7 @@ func TestTaskDiff(t *testing.T) {
GetterOptions: map[string]string{
"bar": "baz",
},
GetterMode: "dir",
RelativeDest: "bar",
},
},
@ -2487,6 +2488,7 @@ func TestTaskDiff(t *testing.T) {
GetterOptions: map[string]string{
"bam": "baz",
},
GetterMode: "file",
RelativeDest: "bam",
},
},
@ -2498,6 +2500,12 @@ func TestTaskDiff(t *testing.T) {
Type: DiffTypeAdded,
Name: "Artifact",
Fields: []*FieldDiff{
{
Type: DiffTypeAdded,
Name: "GetterMode",
Old: "",
New: "file",
},
{
Type: DiffTypeAdded,
Name: "GetterOptions[bam]",
@ -2522,6 +2530,12 @@ func TestTaskDiff(t *testing.T) {
Type: DiffTypeDeleted,
Name: "Artifact",
Fields: []*FieldDiff{
{
Type: DiffTypeDeleted,
Name: "GetterMode",
Old: "dir",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "GetterOptions[bar]",

View file

@ -78,6 +78,10 @@ const (
ProtocolVersion = "protocol"
APIMajorVersion = "api.major"
APIMinorVersion = "api.minor"
GetterModeAny = "any"
GetterModeFile = "file"
GetterModeDir = "dir"
)
// RPCInfo is used to describe common information about query
@ -3405,6 +3409,10 @@ type TaskArtifact struct {
// go-getter.
GetterOptions map[string]string
// GetterMode is the go-getter.ClientMode for fetching resources.
// Defaults to "any" but can be set to "file" or "dir".
GetterMode string
// RelativeDest is the download destination given relative to the task's
// directory.
RelativeDest string
@ -3453,6 +3461,17 @@ func (ta *TaskArtifact) Validate() error {
mErr.Errors = append(mErr.Errors, fmt.Errorf("source must be specified"))
}
switch ta.GetterMode {
case "":
// Default to any
ta.GetterMode = GetterModeAny
case GetterModeAny, GetterModeFile, GetterModeDir:
// Ok
default:
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid artifact mode %q; must be one of: %s, %s, %s",
ta.GetterMode, GetterModeAny, GetterModeFile, GetterModeDir))
}
escaped, err := PathEscapesAllocDir("task", ta.RelativeDest)
if err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))

View file

@ -222,13 +222,17 @@ None
### HTTP (`http`)
None
#### Basic Authentication
To use HTTP basic authentication with go-getter, simply prepend `username:password@` to the
hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`. All special
characters, including the username and password, must be URL encoded.
### S3 (`s3`)
S3 takes various access configurations in the URL. Note that it will also
read these from standard AWS environment variables if they're set. If
the query parameters are present, these take priority.
read these from standard AWS environment variables if they're set. S3 compliant servers like Minio
are also supported. If the query parameters are present, these take priority.
* `aws_access_key_id` - AWS access key.
* `aws_access_key_secret` - AWS access key secret.
@ -240,6 +244,14 @@ If you use go-getter and want to use an EC2 IAM Instance Profile to avoid
using credentials, then just omit these and the profile, if available will
be used automatically.
### Using S3 with Minio
If you use go-gitter for Minio support, you must consider the following:
* `aws_access_key_id` (required) - Minio access key.
* `aws_access_key_secret` (required) - Minio access key secret.
* `region` (optional - defaults to us-east-1) - Region identifier to use.
* `version` (optional - fefaults to Minio default) - Configuration file format.
#### S3 Bucket Examples
S3 has several addressing schemes used to reference your bucket. These are
@ -250,4 +262,5 @@ Some examples for these addressing schemes:
- s3::https://s3-eu-west-1.amazonaws.com/bucket/foo
- bucket.s3.amazonaws.com/foo
- bucket.s3-eu-west-1.amazonaws.com/foo/bar
- "s3::http://127.0.0.1:9000/test-bucket/hello.txt?aws_access_key_id=KEYID&aws_access_key_secret=SECRETKEY&region=us-east-2"

View file

@ -1,5 +1,5 @@
version: "build-{branch}-{build}"
image: Visual Studio 2015
image: Visual Studio 2017
clone_folder: c:\gopath\github.com\hashicorp\go-getter
environment:
GOPATH: c:\gopath

View file

@ -11,7 +11,8 @@ import (
"runtime"
"sort"
"strings"
"testing"
"github.com/mitchellh/go-testing-interface"
)
// TestDecompressCase is a single test case for testing decompressors
@ -24,7 +25,7 @@ type TestDecompressCase struct {
}
// TestDecompressor is a helper function for testing generic decompressors.
func TestDecompressor(t *testing.T, d Decompressor, cases []TestDecompressCase) {
func TestDecompressor(t testing.T, d Decompressor, cases []TestDecompressCase) {
for _, tc := range cases {
t.Logf("Testing: %s", tc.Input)
@ -87,7 +88,7 @@ func TestDecompressor(t *testing.T, d Decompressor, cases []TestDecompressCase)
}
}
func testListDir(t *testing.T, path string) []string {
func testListDir(t testing.T, path string) []string {
var result []string
err := filepath.Walk(path, func(sub string, info os.FileInfo, err error) error {
if err != nil {
@ -116,7 +117,7 @@ func testListDir(t *testing.T, path string) []string {
return result
}
func testMD5(t *testing.T, path string) string {
func testMD5(t testing.T, path string) string {
f, err := os.Open(path)
if err != nil {
t.Fatalf("err: %s", err)

View file

@ -28,7 +28,7 @@ func (g *S3Getter) ClientMode(u *url.URL) (ClientMode, error) {
}
// Create client config
config := g.getAWSConfig(region, creds)
config := g.getAWSConfig(region, u, creds)
sess := session.New(config)
client := s3.New(sess)
@ -84,7 +84,7 @@ func (g *S3Getter) Get(dst string, u *url.URL) error {
return err
}
config := g.getAWSConfig(region, creds)
config := g.getAWSConfig(region, u, creds)
sess := session.New(config)
client := s3.New(sess)
@ -139,7 +139,7 @@ func (g *S3Getter) GetFile(dst string, u *url.URL) error {
return err
}
config := g.getAWSConfig(region, creds)
config := g.getAWSConfig(region, u, creds)
sess := session.New(config)
client := s3.New(sess)
return g.getObject(client, dst, bucket, path, version)
@ -174,7 +174,7 @@ func (g *S3Getter) getObject(client *s3.S3, dst, bucket, key, version string) er
return err
}
func (g *S3Getter) getAWSConfig(region string, creds *credentials.Credentials) *aws.Config {
func (g *S3Getter) getAWSConfig(region string, url *url.URL, creds *credentials.Credentials) *aws.Config {
conf := &aws.Config{}
if creds == nil {
// Grab the metadata URL
@ -195,6 +195,14 @@ func (g *S3Getter) getAWSConfig(region string, creds *credentials.Credentials) *
})
}
if creds != nil {
conf.Endpoint = &url.Host
conf.S3ForcePathStyle = aws.Bool(true)
if url.Scheme == "http" {
conf.DisableSSL = aws.Bool(true)
}
}
conf.Credentials = creds
if region != "" {
conf.Region = aws.String(region)
@ -204,29 +212,48 @@ func (g *S3Getter) getAWSConfig(region string, creds *credentials.Credentials) *
}
func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, creds *credentials.Credentials, err error) {
// Expected host style: s3.amazonaws.com. They always have 3 parts,
// although the first may differ if we're accessing a specific region.
hostParts := strings.Split(u.Host, ".")
if len(hostParts) != 3 {
err = fmt.Errorf("URL is not a valid S3 URL")
return
}
// This just check whether we are dealing with S3 or
// any other S3 compliant service. S3 has a predictable
// url as others do not
if strings.Contains(u.Host, "amazonaws.com") {
// Expected host style: s3.amazonaws.com. They always have 3 parts,
// although the first may differ if we're accessing a specific region.
hostParts := strings.Split(u.Host, ".")
if len(hostParts) != 3 {
err = fmt.Errorf("URL is not a valid S3 URL")
return
}
// Parse the region out of the first part of the host
region = strings.TrimPrefix(strings.TrimPrefix(hostParts[0], "s3-"), "s3")
if region == "" {
region = "us-east-1"
}
// Parse the region out of the first part of the host
region = strings.TrimPrefix(strings.TrimPrefix(hostParts[0], "s3-"), "s3")
if region == "" {
region = "us-east-1"
}
pathParts := strings.SplitN(u.Path, "/", 3)
if len(pathParts) != 3 {
err = fmt.Errorf("URL is not a valid S3 URL")
return
}
pathParts := strings.SplitN(u.Path, "/", 3)
if len(pathParts) != 3 {
err = fmt.Errorf("URL is not a valid S3 URL")
return
}
bucket = pathParts[1]
path = pathParts[2]
version = u.Query().Get("version")
bucket = pathParts[1]
path = pathParts[2]
version = u.Query().Get("version")
} else {
pathParts := strings.SplitN(u.Path, "/", 3)
if len(pathParts) != 3 {
err = fmt.Errorf("URL is not a valid S3 complaint URL")
return
}
bucket = pathParts[1]
path = pathParts[2]
version = u.Query().Get("version")
region = u.Query().Get("region")
if region == "" {
region = "us-east-1"
}
}
_, hasAwsId := u.Query()["aws_access_key_id"]
_, hasAwsSecret := u.Query()["aws_access_key_secret"]

65
vendor/github.com/hashicorp/go-sockaddr/GNUmakefile generated vendored Normal file
View file

@ -0,0 +1,65 @@
TOOLS= golang.org/x/tools/cover
GOCOVER_TMPFILE?= $(GOCOVER_FILE).tmp
GOCOVER_FILE?= .cover.out
GOCOVERHTML?= coverage.html
FIND=`/usr/bin/which 2> /dev/null gfind find | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
XARGS=`/usr/bin/which 2> /dev/null gxargs xargs | /usr/bin/grep -v ^no | /usr/bin/head -n 1`
test:: $(GOCOVER_FILE)
@$(MAKE) -C cmd/sockaddr test
cover:: coverage_report
$(GOCOVER_FILE)::
@${FIND} . -type d ! -path '*cmd*' ! -path '*.git*' -print0 | ${XARGS} -0 -I % sh -ec "cd % && rm -f $(GOCOVER_TMPFILE) && go test -coverprofile=$(GOCOVER_TMPFILE)"
@echo 'mode: set' > $(GOCOVER_FILE)
@${FIND} . -type f ! -path '*cmd*' ! -path '*.git*' -name "$(GOCOVER_TMPFILE)" -print0 | ${XARGS} -0 -n1 cat $(GOCOVER_TMPFILE) | grep -v '^mode: ' >> ${PWD}/$(GOCOVER_FILE)
$(GOCOVERHTML): $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE) -o $(GOCOVERHTML)
coverage_report:: $(GOCOVER_FILE)
go tool cover -html=$(GOCOVER_FILE)
audit_tools::
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
audit::
deadcode
go tool vet -all *.go
go tool vet -shadow=true *.go
golint *.go
ineffassign .
gocyclo -over 65 *.go
misspell *.go
clean::
rm -f $(GOCOVER_FILE) $(GOCOVERHTML)
dev::
@go build
@$(MAKE) -B -C cmd/sockaddr sockaddr
install::
@go install
@$(MAKE) -C cmd/sockaddr install
doc::
@echo Visit: http://127.0.0.1:6161/pkg/github.com/hashicorp/go-sockaddr/
godoc -http=:6161 -goroot $GOROOT
world::
@set -e; \
for os in solaris darwin freebsd linux windows; do \
for arch in amd64; do \
printf "Building on %s-%s\n" "$${os}" "$${arch}" ; \
env GOOS="$${os}" GOARCH="$${arch}" go build -o /dev/null; \
done; \
done
$(MAKE) -C cmd/sockaddr world

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,52 @@
# go-testing-interface
go-testing-interface is a Go library that exports an interface that
`*testing.T` implements as well as a runtime version you can use in its
place.
The purpose of this library is so that you can export test helpers as a
public API without depending on the "testing" package, since you can't
create a `*testing.T` struct manually. This lets you, for example, use the
public testing APIs to generate mock data at runtime, rather than just at
test time.
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/go-testing-interface).
Given a test helper written using `go-testing-interface` like this:
import "github.com/mitchellh/go-testing-interface"
func TestHelper(t testing.T) {
t.Fatal("I failed")
}
You can call the test helper in a real test easily:
import "testing"
func TestThing(t *testing.T) {
TestHelper(t)
}
You can also call the test helper at runtime if needed:
import "github.com/mitchellh/go-testing-interface"
func main() {
TestHelper(&testing.RuntimeT{})
}
## Why?!
**Why would I call a test helper that takes a *testing.T at runtime?**
You probably shouldn't. The only use case I've seen (and I've had) for this
is to implement a "dev mode" for a service where the test helpers are used
to populate mock data, create a mock DB, perhaps run service dependencies
in-memory, etc.
Outside of a "dev mode", I've never seen a use case for this and I think
there shouldn't be one since the point of the `testing.T` interface is that
you can fail immediately.

View file

@ -0,0 +1,70 @@
package testing
import (
"fmt"
"log"
)
// T is the interface that mimics the standard library *testing.T.
//
// In unit tests you can just pass a *testing.T struct. At runtime, outside
// of tests, you can pass in a RuntimeT struct from this package.
type T interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Fail()
FailNow()
Failed() bool
Log(args ...interface{})
Logf(format string, args ...interface{})
}
// RuntimeT implements T and can be instantiated and run at runtime to
// mimic *testing.T behavior. Unlike *testing.T, this will simply panic
// for calls to Fatal. For calls to Error, you'll have to check the errors
// list to determine whether to exit yourself.
type RuntimeT struct {
failed bool
}
func (t *RuntimeT) Error(args ...interface{}) {
log.Println(fmt.Sprintln(args...))
t.Fail()
}
func (t *RuntimeT) Errorf(format string, args ...interface{}) {
log.Println(fmt.Sprintf(format, args...))
t.Fail()
}
func (t *RuntimeT) Fatal(args ...interface{}) {
log.Println(fmt.Sprintln(args...))
t.FailNow()
}
func (t *RuntimeT) Fatalf(format string, args ...interface{}) {
log.Println(fmt.Sprintf(format, args...))
t.FailNow()
}
func (t *RuntimeT) Fail() {
t.failed = true
}
func (t *RuntimeT) FailNow() {
panic("testing.T failed, see logs for output (if any)")
}
func (t *RuntimeT) Failed() bool {
return t.failed
}
func (t *RuntimeT) Log(args ...interface{}) {
log.Println(fmt.Sprintln(args...))
}
func (t *RuntimeT) Logf(format string, args ...interface{}) {
log.Println(fmt.Sprintf(format, args...))
}

16
vendor/vendor.json vendored
View file

@ -689,16 +689,16 @@
"revisionTime": "2017-06-02T22:43:19Z"
},
{
"checksumSHA1": "hHoNT6CfqcDF1+yXRKnz/duuwIk=",
"checksumSHA1": "Sozy3aNAPBleUfiECj0jnaYjw2k=",
"path": "github.com/hashicorp/go-getter",
"revision": "e48f67b534e614bf7fbd978fd0020f61a17b7527",
"revisionTime": "2017-04-05T22:15:29Z"
"revision": "2814e6fb2ca5b3bd950c97eff22553ecb3c7f77b",
"revisionTime": "2017-07-06T02:51:20Z"
},
{
"checksumSHA1": "9J+kDr29yDrwsdu2ULzewmqGjpA=",
"path": "github.com/hashicorp/go-getter/helper/url",
"revision": "e48f67b534e614bf7fbd978fd0020f61a17b7527",
"revisionTime": "2017-04-05T22:15:29Z"
"revision": "2814e6fb2ca5b3bd950c97eff22553ecb3c7f77b",
"revisionTime": "2017-07-06T02:51:20Z"
},
{
"checksumSHA1": "zvmksNyW6g+Fd/bywd4vcn8rp+M=",
@ -981,6 +981,12 @@
"revision": "4fdf99ab29366514c69ccccddab5dc58b8d84062",
"revisionTime": "2017-03-09T13:30:38Z"
},
{
"checksumSHA1": "j83WZHiKiPF86Hj5QMQdWboizls=",
"path": "github.com/mitchellh/go-testing-interface",
"revision": "477c2d05a845d8b55912a5a7993b9b24abcc5ef8",
"revisionTime": "2017-04-30T14:07:22Z"
},
{
"path": "github.com/mitchellh/hashstructure",
"revision": "1ef5c71b025aef149d12346356ac5973992860bc"

View file

@ -46,10 +46,15 @@ unarchived before the starting the task.
## `artifact` Parameters
- `destination` `(string: "local/$1")` - Specifies the directory path to download the
artifact, relative to the root of the task's directory. If omitted, the
default value is to place the binary in `local/`. The destination is treated
as a directory and source files will be downloaded into that directory path.
- `destination` `(string: "local/")` - Specifies the directory path to download
the artifact, relative to the root of the task's directory. If omitted, the
default value is to place the artifact in `local/`. The destination is treated
as a directory unless `mode` is set to `file`. Source files will be downloaded
into that directory path.
- `mode` `(string: "any")` - One of `any`, `file`, or `dir`. If set to `file`
the `destination` must be a file, not a directory. By default the
`destination` will be `local/<filename>`.
- `options` `(map<string|string>: nil)` - Specifies configuration parameters to
fetch the artifact. The key-value pairs map directly to parameters appended to
@ -150,11 +155,13 @@ artifact {
}
```
### Download from an S3 Bucket
### Download from an S3-compatible Bucket
These examples download artifacts from Amazon S3. There are several different
types of [S3 bucket addressing][s3-bucket-addr] and [S3 region-specific
endpoints][s3-region-endpoints].
endpoints][s3-region-endpoints]. As of Nomad 0.6 non-Amazon S3-compatible
endpoints like [Minio] are supported, but you must explicitly set the "s3::"
prefix.
This example uses path-based notation on a publicly-accessible bucket:
@ -194,5 +201,6 @@ artifact {
```
[go-getter]: https://github.com/hashicorp/go-getter "HashiCorp go-getter Library"
[Minio]: https://www.minio.io/
[s3-bucket-addr]: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html#access-bucket-intro "Amazon S3 Bucket Addressing"
[s3-region-endpoints]: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region "Amazon S3 Region Endpoints"