Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Adding support for workspace secret per namespace #133

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/v1alpha1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ type WorkspaceSpec struct {
RunTriggers []*RunTrigger `json:"runTriggers,omitempty"`
// File path within operator pod to load workspace secrets
SecretsMountPath string `json:"secretsMountPath"`
// Name of the secret in the same namespace as the Workspace CR that contains workspace secrets. Those secrets are merged with secrets in the secretsMountPath.
// +optional
SecretName string `json:"secretName"`
// SSH Key ID. This key must already exist in the TF Cloud organization. This can either be the user assigned name of the SSH Key, or the system assigned ID.
// +optional
SSHKeyID string `json:"sshKeyID,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/app.terraform.io_workspaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ spec:
- sourceableName
type: object
type: array
secretName:
description: Name of the secret in the same namespace as the Workspace
CR that contains workspace secrets. Those secrets are merged with
secrets in the secretsMountPath.
type: string
secretsMountPath:
description: File path within operator pod to load workspace secrets
type: string
Expand Down
8 changes: 8 additions & 0 deletions workspacehelper/k8s_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ import (

// GetSecretData retrieves the data from a secret in a given namespace
func (r *WorkspaceHelper) GetSecretData(namespace string, name string) (map[string][]byte, error) {
// If no secretName defined, return empty map
if name == "" {
return make(map[string][]byte), nil
}

r.reqLogger.Info("Getting Secret", "Namespace", namespace, "Name", name)

secret := &corev1.Secret{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, secret)

if err != nil {
r.reqLogger.Error(err, "Failed to get Secret", "Namespace", namespace, "Name", name)

return nil, err
}

return secret.Data, nil
}

Expand Down
47 changes: 25 additions & 22 deletions workspacehelper/tfc_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ func (t *TerraformCloudClient) deleteVariablesFromTFC(specTFCVariables []*tfc.Va
return nil
}

func (t *TerraformCloudClient) createVariablesOnTFC(workspace *tfc.Workspace, specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable) (bool, error) {
func (t *TerraformCloudClient) createVariablesOnTFC(workspace *tfc.Workspace, specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable, secretData map[string][]byte) (bool, error) {
updated := false
for _, v := range specTFCVariables {
index := find(workspaceVariables, v.Key)
if index < 0 {
err := t.CreateTerraformVariable(workspace, v)
err := t.CreateTerraformVariable(workspace, v, secretData)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -104,15 +104,15 @@ func getNonSensitiveVariablesToUpdate(specTFCVariables []*tfc.Variable, workspac
return variablesToUpdate
}

func getSensitiveVariablesToUpdate(specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable, secretsMountPath string) ([]*tfc.Variable, error) {
func getSensitiveVariablesToUpdate(specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable, secretsMountPath string, secretData map[string][]byte) ([]*tfc.Variable, error) {
variablesToUpdate := []*tfc.Variable{}
for _, v := range specTFCVariables {
index := find(workspaceVariables, v.Key)
if index < 0 {
continue
}
if workspaceVariables[index].Sensitive {
if err := checkAndRetrieveIfSensitive(v, secretsMountPath); err != nil {
if err := checkAndRetrieveIfSensitive(v, secretsMountPath, secretData); err != nil {
return nil, err
}
v.ID = workspaceVariables[index].ID
Expand All @@ -124,15 +124,15 @@ func getSensitiveVariablesToUpdate(specTFCVariables []*tfc.Variable, workspaceVa
return variablesToUpdate, nil
}

func generateUpdateVariableList(specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable, secretsMountPath string) ([]*tfc.Variable, error) {
func generateUpdateVariableList(specTFCVariables []*tfc.Variable, workspaceVariables []*tfc.Variable, secretsMountPath string, secretData map[string][]byte) ([]*tfc.Variable, error) {
updateList := []*tfc.Variable{}

nonSensitiveVariablesToUpdate := getNonSensitiveVariablesToUpdate(specTFCVariables, workspaceVariables)
if len(nonSensitiveVariablesToUpdate) == 0 {
return updateList, nil
}

sensitiveVariablesToUpdate, err := getSensitiveVariablesToUpdate(specTFCVariables, workspaceVariables, secretsMountPath)
sensitiveVariablesToUpdate, err := getSensitiveVariablesToUpdate(specTFCVariables, workspaceVariables, secretsMountPath, secretData)
if err != nil {
return nonSensitiveVariablesToUpdate, err
}
Expand All @@ -143,7 +143,7 @@ func generateUpdateVariableList(specTFCVariables []*tfc.Variable, workspaceVaria
}

// CheckVariables creates, updates, or deletes variables as needed
func (t *TerraformCloudClient) CheckVariables(workspace string, specTFCVariables []*tfc.Variable) (bool, error) {
func (t *TerraformCloudClient) CheckVariables(workspace string, specTFCVariables []*tfc.Variable, secretData map[string][]byte) (bool, error) {
tfcWorkspace, err := t.Client.Workspaces.Read(context.TODO(), t.Organization, workspace)
if err != nil {
return false, err
Expand All @@ -156,12 +156,12 @@ func (t *TerraformCloudClient) CheckVariables(workspace string, specTFCVariables
return false, err
}

createdVariables, err := t.createVariablesOnTFC(tfcWorkspace, specTFCVariables, workspaceVariables)
createdVariables, err := t.createVariablesOnTFC(tfcWorkspace, specTFCVariables, workspaceVariables, secretData)
if err != nil {
return false, err
}

variablesToUpdate, err := generateUpdateVariableList(specTFCVariables, workspaceVariables, t.SecretsMountPath)
variablesToUpdate, err := generateUpdateVariableList(specTFCVariables, workspaceVariables, t.SecretsMountPath, secretData)
if err != nil || len(variablesToUpdate) == 0 {
return false, err
}
Expand Down Expand Up @@ -222,25 +222,28 @@ func (t *TerraformCloudClient) UpdateTerraformVariables(variables []*tfc.Variabl
return nil
}

func checkAndRetrieveIfSensitive(variable *tfc.Variable, secretsMountPath string) error {
// Try to read variables with empty value from file. If the value isn't empty,
// it was already read fromValue.SecretKeyRef.
if variable.Sensitive && variable.Value == "" {
filePath := fmt.Sprintf("%s/%s", secretsMountPath, variable.Key)

data, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("could not get secret, %s", err)
func checkAndRetrieveIfSensitive(variable *tfc.Variable, secretsMountPath string, secretData map[string][]byte) error {
if variable.Sensitive {
// First check if the key is in the namespaced Secret
if val, ok := secretData[variable.Key]; ok {
variable.Value = string(val)
} else {
// Try to find key in the mounted Secret
filePath := fmt.Sprintf("%s/%s", secretsMountPath, variable.Key)
data, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("could not get secret, %s", err)
}
secret := string(data)
variable.Value = secret
}
secret := string(data)
variable.Value = secret
}
return nil
}

// CreateTerraformVariable creates a Terraform variable based on key and value
func (t *TerraformCloudClient) CreateTerraformVariable(workspace *tfc.Workspace, variable *tfc.Variable) error {
if err := checkAndRetrieveIfSensitive(variable, t.SecretsMountPath); err != nil {
func (t *TerraformCloudClient) CreateTerraformVariable(workspace *tfc.Workspace, variable *tfc.Variable, secretData map[string][]byte) error {
if err := checkAndRetrieveIfSensitive(variable, t.SecretsMountPath, secretData); err != nil {
return err
}
options := tfc.VariableCreateOptions{
Expand Down
9 changes: 6 additions & 3 deletions workspacehelper/tfc_variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ func TestShouldGetSensitiveVariablesForUpdate(t *testing.T) {
Sensitive: true,
},
}
update, err := getSensitiveVariablesToUpdate(specVariables, workspaceVariables, secretsMount)
secretData := make(map[string][]byte)
update, err := getSensitiveVariablesToUpdate(specVariables, workspaceVariables, secretsMount, secretData)
assert.NoError(t, err)
assert.Len(t, update, 1)
assert.Equal(t, update[0].Key, specVariables[0].Key)
Expand Down Expand Up @@ -132,7 +133,8 @@ func TestShouldUpdateVariables(t *testing.T) {
HCL: false,
},
}
update, err := generateUpdateVariableList(specVariables, workspaceVariables, secretsMount)
secretData := make(map[string][]byte)
update, err := generateUpdateVariableList(specVariables, workspaceVariables, secretsMount, secretData)
assert.NoError(t, err)
assert.Len(t, update, 2)
assert.False(t, update[0].Sensitive)
Expand Down Expand Up @@ -171,7 +173,8 @@ func TestShouldNotUpdateVariables(t *testing.T) {
HCL: true,
},
}
update, err := generateUpdateVariableList(specVariables, workspaceVariables, secretsMount)
secretData := make(map[string][]byte)
update, err := generateUpdateVariableList(specVariables, workspaceVariables, secretsMount, secretData)
assert.NoError(t, err)
assert.Len(t, update, 0)
}
8 changes: 7 additions & 1 deletion workspacehelper/workspace_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,14 @@ func (r *WorkspaceHelper) updateVariables(instance *appv1alpha1.Workspace) (bool
}
}

secretData, err := r.GetSecretData(instance.Namespace, instance.Spec.SecretName)
if err != nil {
r.reqLogger.Error(err, "Could not get namespaced Secret data")
return false, err
}

specTFCVariables := MapToTFCVariable(instance.Spec.Variables)
updatedVariables, err := r.tfclient.CheckVariables(workspace, specTFCVariables)
updatedVariables, err := r.tfclient.CheckVariables(workspace, specTFCVariables, secretData)
if err != nil {
r.reqLogger.Error(err, "Could not update variables")
return false, err
Expand Down