Skip to content

Commit

Permalink
feat: adding support for defining the backend file content (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigorfk authored Jan 5, 2024
1 parent c0ddf25 commit 2a21288
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 9 deletions.
7 changes: 7 additions & 0 deletions apis/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ type ProviderConfigSpec struct {
// +optional
Configuration *string `json:"configuration,omitempty"`

// Terraform backend file configuration content,
// it has the contents of the backend block as top-level attributes,
// without the need to wrap it in another terraform or backend block.
// More details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file.
// +optional
BackendFile *string `json:"backendFile,omitempty"`

// PluginCache enables terraform provider plugin caching mechanism
// https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache
// +optional
Expand Down
5 changes: 5 additions & 0 deletions apis/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions examples/providerconfig-backend-file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apiVersion: tf.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
# Note that unlike most provider configs this one supports an array of
# credentials. This is because each Terraform workspace uses a single
# Crossplane provider config, but could use multiple Terraform providers each
# with their own credentials.
credentials:
- filename: gcp-credentials.json
source: Secret
secretRef:
namespace: upbound-system
name: gcp-creds
key: credentials
# This optional configuration block can be used to inject HCL into any
# workspace that uses this provider config, for example to setup Terraform
# providers.
configuration: |
provider "google" {
credentials = "gcp-credentials.json"
project = "official-provider-testing"
}
// Defining partial backend configuration as documented at
// https://developer.hashicorp.com/terraform/language/settings/backends/configuration#partial-configuration
terraform {
backend "kubernetes" {}
}
# Using backend configuration file as documented at
# https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file
backendFile: |
secret_suffix = "providerconfig-default"
namespace = "upbound-system"
in_cluster_config = true
17 changes: 14 additions & 3 deletions internal/controller/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
errWriteGitCreds = "cannot write .git-credentials to /tmp dir"
errWriteConfig = "cannot write Terraform configuration " + tfConfig
errWriteMain = "cannot write Terraform configuration " + tfMain
errWriteBackend = "cannot write Terraform configuration " + tfBackendFile
errInit = "cannot initialize Terraform configuration"
errWorkspace = "cannot select Terraform workspace"
errResources = "cannot list Terraform resources"
Expand All @@ -81,9 +82,10 @@ const (

const (
// TODO(negz): Make the Terraform binary path and work dir configurable.
tfPath = "terraform"
tfMain = "main.tf"
tfConfig = "crossplane-provider-config.tf"
tfPath = "terraform"
tfMain = "main.tf"
tfConfig = "crossplane-provider-config.tf"
tfBackendFile = "crossplane.remote.tfbackend"
)

func envVarFallback(envvar string, fallback string) string {
Expand Down Expand Up @@ -268,6 +270,12 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
}
}

if pc.Spec.BackendFile != nil {
if err := c.fs.WriteFile(filepath.Join(dir, tfBackendFile), []byte(*pc.Spec.BackendFile), 0600); err != nil {
return nil, errors.Wrap(err, errWriteBackend)
}
}

// NOTE(ytsarev): user tf provider cache mechanism to speed up
// reconciliation, see https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache
if pc.Spec.PluginCache == nil {
Expand All @@ -288,6 +296,9 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
}

o := make([]terraform.InitOption, 0, len(cr.Spec.ForProvider.InitArgs))
if pc.Spec.BackendFile != nil {
o = append(o, terraform.WithInitArgs([]string{"-backend-config=" + filepath.Join(dir, tfBackendFile)}))
}
o = append(o, terraform.WithInitArgs(cr.Spec.ForProvider.InitArgs))
if err := tf.Init(ctx, *pc.Spec.PluginCache, o...); err != nil {
return nil, errors.Wrap(err, errInit)
Expand Down
47 changes: 47 additions & 0 deletions internal/controller/workspace/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,53 @@ func TestConnect(t *testing.T) {
},
want: nil,
},
"SuccessUsingBackendFile": {
reason: "We should not return an error when we successfully 'connect' to Terraform using a Backend file",
fields: fields{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
if pc, ok := obj.(*v1beta1.ProviderConfig); ok {
cfg := "I'm HCL!"
backendFile := "I'm a backend!"
pc.Spec.Configuration = &cfg
pc.Spec.BackendFile = &backendFile
}
return nil
}),
},
usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }),
fs: afero.Afero{Fs: afero.NewMemMapFs()},
terraform: func(_ string) tfclient {
return &MockTf{
MockInit: func(ctx context.Context, cache bool, o ...terraform.InitOption) error {
args := terraform.InitArgsToString(o)
if len(args) != 2 {
return errors.New("two init args are expected")
} else if args[0] != "-backend-config=/tf/no-you-id/crossplane.remote.tfbackend" {
return errors.Errorf("backend config arg has invalid value: %s", args[0])
}
return nil
},
MockGenerateChecksum: func(ctx context.Context) (string, error) { return tfChecksum, nil },
MockWorkspace: func(_ context.Context, _ string) error { return nil },
}
},
},
args: args{
mg: &v1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{UID: uid},
Spec: v1beta1.WorkspaceSpec{
ForProvider: v1beta1.WorkspaceParameters{
InitArgs: []string{"-upgrade=true"},
},
ResourceSpec: xpv1.ResourceSpec{
ProviderConfigReference: &xpv1.Reference{},
},
},
},
},
want: nil,
},
}

for name, tc := range cases {
Expand Down
16 changes: 10 additions & 6 deletions internal/terraform/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ func WithInitArgs(v []string) InitOption {
}
}

// InitArgsToString converts Terraform init arguments to a list of strings.
func InitArgsToString(o []InitOption) []string {
io := &initOptions{}
for _, fn := range o {
fn(io)
}
return io.args
}

// RWMutex protects the terraform shared cache from corruption. If an init is
// performed, it requires a write lock. Only one write lock at a time. If another
// action is performed, a read lock is acquired. More than one read locks can be acquired.
Expand All @@ -157,12 +166,7 @@ var rwmutex = &sync.RWMutex{}

// Init initializes a Terraform configuration.
func (h Harness) Init(ctx context.Context, cache bool, o ...InitOption) error {
io := &initOptions{}
for _, fn := range o {
fn(io)
}

args := append([]string{"init", "-input=false", "-no-color"}, io.args...)
args := append([]string{"init", "-input=false", "-no-color"}, InitArgsToString(o)...)
cmd := exec.CommandContext(ctx, h.Path, args...) //nolint:gosec
cmd.Dir = h.Dir
for _, e := range os.Environ() {
Expand Down
6 changes: 6 additions & 0 deletions package/crds/tf.upbound.io_providerconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ spec:
spec:
description: A ProviderConfigSpec defines the desired state of a ProviderConfig.
properties:
backendFile:
description: Terraform backend file configuration content, it has
the contents of the backend block as top-level attributes, without
the need to wrap it in another terraform or backend block. More
details at https://developer.hashicorp.com/terraform/language/settings/backends/configuration#file.
type: string
configuration:
description: Configuration that should be injected into all workspaces
that use this provider config, expressed as inline HCL. This can
Expand Down

0 comments on commit 2a21288

Please sign in to comment.