From 5a540c603cac9cb54885b7a9e3138c40f373a4af Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Fri, 20 Apr 2018 15:38:47 +0100 Subject: [PATCH 1/3] Add ability to initialise non-existent resources As described in https://github.com/concourse/s3-resource/issues/21, it would be helpful if S3 resources could be configured to be available even if they do not yet exist in S3. This commit adds parameters to support that. For S3 versioning, the `initial_version` parameter describes what the version available should be named if the file does not exist in your S3 bucket. For filepath-based versioning, the `initial_path` parameter describes the path to use if there are no regex matches in your S3 bucket. A missing S3 resource will be empty by default, but you can specify content for it. `initial_content_text` allows specifying text content, or base64-encoded content can be provided to `initial_content_binary`. --- check/command.go | 11 +++ check/command_test.go | 116 ++++++++++++++++++++++++------ in/command.go | 75 +++++++++++++------ in/command_test.go | 163 +++++++++++++++++++++++++++++++++++++++++- models.go | 21 ++++++ 5 files changed, 340 insertions(+), 46 deletions(-) diff --git a/check/command.go b/check/command.go index 4d04da32..b7d302d0 100644 --- a/check/command.go +++ b/check/command.go @@ -32,6 +32,13 @@ func (command *Command) Run(request Request) (Response, error) { func (command *Command) checkByRegex(request Request) Response { extractions := versions.GetBucketFileVersions(command.s3client, request.Source) + if request.Source.InitialPath != "" { + extraction, ok := versions.Extract(request.Source.InitialPath, request.Source.Regexp) + if ok { + extractions = append([]versions.Extraction{extraction}, extractions...) + } + } + if len(extractions) == 0 { return nil } @@ -53,6 +60,10 @@ func (command *Command) checkByVersionedFile(request Request) Response { s3resource.Fatal("finding versions", err) } + if request.Source.InitialVersion != "" { + bucketVersions = append(bucketVersions, request.Source.InitialVersion) + } + if len(bucketVersions) == 0 { return response } diff --git a/check/command_test.go b/check/command_test.go index 06f42285..c23f8a88 100644 --- a/check/command_test.go +++ b/check/command_test.go @@ -66,6 +66,24 @@ var _ = Describe("Check Command", func() { )) }) + Context("when the initial version is set", func() { + It("still returns the latest version", func() { + request.Version.Path = "" + request.Source.InitialPath = "files/abc-0.0.tgz" + request.Source.Regexp = "files/abc-(.*).tgz" + + response, err := command.Run(request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(response).Should(HaveLen(1)) + Ω(response).Should(ConsistOf( + s3resource.Version{ + Path: "files/abc-3.53.tgz", + }, + )) + }) + }) + Context("when the regexp does not match anything", func() { It("does not explode", func() { request.Source.Regexp = "no-files/missing-(.*).tgz" @@ -74,6 +92,24 @@ var _ = Describe("Check Command", func() { Ω(response).Should(HaveLen(0)) }) + + Context("when the initial version is set", func() { + It("returns the initial version", func() { + request.Version.Path = "" + request.Source.InitialPath = "no-files/missing-0.0.tgz" + request.Source.Regexp = "no-files/missing-(.*).tgz" + + response, err := command.Run(request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(response).Should(HaveLen(1)) + Ω(response).Should(ConsistOf( + s3resource.Version{ + Path: "no-files/missing-0.0.tgz", + }, + )) + }) + }) }) Context("when the regex does not match the previous version", func() { @@ -114,30 +150,66 @@ var _ = Describe("Check Command", func() { }) Context("when using versioned file", func() { - BeforeEach(func() { - s3client.BucketFileVersionsReturns([]string{ - "file-version-3", - "file-version-2", - "file-version-1", - }, nil) + Context("when there are existing versions", func() { + BeforeEach(func() { + s3client.BucketFileVersionsReturns([]string{ + "file-version-3", + "file-version-2", + "file-version-1", + }, nil) + }) + + It("includes all versions from the previous one and the current one", func() { + request.Version.VersionID = "file-version-2" + request.Source.VersionedFile = "files/versioned-file" + + response, err := command.Run(request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(response).Should(HaveLen(2)) + Ω(response).Should(ConsistOf( + s3resource.Version{ + VersionID: "file-version-2", + }, + s3resource.Version{ + VersionID: "file-version-3", + }, + )) + }) }) - It("includes all versions from the previous one and the current one", func() { - request.Version.VersionID = "file-version-2" - request.Source.VersionedFile = "files/(.*).tgz" - - response, err := command.Run(request) - Ω(err).ShouldNot(HaveOccurred()) - - Ω(response).Should(HaveLen(2)) - Ω(response).Should(ConsistOf( - s3resource.Version{ - VersionID: "file-version-2", - }, - s3resource.Version{ - VersionID: "file-version-3", - }, - )) + Context("when no version exists", func() { + BeforeEach(func() { + s3client.BucketFileVersionsReturns([]string{}, nil) + }) + + It("returns no versions", func() { + request.Version.VersionID = "" + request.Source.VersionedFile = "files/versioned-file" + + response, err := command.Run(request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(response).Should(HaveLen(0)) + }) + + Context("when the initial version is set", func() { + It("returns the initial version", func() { + request.Version.VersionID = "" + request.Source.VersionedFile = "files/versioned-file" + request.Source.InitialVersion = "file-version-0" + + response, err := command.Run(request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(response).Should(HaveLen(1)) + Ω(response).Should(ConsistOf( + s3resource.Version{ + VersionID: "file-version-0", + }, + )) + }) + }) }) }) }) diff --git a/in/command.go b/in/command.go index a5120cec..0eaad94d 100644 --- a/in/command.go +++ b/in/command.go @@ -1,6 +1,7 @@ package in import ( + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -53,6 +54,8 @@ func (command *Command) Run(destinationDir string, request Request) (Response, e var remotePath string var versionNumber string var versionID string + var url string + var isInitialVersion bool if request.Source.Regexp != "" { if request.Version.Path == "" { @@ -67,40 +70,62 @@ func (command *Command) Run(destinationDir string, request Request) (Response, e } versionNumber = extraction.VersionNumber + + isInitialVersion = request.Source.InitialPath != "" && request.Version.Path == request.Source.InitialPath } else { remotePath = request.Source.VersionedFile versionNumber = request.Version.VersionID versionID = request.Version.VersionID - } - - err = command.downloadFile( - request.Source.Bucket, - remotePath, - versionID, - destinationDir, - path.Base(remotePath), - ) - if err != nil { - return Response{}, err + isInitialVersion = request.Source.InitialVersion != "" && request.Version.VersionID == request.Source.InitialVersion } - if request.Params.Unpack { - destinationPath := filepath.Join(destinationDir, path.Base(remotePath)) - mime := archiveMimetype(destinationPath) - if mime == "" { - return Response{}, fmt.Errorf("not an archive: %s", destinationPath) + if isInitialVersion { + if request.Source.InitialContentText != "" || request.Source.InitialContentBinary == "" { + err = command.createInitialFile(destinationDir, path.Base(remotePath), []byte(request.Source.InitialContentText)) + if err != nil { + return Response{}, err + } } - - err = extractArchive(mime, destinationPath) + if request.Source.InitialContentBinary != "" { + b, err := base64.StdEncoding.DecodeString(request.Source.InitialContentBinary) + if err != nil { + return Response{}, errors.New("failed to decode initial_content_binary, make sure it's base64 encoded") + } + err = command.createInitialFile(destinationDir, path.Base(remotePath), b) + if err != nil { + return Response{}, err + } + } + } else { + err = command.downloadFile( + request.Source.Bucket, + remotePath, + versionID, + destinationDir, + path.Base(remotePath), + ) if err != nil { return Response{}, err } - } - url := command.urlProvider.GetURL(request, remotePath) - if err = command.writeURLFile(destinationDir, url); err != nil { - return Response{}, err + if request.Params.Unpack { + destinationPath := filepath.Join(destinationDir, path.Base(remotePath)) + mime := archiveMimetype(destinationPath) + if mime == "" { + return Response{}, fmt.Errorf("not an archive: %s", destinationPath) + } + + err = extractArchive(mime, destinationPath) + if err != nil { + return Response{}, err + } + } + + url = command.urlProvider.GetURL(request, remotePath) + if err = command.writeURLFile(destinationDir, url); err != nil { + return Response{}, err + } } err = command.writeVersionFile(versionNumber, destinationDir) @@ -146,6 +171,10 @@ func (command *Command) downloadFile(bucketName string, remotePath string, versi ) } +func (command *Command) createInitialFile(destDir string, destFile string, data []byte) error { + return ioutil.WriteFile(filepath.Join(destDir, destFile), []byte(data), 0644) +} + func (command *Command) metadata(remotePath string, private bool, url string) []s3resource.MetadataPair { remoteFilename := filepath.Base(remotePath) @@ -156,7 +185,7 @@ func (command *Command) metadata(remotePath string, private bool, url string) [] }, } - if !private { + if url != "" && !private { metadata = append(metadata, s3resource.MetadataPair{ Name: "url", Value: url, diff --git a/in/command_test.go b/in/command_test.go index 161e6354..7f3814a6 100644 --- a/in/command_test.go +++ b/in/command_test.go @@ -195,7 +195,7 @@ var _ = Describe("In Command", func() { request.Source.Regexp = "not-matching-anything" }) - It("returns an h", func() { + It("returns an error", func() { _, err := command.Run(destDir, request) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("regex does not match provided version")) @@ -372,6 +372,167 @@ var _ = Describe("In Command", func() { }) }) }) + + Context("when the requested path is the initial path", func() { + var initialFilename string + + BeforeEach(func() { + initialFilename = "a-file-0.0" + request.Source.InitialPath = "files/a-file-0.0" + request.Version.Path = request.Source.InitialPath + request.Source.InitialContentText = "the hard questions are hard 🙈" + }) + + It("it creates a file containing the initial text content", func() { + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, initialFilename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal(request.Source.InitialContentText)) + }) + + Context("when the initial content is binary", func() { + BeforeEach(func() { + request.Source.InitialContentText = "" + request.Source.InitialContentBinary = "dGhlIGhhcmQgcXVlc3Rpb25zIGFyZSBoYXJkIPCfmYg=" + }) + It("it creates a file containing the initial binary content", func() { + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, initialFilename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal("the hard questions are hard 🙈")) + }) + + Context("when base64 decoding fails", func() { + BeforeEach(func() { + request.Source.InitialContentBinary = "not base64 data 🙈" + }) + It("should return with an error", func() { + _, err := command.Run(destDir, request) + Ω(err).Should(HaveOccurred()) + }) + }) + }) + + It("should not write the URL file", func() { + urlPath := filepath.Join(destDir, "url") + Ω(urlPath).ShouldNot(ExistOnFilesystem()) + + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(urlPath).ShouldNot(ExistOnFilesystem()) + }) + + It("should not include a URL in the metadata", func() { + response, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + for _, metadatum := range response.Metadata { + Ω(metadatum.Name).ShouldNot(Equal("url")) + } + }) + + It("should not attempt to unpack the initial content", func() { + request.Params.Unpack = true + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, initialFilename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal(request.Source.InitialContentText)) + }) + }) + + Context("when the requested version is the initial version", func() { + var filename = "testfile" + + BeforeEach(func() { + request.Source.Regexp = "" + request.Source.VersionedFile = "file/testfile" + request.Source.InitialVersion = "0.0.0" + request.Version.VersionID = request.Source.InitialVersion + request.Source.InitialContentText = "the hard questions are hard 🙈" + }) + + It("it creates a file containing the initial text content", func() { + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, filename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal(request.Source.InitialContentText)) + }) + + Context("when the initial content is binary", func() { + BeforeEach(func() { + request.Source.InitialContentText = "" + request.Source.InitialContentBinary = "dGhlIGhhcmQgcXVlc3Rpb25zIGFyZSBoYXJkIPCfmYg=" + }) + It("it creates a file containing the initial binary content", func() { + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, filename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal("the hard questions are hard 🙈")) + }) + + Context("when base64 decoding fails", func() { + BeforeEach(func() { + request.Source.InitialContentBinary = "not base64 data 🙈" + }) + It("should return with an error", func() { + _, err := command.Run(destDir, request) + Ω(err).Should(HaveOccurred()) + }) + }) + }) + + It("should not write the URL file", func() { + urlPath := filepath.Join(destDir, "url") + Ω(urlPath).ShouldNot(ExistOnFilesystem()) + + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(urlPath).ShouldNot(ExistOnFilesystem()) + }) + + It("should not include a URL in the metadata", func() { + response, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + for _, metadatum := range response.Metadata { + Ω(metadatum.Name).ShouldNot(Equal("url")) + } + }) + + It("should not attempt to unpack the initial content", func() { + request.Params.Unpack = true + _, err := command.Run(destDir, request) + Ω(err).ShouldNot(HaveOccurred()) + + contentFile := filepath.Join(destDir, filename) + Ω(contentFile).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(contentFile) + Ω(err).ShouldNot(HaveOccurred()) + Ω(string(contents)).Should(Equal(request.Source.InitialContentText)) + }) + }) }) }) diff --git a/models.go b/models.go index aef7d41c..91c01f9b 100644 --- a/models.go +++ b/models.go @@ -16,6 +16,10 @@ type Source struct { SSEKMSKeyId string `json:"sse_kms_key_id"` UseV2Signing bool `json:"use_v2_signing"` SkipSSLVerification bool `json:"skip_ssl_verification"` + InitialVersion string `json:"initial_version"` + InitialPath string `json:"initial_path"` + InitialContentText string `json:"initial_content_text"` + InitialContentBinary string `json:"initial_content_binary"` } func (source Source) IsValid() (bool, string) { @@ -23,6 +27,23 @@ func (source Source) IsValid() (bool, string) { return false, "please specify either regexp or versioned_file" } + if source.Regexp != "" && source.InitialVersion != "" { + return false, "please use initial_path when regexp is set" + } + + if source.VersionedFile != "" && source.InitialPath != "" { + return false, "please use initial_version when versioned_file is set" + } + + if source.InitialContentText != "" && source.InitialContentBinary != "" { + return false, "please use intial_content_text or initial_content_binary but not both" + } + + hasInitialContent := source.InitialContentText != "" || source.InitialContentBinary != "" + if hasInitialContent && source.InitialVersion == "" && source.InitialPath == "" { + return false, "please specify initial_version or initial_path if initial content is set" + } + return true, "" } From 57fb1eea8d39e25777116032e6742c09091f2e46 Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Fri, 20 Apr 2018 15:45:10 +0100 Subject: [PATCH 2/3] Add integration tests for resource initialisation --- integration/in_test.go | 115 +++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 27 deletions(-) diff --git a/integration/in_test.go b/integration/in_test.go index 3decfacf..136a3404 100644 --- a/integration/in_test.go +++ b/integration/in_test.go @@ -20,11 +20,11 @@ import ( var _ = Describe("in", func() { var ( - command *exec.Cmd - stdin *bytes.Buffer - session *gexec.Session - destDir string - + command *exec.Cmd + inRequest in.Request + stdin *bytes.Buffer + session *gexec.Session + destDir string expectedExitStatus int ) @@ -47,6 +47,10 @@ var _ = Describe("in", func() { JustBeforeEach(func() { var err error + + err = json.NewEncoder(stdin).Encode(inRequest) + Ω(err).ShouldNot(HaveOccurred()) + session, err = gexec.Start(command, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) @@ -55,8 +59,6 @@ var _ = Describe("in", func() { }) Context("with a versioned_file and a regex", func() { - var inRequest in.Request - BeforeEach(func() { inRequest = in.Request{ Source: s3resource.Source{ @@ -73,9 +75,6 @@ var _ = Describe("in", func() { } expectedExitStatus = 1 - - err := json.NewEncoder(stdin).Encode(inRequest) - Ω(err).ShouldNot(HaveOccurred()) }) It("returns an error", func() { @@ -84,7 +83,6 @@ var _ = Describe("in", func() { }) Context("when the given version only has a path", func() { - var inRequest in.Request var directoryPrefix string BeforeEach(func() { @@ -104,9 +102,6 @@ var _ = Describe("in", func() { }, } - err := json.NewEncoder(stdin).Encode(inRequest) - Ω(err).ShouldNot(HaveOccurred()) - tempFile, err := ioutil.TempFile("", "file-to-upload") Ω(err).ShouldNot(HaveOccurred()) tempFile.Close() @@ -168,10 +163,48 @@ var _ = Describe("in", func() { Ω(err).ShouldNot(HaveOccurred()) Ω(urlContents).Should(Equal([]byte(buildEndpoint(bucketName, endpoint) + "/in-request-files/some-file-2"))) }) + + Context("when the path matches the initial path", func() { + BeforeEach(func() { + inRequest.Source.InitialPath = filepath.Join(directoryPrefix, "some-file-0.0.0") + inRequest.Source.InitialContentText = "initial content" + inRequest.Version.Path = inRequest.Source.InitialPath + }) + + It("uses the initial content", func() { + reader := bytes.NewBuffer(session.Out.Contents()) + + var response in.Response + err := json.NewDecoder(reader).Decode(&response) + + Ω(response).Should(Equal(in.Response{ + Version: s3resource.Version{ + Path: inRequest.Source.InitialPath, + }, + Metadata: []s3resource.MetadataPair{ + { + Name: "filename", + Value: "some-file-0.0.0", + }, + }, + })) + + Ω(filepath.Join(destDir, "some-file-0.0.0")).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(filepath.Join(destDir, "some-file-0.0.0")) + Ω(err).ShouldNot(HaveOccurred()) + Ω(contents).Should(Equal([]byte(inRequest.Source.InitialContentText))) + + Ω(filepath.Join(destDir, "version")).Should(BeARegularFile()) + versionContents, err := ioutil.ReadFile(filepath.Join(destDir, "version")) + Ω(err).ShouldNot(HaveOccurred()) + Ω(versionContents).Should(Equal([]byte("0.0.0"))) + + Ω(filepath.Join(destDir, "url")).ShouldNot(BeARegularFile()) + }) + }) }) Context("when the given version has a versionID and path", func() { - var inRequest in.Request var directoryPrefix string var expectedVersion string @@ -208,9 +241,6 @@ var _ = Describe("in", func() { Ω(err).ShouldNot(HaveOccurred()) expectedVersion = versions[1] inRequest.Version.VersionID = expectedVersion - - err = json.NewEncoder(stdin).Encode(inRequest) - Ω(err).ShouldNot(HaveOccurred()) }) AfterEach(func() { @@ -260,10 +290,49 @@ var _ = Describe("in", func() { Ω(err).ShouldNot(HaveOccurred()) Ω(urlContents).Should(Equal([]byte(buildEndpoint(versionedBucketName, endpoint) + "/in-request-files-versioned/some-file?versionId=" + expectedVersion))) }) + + Context("when the version ID matches the InitialVersion", func() { + BeforeEach(func() { + inRequest.Source.InitialVersion = "0.0.0" + inRequest.Source.InitialContentText = "initial content" + inRequest.Version.VersionID = inRequest.Source.InitialVersion + expectedVersion = inRequest.Source.InitialVersion + }) + + It("uses the initial content", func() { + reader := bytes.NewBuffer(session.Out.Contents()) + + var response in.Response + err := json.NewDecoder(reader).Decode(&response) + + Ω(response).Should(Equal(in.Response{ + Version: s3resource.Version{ + VersionID: inRequest.Source.InitialVersion, + }, + Metadata: []s3resource.MetadataPair{ + { + Name: "filename", + Value: "some-file", + }, + }, + })) + + Ω(filepath.Join(destDir, "some-file")).Should(BeARegularFile()) + contents, err := ioutil.ReadFile(filepath.Join(destDir, "some-file")) + Ω(err).ShouldNot(HaveOccurred()) + Ω(contents).Should(Equal([]byte(inRequest.Source.InitialContentText))) + + Ω(filepath.Join(destDir, "version")).Should(BeARegularFile()) + versionContents, err := ioutil.ReadFile(filepath.Join(destDir, "version")) + Ω(err).ShouldNot(HaveOccurred()) + Ω(versionContents).Should(Equal([]byte(expectedVersion))) + + Ω(filepath.Join(destDir, "url")).ShouldNot(BeARegularFile()) + }) + }) }) Context("when cloudfront_url is set", func() { - var inRequest in.Request var directoryPrefix string BeforeEach(func() { @@ -301,9 +370,6 @@ var _ = Describe("in", func() { _, err = s3client.UploadFile(bucketName, filepath.Join(directoryPrefix, fmt.Sprintf("some-file-%d", i)), tempFile.Name(), s3resource.NewUploadFileOptions()) Ω(err).ShouldNot(HaveOccurred()) } - - err = os.Remove(tempFile.Name()) - Ω(err).ShouldNot(HaveOccurred()) }) AfterEach(func() { @@ -349,8 +415,6 @@ var _ = Describe("in", func() { }) Context("when cloudfront_url is set but has too few dots", func() { - var inRequest in.Request - BeforeEach(func() { inRequest = in.Request{ Source: s3resource.Source{ @@ -368,9 +432,6 @@ var _ = Describe("in", func() { } expectedExitStatus = 1 - - err := json.NewEncoder(stdin).Encode(inRequest) - Ω(err).ShouldNot(HaveOccurred()) }) It("returns an error", func() { From cb40258df7f69d3229d246f58cf33755a5e082b6 Mon Sep 17 00:00:00 2001 From: bandesz Date: Thu, 19 Apr 2018 10:59:07 +0100 Subject: [PATCH 3/3] Add documentation for S3 resource initialisation --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f85ee41f..6c80b106 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,22 @@ One of the following two options must be specified: without resorting to version numbers. This property is the path to the file in your S3 bucket. +### Initial state + +If no resource versions exist you can set up this resource to emit an initial version with a specified content. This won't create a real resource in S3 but only create an initial version for Concourse. The resource file will be created as usual when you `get` a resource with an initial version. + +You can define one of the following two options: + +* `initial_path`: *Optional.* Must be used with the `regexp` option. You should set this to the file path containing the initial version which would match the given regexp. E.g. if `regexp` is `file/build-(.*).zip`, then `initial_path` might be `file/build-0.0.0.zip`. The resource version will be `0.0.0` in this case. + +* `initial_version`: *Optional.* Must be used with the `versioned_file` option. This will be the resource version. + +By default the resource file will be created with no content when `get` runs. You can set the content by using one of the following options: + +* `initial_content_text`: *Optional.* Initial content as a string. + +* `initial_content_binary`: *Optional.* You can pass binary content as a base64 encoded string. + ## Behavior ### `check`: Extract versions from the bucket. @@ -85,7 +101,7 @@ Places the following files in the destination: #### Parameters -* `unpack`: *Optional.* If true and the file is an archive (tar, gzipped tar, other gzipped file, or zip), unpack the file. Gzipped tarballs will be both ungzipped and untarred. +* `unpack`: *Optional.* If true and the file is an archive (tar, gzipped tar, other gzipped file, or zip), unpack the file. Gzipped tarballs will be both ungzipped and untarred. It is ignored when `get` is running on the initial version. ### `out`: Upload an object to the bucket.