Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terraform CLI logs support #248

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Prev Previous commit
Bug fix, Added additional test and documentation
Signed-off-by: Katrina Ronquillo <[email protected]>
Suresh Ramasamy authored and Katrina Ronquillo committed Mar 11, 2024
commit 7e1ba76641db2f367d03e0a08a19975e4aa26986
22 changes: 22 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
@@ -355,3 +355,25 @@ spec:
At Vault side configuration is also needed to allow the write operation, see
[example](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/)
here for inspiration.

## Enable Terraform CLI logs

Terraform CLI logs can be written to a log file to assist with debugging and to view detailed information about Terraform operations.
To enable it, the `ProviderConfig` spec has an **optional** `LogConfig` field.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To enable it, the `ProviderConfig` spec has an **optional** `LogConfig` field.
To enable it, the `ProviderConfig` spec has an **optional** `logConfig` field.

nit: LogConfig -> logConfig


```yaml
apiVersion: tf.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
logConfig:
enableLogging: True
backupLogFilesCount: 1
...
```

- `enableLogging`: Specifies whether logging is enabled (`true`) or disabled (`false`). When enabled, Terraform CLI command logs will be written to a file. Default is `false`
- `backupLogFilesCount`: Specifies the number of archived log files to retain. When a new log file is created due to a change detected by `terraform diff`, the previous log file is archived and renamed with a timestamp. This parameter controls how many archived log files are kept before older ones are deleted. Default is `0`

By default, Terraform CLI stores log files in the workspace directory. The default log file name is `terraform.log`. When a backup is taken (e.g., due to a new change detected by `terraform diff`), the current log file is renamed to `terraform.log.<timestamp>`, where `<timestamp>` is a placeholder for the actual timestamp of the backup.
16 changes: 16 additions & 0 deletions examples/providerconfig-with-logConfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: tf.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
configuration: |
terraform {
backend "kubernetes" {
secret_suffix = "providerconfig-aws-eu-west-1"
namespace = "upbound-system"
in_cluster_config = true
}
}
logConfig:
backupLogFilesCount: 1
enableLogging: True
2 changes: 1 addition & 1 deletion internal/controller/workspace/workspace.go
Original file line number Diff line number Diff line change
@@ -285,7 +285,7 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
*pc.Spec.PluginCache = true
}

// diable logging by default
// disable logging by default
if pc.Spec.LogConfig == nil {
pc.Spec.LogConfig = &v1beta1.LogConfig{
EnableLogging: new(bool),
28 changes: 14 additions & 14 deletions internal/terraform/terraform.go
Original file line number Diff line number Diff line change
@@ -526,7 +526,6 @@ func (h Harness) Diff(ctx context.Context, o ...Option) (bool, error) {
return false, errors.Wrap(err, errWriteVarFile)
}
}

args := append([]string{"plan", "-no-color", "-input=false", "-detailed-exitcode", "-lock=false"}, ao.args...)
cmd := exec.Command(h.Path, args...) //nolint:gosec
cmd.Dir = h.Dir
@@ -560,12 +559,15 @@ func writeTerraformCLILogs(out []byte, logConfig v1beta1.LogConfig, dir string,
if *logConfig.EnableLogging {
fileName := "terraform.log"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we move it to const ?

if logRollOver {
// if logRollOver is true, we need to create a new file with a new name
// by appending a timestamp to the file name
archiveFileName := fileName + "." + time.Now().Format("20060102-150405")
err := os.Rename(filepath.Join(dir, fileName), filepath.Join(dir, archiveFileName))
if err != nil {
return errors.Wrap(err, errWriteLogFile)
// Backup the already existing terraform.log file
if _, err := os.Stat(filepath.Join(dir, fileName)); err == nil {
// if logRollOver is true, we need to create a new file with a new name
// by appending a timestamp to the file name
archiveFileName := fileName + "." + time.Now().Format("20060102-150405")
err := os.Rename(filepath.Join(dir, fileName), filepath.Join(dir, archiveFileName))
if err != nil {
return errors.Wrap(err, errWriteLogFile)
}
}
}
filePath := filepath.Join(dir, fileName)
@@ -657,16 +659,15 @@ func (h Harness) Apply(ctx context.Context, o ...Option) error {
}
out, err := runCommand(ctx, cmd)

// In case of terraform destroy
// In case of terraform apply
// 0 - Succeeded
// 1 - Errored

// Non Zero output - Errored
switch cmd.ProcessState.ExitCode() {
case 0:
if err := writeTerraformCLILogs(out, h.LogConfig, h.Dir, false); err != nil {
return errors.Wrap(err, "error writing logs")
}
case 1:
default:
ee := &exec.ExitError{}
errors.As(err, &ee)
if err := writeTerraformCLILogs(ee.Stderr, h.LogConfig, h.Dir, false); err != nil {
@@ -700,16 +701,15 @@ func (h Harness) Destroy(ctx context.Context, o ...Option) error {
}

out, err := runCommand(ctx, cmd)

// In case of terraform destroy
// 0 - Succeeded
// Non Zero output(1,2) - Errored

// Non Zero output - Errored
switch cmd.ProcessState.ExitCode() {
case 0:
if err := writeTerraformCLILogs(out, h.LogConfig, h.Dir, false); err != nil {
return errors.Wrap(err, errWriteVarFile)
}
break
default:
ee := &exec.ExitError{}
errors.As(err, &ee)
61 changes: 41 additions & 20 deletions internal/terraform/terraform_test.go
Original file line number Diff line number Diff line change
@@ -17,11 +17,12 @@ limitations under the License.
package terraform

import (
"github.com/upbound/provider-terraform/apis/v1beta1"
"os"
"os/exec"
"testing"

"github.com/upbound/provider-terraform/apis/v1beta1"

"github.com/MakeNowJust/heredoc"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
@@ -291,69 +292,89 @@ func TestFormatTerraformErrorOutput(t *testing.T) {
}
}

func TestWriteTerraformCLILogsWithLoggingDisabled(t *testing.T) {
// Create a temporary directory for testing
directory := "tmp"
os.Mkdir(directory, 0755)
defer os.RemoveAll(directory)
loggingEnabled := false
backUpFilesCount := 1
logConfig := v1beta1.LogConfig{
EnableLogging: &loggingEnabled,
BackupLogFilesCount: &backUpFilesCount,
}
err := writeTerraformCLILogs([]byte("This is a test log"), logConfig, directory, false)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
files, err := os.ReadDir(directory)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if len(files) != 0 {
t.Errorf("Unexpected number of files got: %d expected: %d", len(files), 0)
}
}

func TestWriteTerraformCLILogs(t *testing.T) {
//create a directory
// Create a temporary directory for testing
os.Mkdir("tmp", 0755)
defer os.RemoveAll("tmp")
newTrue := true
filesCount := 1
loggingEnabled := true
backUpFilesCount := 1
input := []struct {
output []byte
logConfig v1beta1.LogConfig
dir string
logRollOver bool
isPositive bool
}{
{
output: []byte("This is a test log"),
logConfig: v1beta1.LogConfig{
EnableLogging: &newTrue,
BackupLogFilesCount: &filesCount,
EnableLogging: &loggingEnabled,
BackupLogFilesCount: &backUpFilesCount,
},
dir: "tmp",
logRollOver: false,
isPositive: true,
},
{
output: []byte("This is a test log"),
logConfig: v1beta1.LogConfig{
EnableLogging: &newTrue,
BackupLogFilesCount: &filesCount,
EnableLogging: &loggingEnabled,
BackupLogFilesCount: &backUpFilesCount,
},
dir: "tmp",
logRollOver: true,
isPositive: true,
},
{
output: []byte("This is a test log"),
logConfig: v1beta1.LogConfig{
EnableLogging: &newTrue,
BackupLogFilesCount: &filesCount,
EnableLogging: &loggingEnabled,
BackupLogFilesCount: &backUpFilesCount,
},
dir: "tmp",
logRollOver: true,
isPositive: true,
},
}

for _, in := range input {
err := writeTerraformCLILogs(in.output, in.logConfig, in.dir, in.logRollOver)
if err != nil && in.isPositive {
if err != nil {
t.Errorf("Unexpected error: %s", err)
}

if in.isPositive && !in.logRollOver {
if !in.logRollOver {
if _, err := os.Stat(in.dir); os.IsNotExist(err) {
t.Errorf("Directory %s does not exist", in.dir)
}
// Check if the file exists
if _, err := os.Stat(in.dir + "/terraform.log"); os.IsNotExist(err) {
t.Errorf("File %s does not exist", in.dir+"/terraform.log")
}
} else if in.isPositive && in.logRollOver {
//this block is visited for input 2 & 3
//check if file count is 2 , even when the cli logs are created since the filesCount is 1
//it will only preserve 1 archived log and terraform.log and delete the oldest log file
} else if in.logRollOver {
// this block is visited for input 2 & 3
// check if file count is 2, since the backUpFilesCount is 1, it will delete the oldest archived log
// and preserve 1 archived log & terraform.log
files, err := os.ReadDir(in.dir)
if err != nil {
t.Errorf("Unexpected error: %s", err)