Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datasources added: artifactory_file & artifactory_fileinfo #65

Merged
merged 10 commits into from
Dec 16, 2019
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/atlassian/terraform-provider-artifactory

require (
github.com/atlassian/go-artifactory/v2 v2.4.0
github.com/atlassian/go-artifactory/v2 v2.5.0
github.com/hashicorp/terraform v0.12.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/atlassian/go-artifactory/v2 v2.4.0 h1:qj2nlDREa8BB03a09BcE+bj7gwlj/VXuD2hBvV80PDM=
github.com/atlassian/go-artifactory/v2 v2.4.0/go.mod h1:mMEbxu89yTyKev4mysL03aSioTEdZ8+08KuMGG7myUY=
github.com/atlassian/go-artifactory/v2 v2.5.0 h1:NKs9kuGgb2Gj+pU+Y7BzFlx6D/e6P82zP3m/FJZpy40=
github.com/atlassian/go-artifactory/v2 v2.5.0/go.mod h1:mMEbxu89yTyKev4mysL03aSioTEdZ8+08KuMGG7myUY=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.18 h1:Hb3+b9HCqrOrbAtFstUWg7H5TQ+/EcklJtE8VShVs8o=
Expand Down
167 changes: 167 additions & 0 deletions pkg/artifactory/datasource_artifactory_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package artifactory

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/atlassian/go-artifactory/v2/artifactory"
"github.com/atlassian/go-artifactory/v2/artifactory/v1"
"github.com/hashicorp/terraform/helper/schema"
"io"
"os"
)

func dataSourceArtifactoryFile() *schema.Resource {
return &schema.Resource{
Read: dataSourceFileRead,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
},
"path": {
Type: schema.TypeString,
Required: true,
},
"created": {
Type: schema.TypeString,
Computed: true,
},
"created_by": {
Type: schema.TypeString,
Computed: true,
},
"last_modified": {
Type: schema.TypeString,
Computed: true,
},
"modified_by": {
Type: schema.TypeString,
Computed: true,
},
"last_updated": {
Type: schema.TypeString,
Computed: true,
},
"download_uri": {
Type: schema.TypeString,
Computed: true,
},
"mimetype": {
Type: schema.TypeString,
Computed: true,
},
"size": {
Type: schema.TypeInt,
Computed: true,
},
"md5": {
Type: schema.TypeString,
Computed: true,
},
"sha1": {
Type: schema.TypeString,
Computed: true,
},
"sha256": {
Type: schema.TypeString,
Computed: true,
},
"output_path": {
Type: schema.TypeString,
Required: true,
},
"force_overwrite": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}

func dataSourceFileRead(d *schema.ResourceData, m interface{}) error {
c := m.(*artifactory.Artifactory)

repository := d.Get("repository").(string)
path := d.Get("path").(string)
outputPath := d.Get("output_path").(string)
forceOverwrite := d.Get("force_overwrite").(bool)

fileInfo, _, err := c.V1.Artifacts.FileInfo(context.Background(), repository, path)
if err != nil {
return err
}

skip, err := SkipDownload(fileInfo, outputPath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that output_path is required, do we still need this? Maybe the verification of the SHAs can be here instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to refactor that part, however I believe it's not safe to verify the checksum only. There are multiple reasons why the checksum verification could fail:

  • file doesn't exist
  • any problems with the hash library
  • io issues

However we only want to proceed with the download in the first case. Having that said, we'll still need to check whether the file exists or not.

if err != nil && !forceOverwrite {
return err
}

if !skip {
outFile, err := os.Create(outputPath)
if err != nil {
return err
}

defer outFile.Close()

fileInfo, _, err = c.V1.Artifacts.FileContents(context.Background(), repository, path, outFile)
if err != nil {
return err
}
}

return packFileInfo(fileInfo, d)
}

func SkipDownload(fileInfo *v1.FileInfo, path string) (bool, error) {
const skip = true
const dontSkip = false

if path == "" {
// no path specified, nothing to download
return skip, nil
}

if FileExists(path) {
chks_matches, err := VerifySha256Checksum(path, *fileInfo.Checksums.Sha256)

if chks_matches {
return skip, nil
} else if err != nil {
return dontSkip, err
} else {
return dontSkip, fmt.Errorf("Local file differs from upstream version")
}
} else {
return dontSkip, nil
}
}

func FileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}

func VerifySha256Checksum(path string, expectedSha256 string) (bool, error) {
f, err := os.Open(path)
if err != nil {
return false, err
}
defer f.Close()

hasher := sha256.New()

if _, err := io.Copy(hasher, f); err != nil {
return false, err
}

return hex.EncodeToString(hasher.Sum(nil)) == expectedSha256, nil
}
91 changes: 91 additions & 0 deletions pkg/artifactory/datasource_artifactory_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package artifactory

import (
"github.com/atlassian/go-artifactory/v2/artifactory/v1"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path/filepath"
"testing"
)

func TestSkipDownload(t *testing.T) {
const testString = "test content"
const expectedSha256 = "6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72"

file, err := CreateTempFile(testString)

assert.Nil(t, err)

defer CloseAndRemove(file)

existingPath, _ := filepath.Abs(file.Name())
nonExistingPath := existingPath + "-doesnt-exist"

sha256 := expectedSha256
fileInfo := new(v1.FileInfo)
fileInfo.Checksums = new(v1.Checksums)
fileInfo.Checksums.Sha256 = &sha256

skip, err := SkipDownload(fileInfo, existingPath)
assert.Equal(t, true, skip) // file exists, checksum matches => skip
assert.Nil(t, err)

skip, err = SkipDownload(fileInfo, nonExistingPath)
assert.Equal(t, false, skip) // file doesn't exist => dont skip
assert.Nil(t, err)

sha256 = "6666666666666666666666666666666666666666666666666666666666666666"
fileInfo.Checksums.Sha256 = &sha256

skip, err = SkipDownload(fileInfo, existingPath)
assert.Equal(t, false, skip) // file exists, checksum doesnt match => dont skip & err
assert.NotNil(t, err)
}

func TestFileExists(t *testing.T) {
tmpFile, err := CreateTempFile("test")

assert.Nil(t, err)

defer CloseAndRemove(tmpFile)

existingPath, _ := filepath.Abs(tmpFile.Name())
nonExistingPath := existingPath + "-doesnt-exist"

assert.Equal(t, true, FileExists(existingPath))
assert.Equal(t, false, FileExists(nonExistingPath))
}

func TestVerifySha256Checksum(t *testing.T) {
const testString = "test content"
const expectedSha256 = "6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72"

file, err := CreateTempFile(testString)

assert.Nil(t, err)

defer CloseAndRemove(file)

filePath, _ := filepath.Abs(file.Name())

sha256Verified, err := VerifySha256Checksum(filePath, expectedSha256)

assert.Nil(t, err)
assert.Equal(t, true, sha256Verified)
}

func CreateTempFile(content string) (f *os.File, err error) {
file, err := ioutil.TempFile(os.TempDir(), "terraform-provider-artifactory-")

if content != "" {
file.WriteString(content)
}

return file, err
}

func CloseAndRemove(f *os.File) {
f.Close()
os.Remove(f.Name())
}
112 changes: 112 additions & 0 deletions pkg/artifactory/datasource_artifactory_fileinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package artifactory

import (
"context"
"fmt"
"github.com/atlassian/go-artifactory/v2/artifactory"
"github.com/atlassian/go-artifactory/v2/artifactory/v1"
"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceArtifactoryFileInfo() *schema.Resource {
return &schema.Resource{
Read: dataSourceFileInfoRead,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
},
"path": {
Type: schema.TypeString,
Required: true,
},
"created": {
Type: schema.TypeString,
Computed: true,
},
"created_by": {
Type: schema.TypeString,
Computed: true,
},
"last_modified": {
Type: schema.TypeString,
Computed: true,
},
"modified_by": {
Type: schema.TypeString,
Computed: true,
},
"last_updated": {
Type: schema.TypeString,
Computed: true,
},
"download_uri": {
Type: schema.TypeString,
Computed: true,
},
"mimetype": {
Type: schema.TypeString,
Computed: true,
},
"size": {
Type: schema.TypeInt,
Computed: true,
},
"md5": {
Type: schema.TypeString,
Computed: true,
},
"sha1": {
Type: schema.TypeString,
Computed: true,
},
"sha256": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceFileInfoRead(d *schema.ResourceData, m interface{}) error {
c := m.(*artifactory.Artifactory)

repository := d.Get("repository").(string)
path := d.Get("path").(string)

fileInfo, _, err := c.V1.Artifacts.FileInfo(context.Background(), repository, path)
if err != nil {
return err
}

return packFileInfo(fileInfo, d)
}

func packFileInfo(fileInfo *v1.FileInfo, d *schema.ResourceData) error {
hasErr := false
logErr := cascadingErr(&hasErr)

d.SetId(*fileInfo.DownloadUri)

logErr(d.Set("created", *fileInfo.Created))
logErr(d.Set("created_by", *fileInfo.CreatedBy))
logErr(d.Set("last_modified", *fileInfo.LastModified))
logErr(d.Set("modified_by", *fileInfo.ModifiedBy))
logErr(d.Set("last_updated", *fileInfo.LastUpdated))
logErr(d.Set("download_uri", *fileInfo.DownloadUri))
logErr(d.Set("mimetype", *fileInfo.MimeType))
logErr(d.Set("size", *fileInfo.Size))

if fileInfo.Checksums != nil {
logErr(d.Set("md5", *fileInfo.Checksums.Md5))
logErr(d.Set("sha1", *fileInfo.Checksums.Sha1))
logErr(d.Set("sha256", *fileInfo.Checksums.Sha256))
}

if hasErr {
return fmt.Errorf("failed to pack fileInfo")
}

return nil
}
Loading