diff --git a/apis/v1beta1/workspace_types.go b/apis/v1beta1/workspace_types.go index d809e8e..be0e065 100644 --- a/apis/v1beta1/workspace_types.go +++ b/apis/v1beta1/workspace_types.go @@ -39,14 +39,14 @@ const ( VarFileSourceSecretKey VarFileSource = "SecretKey" ) -// A VarFileFormat specifies the format of a Terraform vars file. +// A FileFormat specifies the format of a Terraform file. // +kubebuilder:validation:Enum=HCL;JSON -type VarFileFormat string +type FileFormat string // Vars file formats. var ( - VarFileFormatHCL VarFileFormat = "HCL" - VarFileFormatJSON VarFileFormat = "JSON" + FileFormatHCL FileFormat = "HCL" + FileFormatJSON FileFormat = "JSON" ) // A VarFile is a file containing many Terraform variables. @@ -57,7 +57,7 @@ type VarFile struct { // Format of this vars file. // +kubebuilder:default=HCL // +optional - Format *VarFileFormat `json:"format,omitempty"` + Format *FileFormat `json:"format,omitempty"` // A ConfigMap key containing the vars file. // +optional @@ -108,9 +108,13 @@ type WorkspaceParameters struct { // file. When the workspace's source is 'Remote' (the default) this can be // any address supported by terraform init -from-module, for example a git // repository or an S3 bucket. When the workspace's source is 'Inline' the - // content of a simple main.tf file may be written inline. + // content of a simple main.tf or main.tf.json file may be written inline. Module string `json:"module"` + // Specifies the format of the inline Terraform content + // if Source is 'Inline' + InlineFormat FileFormat `json:"inlineFormat,omitempty"` + // Source of the root module of this workspace. Source ModuleSource `json:"source"` diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go index f7a8bd9..f9f3e56 100644 --- a/apis/v1beta1/zz_generated.deepcopy.go +++ b/apis/v1beta1/zz_generated.deepcopy.go @@ -362,7 +362,7 @@ func (in *VarFile) DeepCopyInto(out *VarFile) { *out = *in if in.Format != nil { in, out := &in.Format, &out.Format - *out = new(VarFileFormat) + *out = new(FileFormat) **out = **in } if in.ConfigMapKeyReference != nil { diff --git a/internal/controller/workspace/workspace.go b/internal/controller/workspace/workspace.go index 258df5e..e1eb1b3 100644 --- a/internal/controller/workspace/workspace.go +++ b/internal/controller/workspace/workspace.go @@ -64,7 +64,7 @@ const ( errWriteCreds = "cannot write Terraform credentials" errWriteGitCreds = "cannot write .git-credentials to /tmp dir" errWriteConfig = "cannot write Terraform configuration " + tfConfig - errWriteMain = "cannot write Terraform configuration " + tfMain + errWriteMain = "cannot write Terraform configuration " errWriteBackend = "cannot write Terraform configuration " + tfBackendFile errInit = "cannot initialize Terraform configuration" errWorkspace = "cannot select Terraform workspace" @@ -87,6 +87,7 @@ const ( // TODO(negz): Make the Terraform binary path and work dir configurable. tfPath = "terraform" tfMain = "main.tf" + tfMainJSON = "main.tf.json" tfConfig = "crossplane-provider-config.tf" tfBackendFile = "crossplane.remote.tfbackend" ) @@ -249,8 +250,12 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E } case v1beta1.ModuleSourceInline: - if err := c.fs.WriteFile(filepath.Join(dir, tfMain), []byte(cr.Spec.ForProvider.Module), 0600); err != nil { - return nil, errors.Wrap(err, errWriteMain) + fn := tfMain + if cr.Spec.ForProvider.InlineFormat == v1beta1.FileFormatJSON { + fn = tfMainJSON + } + if err := c.fs.WriteFile(filepath.Join(dir, fn), []byte(cr.Spec.ForProvider.Module), 0600); err != nil { + return nil, errors.Wrap(err, errWriteMain+fn) } } @@ -481,7 +486,7 @@ func (c *external) options(ctx context.Context, p v1beta1.WorkspaceParameters) ( for _, vf := range p.VarFiles { fmt := terraform.HCL - if vf.Format != nil && *vf.Format == v1beta1.VarFileFormatJSON { + if vf.Format != nil && *vf.Format == v1beta1.FileFormatJSON { fmt = terraform.JSON } diff --git a/internal/controller/workspace/workspace_test.go b/internal/controller/workspace/workspace_test.go index 75776ef..f737456 100644 --- a/internal/controller/workspace/workspace_test.go +++ b/internal/controller/workspace/workspace_test.go @@ -519,7 +519,43 @@ func TestConnect(t *testing.T) { }, }, }, - want: errors.Wrap(errBoom, errWriteMain), + want: errors.Wrap(errBoom, errWriteMain+tfMain), + }, + "WriteMainJsonError": { + reason: "We should return any error encountered while writing our main.tf file", + fields: fields{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + usage: resource.TrackerFn(func(_ context.Context, _ resource.Managed) error { return nil }), + fs: afero.Afero{ + Fs: &ErrFs{ + Fs: afero.NewMemMapFs(), + errs: map[string]error{filepath.Join(tfDir, string(uid), tfMainJSON): errBoom}, + }, + }, + terraform: func(_ string, _ bool, _ ...string) tfclient { + return &MockTf{ + MockInit: func(ctx context.Context, o ...terraform.InitOption) error { return nil }, + } + }, + }, + args: args{ + mg: &v1beta1.Workspace{ + ObjectMeta: metav1.ObjectMeta{UID: uid}, + Spec: v1beta1.WorkspaceSpec{ + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{}, + }, + ForProvider: v1beta1.WorkspaceParameters{ + Module: "I'm JSON!", + Source: v1beta1.ModuleSourceInline, + InlineFormat: v1beta1.FileFormatJSON, + }, + }, + }, + }, + want: errors.Wrap(errBoom, errWriteMain+tfMainJSON), }, "TerraformInitError": { reason: "We should return any error encountered while initializing Terraform", @@ -1298,7 +1334,7 @@ func TestCreate(t *testing.T) { { Source: v1beta1.VarFileSourceSecretKey, SecretKeyReference: &v1beta1.KeyReference{}, - Format: &v1beta1.VarFileFormatJSON, + Format: &v1beta1.FileFormatJSON, }, }, }, @@ -1483,7 +1519,7 @@ func TestDelete(t *testing.T) { { Source: v1beta1.VarFileSourceSecretKey, SecretKeyReference: &v1beta1.KeyReference{}, - Format: &v1beta1.VarFileFormatJSON, + Format: &v1beta1.FileFormatJSON, }, }, }, diff --git a/package/crds/tf.upbound.io_workspaces.yaml b/package/crds/tf.upbound.io_workspaces.yaml index 41e352f..fd2d204 100644 --- a/package/crds/tf.upbound.io_workspaces.yaml +++ b/package/crds/tf.upbound.io_workspaces.yaml @@ -147,13 +147,21 @@ spec: items: type: string type: array + inlineFormat: + description: |- + Specifies the format of the inline Terraform content + if Source is 'Inline' + enum: + - HCL + - JSON + type: string module: description: |- The root module of this workspace; i.e. the module containing its main.tf file. When the workspace's source is 'Remote' (the default) this can be any address supported by terraform init -from-module, for example a git repository or an S3 bucket. When the workspace's source is 'Inline' the - content of a simple main.tf file may be written inline. + content of a simple main.tf or main.tf.json file may be written inline. type: string planArgs: description: Arguments to be included in the terraform plan CLI