mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
move artifactory remote state to backend
This commit is contained in:
parent
178eb6076e
commit
979faa5dbe
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
backendatlas "github.com/hashicorp/terraform/backend/atlas"
|
backendatlas "github.com/hashicorp/terraform/backend/atlas"
|
||||||
backendlocal "github.com/hashicorp/terraform/backend/local"
|
backendlocal "github.com/hashicorp/terraform/backend/local"
|
||||||
|
backendartifactory "github.com/hashicorp/terraform/backend/remote-state/artifactory"
|
||||||
backendAzure "github.com/hashicorp/terraform/backend/remote-state/azure"
|
backendAzure "github.com/hashicorp/terraform/backend/remote-state/azure"
|
||||||
backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul"
|
backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul"
|
||||||
backendetcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2"
|
backendetcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2"
|
||||||
@ -42,19 +43,19 @@ func init() {
|
|||||||
// Our hardcoded backends. We don't need to acquire a lock here
|
// Our hardcoded backends. We don't need to acquire a lock here
|
||||||
// since init() code is serial and can't spawn goroutines.
|
// since init() code is serial and can't spawn goroutines.
|
||||||
backends = map[string]func() backend.Backend{
|
backends = map[string]func() backend.Backend{
|
||||||
"atlas": func() backend.Backend { return &backendatlas.Backend{} },
|
"artifactory": func() backend.Backend { return backendartifactory.New() },
|
||||||
"http": func() backend.Backend { return backendhttp.New() },
|
"atlas": func() backend.Backend { return &backendatlas.Backend{} },
|
||||||
"local": func() backend.Backend { return &backendlocal.Local{} },
|
"http": func() backend.Backend { return backendhttp.New() },
|
||||||
"consul": func() backend.Backend { return backendconsul.New() },
|
"local": func() backend.Backend { return &backendlocal.Local{} },
|
||||||
"inmem": func() backend.Backend { return backendinmem.New() },
|
"consul": func() backend.Backend { return backendconsul.New() },
|
||||||
"swift": func() backend.Backend { return backendSwift.New() },
|
"inmem": func() backend.Backend { return backendinmem.New() },
|
||||||
"s3": func() backend.Backend { return backendS3.New() },
|
"swift": func() backend.Backend { return backendSwift.New() },
|
||||||
"azurerm": func() backend.Backend { return backendAzure.New() },
|
"s3": func() backend.Backend { return backendS3.New() },
|
||||||
"etcd": func() backend.Backend { return backendetcdv2.New() },
|
"azurerm": func() backend.Backend { return backendAzure.New() },
|
||||||
"etcdv3": func() backend.Backend { return backendetcdv3.New() },
|
"etcd": func() backend.Backend { return backendetcdv2.New() },
|
||||||
"gcs": func() backend.Backend { return backendGCS.New() },
|
"etcdv3": func() backend.Backend { return backendetcdv3.New() },
|
||||||
"manta": func() backend.Backend { return backendManta.New() },
|
"gcs": func() backend.Backend { return backendGCS.New() },
|
||||||
}
|
"manta": func() backend.Backend { return backendManta.New() },
|
||||||
|
|
||||||
"azure": func() backend.Backend {
|
"azure": func() backend.Backend {
|
||||||
return deprecateBackend(
|
return deprecateBackend(
|
||||||
|
100
backend/remote-state/artifactory/backend.go
Normal file
100
backend/remote-state/artifactory/backend.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package artifactory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
artifactory "github.com/lusis/go-artifactory/src/artifactory.v401"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() backend.Backend {
|
||||||
|
s := &schema.Backend{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"username": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARTIFACTORY_USERNAME", nil),
|
||||||
|
Description: "Username",
|
||||||
|
},
|
||||||
|
"password": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARTIFACTORY_PASSWORD", nil),
|
||||||
|
Description: "Password",
|
||||||
|
},
|
||||||
|
"url": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARTIFACTORY_URL", nil),
|
||||||
|
Description: "Artfactory base URL",
|
||||||
|
},
|
||||||
|
"repo": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "The repository name",
|
||||||
|
},
|
||||||
|
"subpath": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "Path within the repository",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &Backend{Backend: s}
|
||||||
|
b.Backend.ConfigureFunc = b.configure
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type Backend struct {
|
||||||
|
*schema.Backend
|
||||||
|
|
||||||
|
client *ArtifactoryClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) configure(ctx context.Context) error {
|
||||||
|
data := schema.FromContextBackendConfig(ctx)
|
||||||
|
|
||||||
|
userName := data.Get("username").(string)
|
||||||
|
password := data.Get("password").(string)
|
||||||
|
url := data.Get("url").(string)
|
||||||
|
repo := data.Get("repo").(string)
|
||||||
|
subpath := data.Get("subpath").(string)
|
||||||
|
|
||||||
|
clientConf := &artifactory.ClientConfig{
|
||||||
|
BaseURL: url,
|
||||||
|
Username: userName,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
nativeClient := artifactory.NewClient(clientConf)
|
||||||
|
|
||||||
|
b.client = &ArtifactoryClient{
|
||||||
|
nativeClient: &nativeClient,
|
||||||
|
userName: userName,
|
||||||
|
password: password,
|
||||||
|
url: url,
|
||||||
|
repo: repo,
|
||||||
|
subpath: subpath,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) States() ([]string, error) {
|
||||||
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) DeleteState(string) error {
|
||||||
|
return backend.ErrNamedStatesNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) State(name string) (state.State, error) {
|
||||||
|
if name != backend.DefaultStateName {
|
||||||
|
return nil, backend.ErrNamedStatesNotSupported
|
||||||
|
}
|
||||||
|
return &remote.State{
|
||||||
|
Client: b.client,
|
||||||
|
}, nil
|
||||||
|
}
|
63
backend/remote-state/artifactory/client.go
Normal file
63
backend/remote-state/artifactory/client.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package artifactory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
artifactory "github.com/lusis/go-artifactory/src/artifactory.v401"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ARTIF_TFSTATE_NAME = "terraform.tfstate"
|
||||||
|
|
||||||
|
type ArtifactoryClient struct {
|
||||||
|
nativeClient *artifactory.ArtifactoryClient
|
||||||
|
userName string
|
||||||
|
password string
|
||||||
|
url string
|
||||||
|
repo string
|
||||||
|
subpath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ArtifactoryClient) Get() (*remote.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 := &remote.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
|
||||||
|
}
|
@ -1,25 +1,21 @@
|
|||||||
package remote
|
package artifactory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestArtifactoryClient_impl(t *testing.T) {
|
func TestArtifactoryClient_impl(t *testing.T) {
|
||||||
var _ Client = new(ArtifactoryClient)
|
var _ remote.Client = new(ArtifactoryClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArtifactoryFactory(t *testing.T) {
|
func TestArtifactoryFactory(t *testing.T) {
|
||||||
// This test just instantiates the client. Shouldn't make any actual
|
// This test just instantiates the client. Shouldn't make any actual
|
||||||
// requests nor incur any costs.
|
// requests nor incur any costs.
|
||||||
|
|
||||||
config := make(map[string]string)
|
config := make(map[string]interface{})
|
||||||
|
|
||||||
// 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["url"] = "http://artifactory.local:8081/artifactory"
|
||||||
config["repo"] = "terraform-repo"
|
config["repo"] = "terraform-repo"
|
||||||
config["subpath"] = "myproject"
|
config["subpath"] = "myproject"
|
||||||
@ -30,12 +26,14 @@ func TestArtifactoryFactory(t *testing.T) {
|
|||||||
config["username"] = "test"
|
config["username"] = "test"
|
||||||
config["password"] = "testpass"
|
config["password"] = "testpass"
|
||||||
|
|
||||||
client, err := artifactoryFactory(config)
|
b := backend.TestBackendConfig(t, New(), config)
|
||||||
|
|
||||||
|
state, err := b.State(backend.DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error for valid config")
|
t.Fatalf("Error for valid config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
artifactoryClient := client.(*ArtifactoryClient)
|
artifactoryClient := state.(*remote.State).Client.(*ArtifactoryClient)
|
||||||
|
|
||||||
if artifactoryClient.nativeClient.Config.BaseURL != "http://artifactory.local:8081/artifactory" {
|
if artifactoryClient.nativeClient.Config.BaseURL != "http://artifactory.local:8081/artifactory" {
|
||||||
t.Fatalf("Incorrect url was populated")
|
t.Fatalf("Incorrect url was populated")
|
@ -1,117 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -44,6 +44,4 @@ func NewClient(t string, conf map[string]string) (Client, error) {
|
|||||||
|
|
||||||
// BuiltinClients is the list of built-in clients that can be used with
|
// BuiltinClients is the list of built-in clients that can be used with
|
||||||
// NewClient.
|
// NewClient.
|
||||||
var BuiltinClients = map[string]Factory{
|
var BuiltinClients = map[string]Factory{}
|
||||||
"artifactory": artifactoryFactory,
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user