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

add artifactory remote state storage #3684

Merged
merged 1 commit into from
Dec 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions state/remote/artifactory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package remote

import (
"crypto/md5"
"fmt"
"os"
"strings"

artifactory "github.com/lusis/go-artifactory/src/artifactory.v401"
)

const ARTIF_TFSTATE_NAME = "terraform.tfstate"

func artifactoryFactory(conf map[string]string) (Client, error) {
userName, ok := conf["username"]
if !ok {
userName = os.Getenv("ARTIFACTORY_USERNAME")
if userName == "" {
return nil, fmt.Errorf(
"missing 'username' configuration or ARTIFACTORY_USERNAME environment variable")
}
}
password, ok := conf["password"]
if !ok {
password = os.Getenv("ARTIFACTORY_PASSWORD")
if password == "" {
return nil, fmt.Errorf(
"missing 'password' configuration or ARTIFACTORY_PASSWORD environment variable")
}
}
url, ok := conf["url"]
if !ok {
url = os.Getenv("ARTIFACTORY_URL")
if url == "" {
return nil, fmt.Errorf(
"missing 'url' configuration or ARTIFACTORY_URL environment variable")
}
}
repo, ok := conf["repo"]
if !ok {
return nil, fmt.Errorf(
"missing 'repo' configuration")
}
subpath, ok := conf["subpath"]
if !ok {
return nil, fmt.Errorf(
"missing 'subpath' configuration")
}

clientConf := &artifactory.ClientConfig{
BaseURL: url,
Username: userName,
Password: password,
}
nativeClient := artifactory.NewClient(clientConf)

return &ArtifactoryClient{
nativeClient: &nativeClient,
userName: userName,
password: password,
url: url,
repo: repo,
subpath: subpath,
}, nil

}

type ArtifactoryClient struct {
nativeClient *artifactory.ArtifactoryClient
userName string
password string
url string
repo string
subpath string
}

func (c *ArtifactoryClient) Get() (*Payload, error) {
p := fmt.Sprintf("%s/%s/%s", c.repo, c.subpath, ARTIF_TFSTATE_NAME)
output, err := c.nativeClient.Get(p, make(map[string]string))
if err != nil {
if strings.Contains(err.Error(), "404") {
return nil, nil
}
return nil, err
}

// TODO: migrate to using X-Checksum-Md5 header from artifactory
// needs to be exposed by go-artifactory first

hash := md5.Sum(output)
payload := &Payload{
Data: output,
MD5: hash[:md5.Size],
}

// If there was no data, then return nil
if len(payload.Data) == 0 {
return nil, nil
}

return payload, nil
}

func (c *ArtifactoryClient) Put(data []byte) error {
p := fmt.Sprintf("%s/%s/%s", c.repo, c.subpath, ARTIF_TFSTATE_NAME)
if _, err := c.nativeClient.Put(p, string(data), make(map[string]string)); err == nil {
return nil
} else {
return fmt.Errorf("Failed to upload state: %v", err)
}
}

func (c *ArtifactoryClient) Delete() error {
p := fmt.Sprintf("%s/%s/%s", c.repo, c.subpath, ARTIF_TFSTATE_NAME)
err := c.nativeClient.Delete(p)
return err
}
55 changes: 55 additions & 0 deletions state/remote/artifactory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package remote

import (
"testing"
)

func TestArtifactoryClient_impl(t *testing.T) {
var _ Client = new(ArtifactoryClient)
}

func TestArtifactoryFactory(t *testing.T) {
// This test just instantiates the client. Shouldn't make any actual
// requests nor incur any costs.

config := make(map[string]string)

// Empty config is an error
_, err := artifactoryFactory(config)
if err == nil {
t.Fatalf("Empty config should be error")
}

config["url"] = "http://artifactory.local:8081/artifactory"
config["repo"] = "terraform-repo"
config["subpath"] = "myproject"

// For this test we'll provide the credentials as config. The
// acceptance tests implicitly test passing credentials as
// environment variables.
config["username"] = "test"
config["password"] = "testpass"

client, err := artifactoryFactory(config)
if err != nil {
t.Fatalf("Error for valid config")
}

artifactoryClient := client.(*ArtifactoryClient)

if artifactoryClient.nativeClient.Config.BaseURL != "http://artifactory.local:8081/artifactory" {
t.Fatalf("Incorrect url was populated")
}
if artifactoryClient.nativeClient.Config.Username != "test" {
t.Fatalf("Incorrect username was populated")
}
if artifactoryClient.nativeClient.Config.Password != "testpass" {
t.Fatalf("Incorrect password was populated")
}
if artifactoryClient.repo != "terraform-repo" {
t.Fatalf("Incorrect repo was populated")
}
if artifactoryClient.subpath != "myproject" {
t.Fatalf("Incorrect subpath was populated")
}
}
13 changes: 7 additions & 6 deletions state/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ func NewClient(t string, conf map[string]string) (Client, error) {
// BuiltinClients is the list of built-in clients that can be used with
// NewClient.
var BuiltinClients = map[string]Factory{
"atlas": atlasFactory,
"consul": consulFactory,
"etcd": etcdFactory,
"http": httpFactory,
"s3": s3Factory,
"swift": swiftFactory,
"atlas": atlasFactory,
"consul": consulFactory,
"etcd": etcdFactory,
"http": httpFactory,
"s3": s3Factory,
"swift": swiftFactory,
"artifactory": artifactoryFactory,

// This is used for development purposes only.
"_local": fileFactory,
Expand Down