Skip to content

Commit

Permalink
Add Support for TFE Endpoint (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
joatmon08 authored May 20, 2020
1 parent 1624adb commit d63210e
Show file tree
Hide file tree
Showing 143 changed files with 31,756 additions and 1,950 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## Unreleased
## 0.1.5-alpha (May 20, 2020)

* Upgrade go-tfe to v0.7.0 and dependencies
* Upgrade go-tfe to v0.7.0 and dependencies
* Fix issue that prevents SSHKey from being set on new workspaces (#44)
* Add Terraform Enterprise endpoint with `TF_URL` environment variable (#13)

## 0.1.4-alpha (May 14, 2020)

Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ Or to run a specific test in the suite:
go test ./... -run SomeTestFunction_name
```

Example of running specific acceptance tests against Terraform Cloud and Terraform Enterprise:
```shell
export TF_URL="https://my-tfe-hostname"
export TF_ACC=1
export TF_CLI_CONFIG_FILE=$HOME/.terraformrc
export TF_ORG="my-tfe-org"
go test -v ./... -run TestOrganizationTerraformEnterprise

export TF_ORG="my-tfc-org"
go test -v ./... -run TestOrganizationTerraformCloud
```

To create a docker image with your local changes:

```shell
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ dev-tree:
@$(SHELL) $(CURDIR)/build-support/scripts/dev.sh $(DEV_PUSH_ARG)

test:
go test ./...
go test ./... -v

cov:
go test ./... -coverprofile=coverage.out
Expand Down
35 changes: 32 additions & 3 deletions operator/pkg/controller/workspace/tfc_org.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package workspace
import (
"context"
"fmt"
"net/url"
"os"

tfc "github.com/hashicorp/go-tfe"
Expand All @@ -23,21 +24,49 @@ type TerraformCloudClient struct {
SecretsMountPath string
}

func createTerraformConfig(address string, tfConfig *cliconfig.Config) (*tfc.Config, error) {
if len(address) == 0 {
address = tfc.DefaultAddress
}
u, err := url.Parse(address)
if u.Scheme == "" {
return nil, fmt.Errorf("Invalid Terraform Cloud or Enterprise URL. Please specify a scheme (http:// or https://)")
}
if err != nil {
return nil, fmt.Errorf("Not a valid Terraform Cloud or Enterprise URL, %v", err)
}
host := u.Host
if host == "" {
return nil, fmt.Errorf("Terraform Cloud or Enterprise URL hostname is ''. Invalid hostname")
}

if len(tfConfig.Credentials[host]) == 0 {
return nil, fmt.Errorf("Define token for %s", host)
}

return &tfc.Config{
Address: address,
Token: fmt.Sprintf("%v", tfConfig.Credentials[host]["token"]),
}, nil
}

// GetClient creates the configuration for Terraform Cloud
func (t *TerraformCloudClient) GetClient() error {
func (t *TerraformCloudClient) GetClient(tfEndpoint string) error {
tfConfig, diag := cliconfig.LoadConfig()
if diag.Err() != nil {
return diag.Err()
}

config := &tfc.Config{
Token: fmt.Sprintf("%v", tfConfig.Credentials["app.terraform.io"]["token"]),
config, err := createTerraformConfig(tfEndpoint, tfConfig)
if err != nil {
return err
}

client, err := tfc.NewClient(config)
if err != nil {
return diag.Err()
}

t.Client = client
return nil
}
Expand Down
49 changes: 49 additions & 0 deletions operator/pkg/controller/workspace/tfc_org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,55 @@ import (
"github.com/stretchr/testify/assert"
)

func setupClient(t *testing.T, tfAddress string) (*TerraformCloudClient, error) {
testOrganization := os.Getenv("TF_ORG")
if os.Getenv("TF_ACC") == "" || os.Getenv("TF_CLI_CONFIG_FILE") == "" {
t.Skipf("this test requires Terraform Cloud and Enterprise access and credentials;" +
"set TF_ACC=1 and TF_CLI_CONFIG_FILE to run it")
}
tfClient := &TerraformCloudClient{
Organization: testOrganization,
}
err := tfClient.GetClient(tfAddress)
return tfClient, err
}

func TestFailsForNonMatchingTerraformEnterpriseHostname(t *testing.T) {
_, err := setupClient(t, "notahostname")
assert.Error(t, err)
}

func TestOrganizationTerraformCloud(t *testing.T) {
tfClient, err := setupClient(t, "")
assert.NoError(t, err)

err = tfClient.CheckOrganization()
assert.NoError(t, err)
}

func TestOrganizationTerraformEnterprise(t *testing.T) {
if os.Getenv("TF_URL") == "" {
t.Skipf("this test requires TF_URL for Terraform Enterprise")
}
tfClient, err := setupClient(t, os.Getenv("TF_URL"))
assert.NoError(t, err)

err = tfClient.CheckOrganization()
assert.NoError(t, err)
}

func TestOrganizationTerraformEnterpriseNotFound(t *testing.T) {
if os.Getenv("TF_URL") == "" {
t.Skipf("this test requires TF_URL for Terraform Enterprise")
}
tfClient, err := setupClient(t, os.Getenv("TF_URL"))
assert.NoError(t, err)

tfClient.Organization = "doesnotexist"
err = tfClient.CheckOrganization()
assert.Error(t, err)
}

func TestShouldGetTerraformVersionFromEnvVariable(t *testing.T) {
expected := "0.11.0"
os.Setenv("TF_VERSION", expected)
Expand Down
13 changes: 7 additions & 6 deletions operator/pkg/controller/workspace/workspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package workspace
import (
"context"
"fmt"
"os"
"reflect"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -33,9 +34,10 @@ func Add(mgr manager.Manager) error {
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
tfclient := &TerraformCloudClient{}
err := tfclient.GetClient()
err := tfclient.GetClient(os.Getenv("TF_URL"))
if err != nil {
log.Error(err, "could not create Terraform Cloud client")
log.Error(err, "could not create Terraform Cloud or Enterprise client")
os.Exit(1)
}
return &ReconcileWorkspace{
client: mgr.GetClient(),
Expand Down Expand Up @@ -85,13 +87,12 @@ type ReconcileWorkspace struct {

// Reconcile reads that state of the cluster for a Workspace object and makes changes based on the state read
// and what is in the Workspace.Spec
// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates
// a Pod as an example
// Note:
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Result, error) {
r.reqLogger.WithValues("Name", request.Name, "Namespace", request.Namespace)

// Fetch the Workspace instance
instance := &appv1alpha1.Workspace{}
err := r.client.Get(context.TODO(), request.NamespacedName, instance)
Expand All @@ -111,13 +112,13 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res

if err := r.tfclient.CheckOrganization(); err != nil {
r.reqLogger.Error(err, "Could not find organization")
return reconcile.Result{}, nil
return reconcile.Result{}, err
}

r.tfclient.SecretsMountPath = instance.Spec.SecretsMountPath
if err := r.tfclient.CheckSecretsMountPath(); err != nil {
r.reqLogger.Error(err, "Could not find secrets mount path")
return reconcile.Result{}, nil
return reconcile.Result{}, err
}

workspaceID, err := r.tfclient.CheckWorkspace(workspace, instance)
Expand Down
95 changes: 95 additions & 0 deletions vendor/github.com/apparentlymart/go-textseg/v12/LICENSE

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

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

Loading

0 comments on commit d63210e

Please sign in to comment.