Merge branch 'lusis-artifactory-remote-state'
This commit is contained in:
commit
b2964a6443
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -82,6 +82,13 @@ The following backends are supported:
|
|||
* `acl` - [Canned ACL](http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl)
|
||||
to be applied to the state file.
|
||||
|
||||
* Artifactory - Stores the state as an artifact in a given repository in
|
||||
Artifactory. Requires the `url`, `username`, `password`, `repo` and `subpath`
|
||||
variables. Generic HTTP repositories are supported, and state from different
|
||||
configurations may be kept at different subpaths within the repository. The URL
|
||||
must include the path to the Artifactory installation - it will likely end in
|
||||
`/artifactory`.
|
||||
|
||||
* HTTP - Stores the state using a simple REST client. State will be fetched
|
||||
via GET, updated via POST, and purged with DELETE. Requires the `address` variable.
|
||||
|
||||
|
|
Loading…
Reference in New Issue