Skip to content

Commit

Permalink
backport of commit 21d80a2
Browse files Browse the repository at this point in the history
  • Loading branch information
alisdair committed Nov 18, 2020
1 parent c71aa16 commit 7253dec
Show file tree
Hide file tree
Showing 195 changed files with 4,422 additions and 3,853 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ orbs:
executors:
go:
docker:
- image: docker.mirror.hashicorp.services/circleci/golang:1.15
- image: circleci/golang:1.15
environment:
CONSUL_VERSION: 1.7.2
GOMAXPROCS: 4
Expand Down
21 changes: 0 additions & 21 deletions .github/workflows/main.yml

This file was deleted.

83 changes: 70 additions & 13 deletions CHANGELOG.md

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
# using the current working directory.
#
# This produces a docker image that contains a working Terraform binary along
# with all of its source code. This is not what produces the official releases
# in the "terraform" namespace on Dockerhub; those images include only
# the officially-released binary from releases.hashicorp.com and are
# built by the (closed-source) official release process.
# with all of its source code, which is what gets released on hub.docker.com
# as terraform:full. The main releases (terraform:latest, terraform:light and
# the release tags) are lighter images including only the officially-released
# binary from releases.hashicorp.com; these are built instead from
# scripts/docker-release/Dockerfile-release.

FROM docker.mirror.hashicorp.services/golang:alpine
FROM golang:alpine
LABEL maintainer="HashiCorp Terraform Team <[email protected]>"

RUN apk add --no-cache git bash openssh
Expand Down
194 changes: 194 additions & 0 deletions backend/atlas/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package atlas

import (
"fmt"
"net/url"
"os"
"strings"
"sync"

"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/states/remote"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
)

const EnvVarToken = "ATLAS_TOKEN"
const EnvVarAddress = "ATLAS_ADDRESS"

// Backend is an implementation of EnhancedBackend that performs all operations
// in Atlas. State must currently also be stored in Atlas, although it is worth
// investigating in the future if state storage can be external as well.
type Backend struct {
// CLI and Colorize control the CLI output. If CLI is nil then no CLI
// output will be done. If CLIColor is nil then no coloring will be done.
CLI cli.Ui
CLIColor *colorstring.Colorize

// ContextOpts are the base context options to set when initializing a
// Terraform context. Many of these will be overridden or merged by
// Operation. See Operation for more details.
ContextOpts *terraform.ContextOpts

//---------------------------------------------------------------
// Internal fields, do not set
//---------------------------------------------------------------
// stateClient is the legacy state client, setup in Configure
stateClient *stateClient

// opLock locks operations
opLock sync.Mutex
}

var _ backend.Backend = (*Backend)(nil)

// New returns a new initialized Atlas backend.
func New() *Backend {
return &Backend{}
}

func (b *Backend) ConfigSchema() *configschema.Block {
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"name": {
Type: cty.String,
Required: true,
Description: "Full name of the environment in Terraform Enterprise, such as 'myorg/myenv'",
},
"access_token": {
Type: cty.String,
Optional: true,
Description: "Access token to use to access Terraform Enterprise; the ATLAS_TOKEN environment variable is used if this argument is not set",
},
"address": {
Type: cty.String,
Optional: true,
Description: "Base URL for your Terraform Enterprise installation; the ATLAS_ADDRESS environment variable is used if this argument is not set, finally falling back to a default of 'https://atlas.hashicorp.com/' if neither are set.",
},
},
}
}

func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

name := obj.GetAttr("name").AsString()
if ct := strings.Count(name, "/"); ct != 1 {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Invalid workspace selector",
`The "name" argument must be an organization name and a workspace name separated by a slash, such as "acme/network-production".`,
cty.Path{cty.GetAttrStep{Name: "name"}},
))
}

if v := obj.GetAttr("address"); !v.IsNull() {
addr := v.AsString()
_, err := url.Parse(addr)
if err != nil {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Invalid Terraform Enterprise URL",
fmt.Sprintf(`The "address" argument must be a valid URL: %s.`, err),
cty.Path{cty.GetAttrStep{Name: "address"}},
))
}
}

return obj, diags
}

func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics

client := &stateClient{
// This is optionally set during Atlas Terraform runs.
RunId: os.Getenv("ATLAS_RUN_ID"),
}

name := obj.GetAttr("name").AsString() // assumed valid due to PrepareConfig method
slashIdx := strings.Index(name, "/")
client.User = name[:slashIdx]
client.Name = name[slashIdx+1:]

if v := obj.GetAttr("access_token"); !v.IsNull() {
client.AccessToken = v.AsString()
} else {
client.AccessToken = os.Getenv(EnvVarToken)
if client.AccessToken == "" {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Missing Terraform Enterprise access token",
`The "access_token" argument must be set unless the ATLAS_TOKEN environment variable is set to provide the authentication token for Terraform Enterprise.`,
cty.Path{cty.GetAttrStep{Name: "access_token"}},
))
}
}

if v := obj.GetAttr("address"); !v.IsNull() {
addr := v.AsString()
addrURL, err := url.Parse(addr)
if err != nil {
// We already validated the URL in PrepareConfig, so this shouldn't happen
panic(err)
}
client.Server = addr
client.ServerURL = addrURL
} else {
addr := os.Getenv(EnvVarAddress)
if addr == "" {
addr = defaultAtlasServer
}
addrURL, err := url.Parse(addr)
if err != nil {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Invalid Terraform Enterprise URL",
fmt.Sprintf(`The ATLAS_ADDRESS environment variable must contain a valid URL: %s.`, err),
cty.Path{cty.GetAttrStep{Name: "address"}},
))
}
client.Server = addr
client.ServerURL = addrURL
}

b.stateClient = client

return diags
}

func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported
}

func (b *Backend) DeleteWorkspace(name string) error {
return backend.ErrWorkspacesNotSupported
}

func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
if name != backend.DefaultStateName {
return nil, backend.ErrWorkspacesNotSupported
}

return &remote.State{Client: b.stateClient}, nil
}

// Colorize returns the Colorize structure that can be used for colorizing
// output. This is gauranteed to always return a non-nil value and so is useful
// as a helper to wrap any potentially colored strings.
func (b *Backend) Colorize() *colorstring.Colorize {
if b.CLIColor != nil {
return b.CLIColor
}

return &colorstring.Colorize{
Colors: colorstring.DefaultColors,
Disable: true,
}
}
53 changes: 53 additions & 0 deletions backend/atlas/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package atlas

import (
"os"
"testing"

"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/backend"
)

func TestImpl(t *testing.T) {
var _ backend.Backend = new(Backend)
var _ backend.CLI = new(Backend)
}

func TestConfigure_envAddr(t *testing.T) {
defer os.Setenv("ATLAS_ADDRESS", os.Getenv("ATLAS_ADDRESS"))
os.Setenv("ATLAS_ADDRESS", "http:https://foo.com")

b := New()
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("foo/bar"),
"address": cty.NullVal(cty.String),
"access_token": cty.StringVal("placeholder"),
}))
for _, diag := range diags {
t.Error(diag)
}

if got, want := b.stateClient.Server, "http:https://foo.com"; got != want {
t.Fatalf("wrong URL %#v; want %#v", got, want)
}
}

func TestConfigure_envToken(t *testing.T) {
defer os.Setenv("ATLAS_TOKEN", os.Getenv("ATLAS_TOKEN"))
os.Setenv("ATLAS_TOKEN", "foo")

b := New()
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("foo/bar"),
"address": cty.NullVal(cty.String),
"access_token": cty.NullVal(cty.String),
}))
for _, diag := range diags {
t.Error(diag)
}

if got, want := b.stateClient.AccessToken, "foo"; got != want {
t.Fatalf("wrong access token %#v; want %#v", got, want)
}
}
13 changes: 13 additions & 0 deletions backend/atlas/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package atlas

import (
"github.com/hashicorp/terraform/backend"
)

// backend.CLI impl.
func (b *Backend) CLIInit(opts *backend.CLIOpts) error {
b.CLI = opts.CLI
b.CLIColor = opts.CLIColor
b.ContextOpts = opts.ContextOpts
return nil
}
Loading

0 comments on commit 7253dec

Please sign in to comment.