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

Enhance deploy to output JSON instead of plain-english #3618

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 8 additions & 7 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ import (
)

func addDeployFlags(c *cobra.Command) *cobra.Command {
return addGroupSelectionFlags(
addAutoApproveFlag(
addArtifactsDirFlag(
addCreateFlags(c))))
return addJsonOutputFlag(
addGroupSelectionFlags(
addAutoApproveFlag(
addArtifactsDirFlag(
addCreateFlags(c)))))
}

func init() {
Expand Down Expand Up @@ -93,7 +94,7 @@ func doDeploy(deplRoot string) {
moduleDir := filepath.Join(groupDir, subPath)
checkErr(deployPackerGroup(moduleDir, getApplyBehavior()), ctx)
case config.TerraformKind:
checkErr(deployTerraformGroup(groupDir, artDir, getApplyBehavior()), ctx)
checkErr(deployTerraformGroup(groupDir, artDir, getApplyBehavior(), getJsonOutputBehavior()), ctx)
default:
checkErr(
config.BpError{
Expand Down Expand Up @@ -152,10 +153,10 @@ func deployPackerGroup(moduleDir string, applyBehavior shell.ApplyBehavior) erro
return nil
}

func deployTerraformGroup(groupDir string, artifactsDir string, applyBehavior shell.ApplyBehavior) error {
func deployTerraformGroup(groupDir string, artifactsDir string, applyBehavior shell.ApplyBehavior, outputFormat shell.OutputFormat) error {
tf, err := shell.ConfigureTerraform(groupDir)
if err != nil {
return err
}
return shell.ExportOutputs(tf, artifactsDir, applyBehavior)
return shell.ExportOutputs(tf, artifactsDir, applyBehavior, outputFormat)
}
2 changes: 1 addition & 1 deletion cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (s *MySuite) TestDeployGroups(c *C) {
pathEnv := os.Getenv("PATH")
os.Setenv("PATH", "")

err = deployTerraformGroup(".", getArtifactsDir("."), shell.NeverApply)
err = deployTerraformGroup(".", getArtifactsDir("."), shell.NeverApply, shell.TextOutput)
c.Check(err, NotNil)

err = deployPackerGroup(".", shell.NeverApply)
Expand Down
4 changes: 3 additions & 1 deletion cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ func destroyTerraformGroup(groupDir string) error {
return err
}

return shell.Destroy(tf, getApplyBehavior())
// Always output text when destroying the cluster
// The current implementation outputs JSON only for the "deploy" command
return shell.Destroy(tf, getApplyBehavior(), shell.TextOutput)
}

func destroyChoice(nextGroup config.GroupName) bool {
Expand Down
3 changes: 2 additions & 1 deletion cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@ func runExportCmd(cmd *cobra.Command, args []string) {
tf, err := shell.ConfigureTerraform(groupDir)
checkErr(err, ctx)

checkErr(shell.ExportOutputs(tf, artifactsDir, shell.NeverApply), ctx)
// Always output text when exporting (never JSON)
checkErr(shell.ExportOutputs(tf, artifactsDir, shell.NeverApply, shell.TextOutput), ctx)
}
14 changes: 14 additions & 0 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ func filterYaml(cmd *cobra.Command, args []string, toComplete string) ([]string,
return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
}

var flagJsonOutput bool

func getJsonOutputBehavior() shell.OutputFormat {
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
if flagJsonOutput {
return shell.JsonOutput
}
return shell.TextOutput
}

func addJsonOutputFlag(c *cobra.Command) *cobra.Command {
c.Flags().BoolVar(&flagJsonOutput, "json-output", false, "Output errors in JSON format")
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
return c
}

var flagSkipGroups []string
var flagOnlyGroups []string

Expand Down
69 changes: 59 additions & 10 deletions pkg/shell/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ import (
"github.com/zclconf/go-cty/cty/gocty"
)

// OutputFormat determines the format in which the errors are reported.
// Current supported format are text (default option) and JSON. Future
// format could be protobuf
type OutputFormat uint

// 2 output formats are currently supported:
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
// - Text
// - JSON
// - Future option is ProtoBuf
const (
TextOutput OutputFormat = iota
JsonOutput
)

// ApplyBehavior abstracts behaviors for making changes to cloud infrastructure
// when gcluster believes that they may be necessary
type ApplyBehavior uint
Expand Down Expand Up @@ -224,6 +238,31 @@ func promptForApply(tf *tfexec.Terraform, path string, b ApplyBehavior) bool {
}
}

// This function applies the terraform plan, but generates outputs in JSON format
// (instead of text)
func applyPlanJsonOutput(tf *tfexec.Terraform, path string) error {
planFileOpt := tfexec.DirOrPlan(path)
logging.Info("Running terraform apply on deployment group %s", tf.WorkingDir())
// To do: Make file name as a user input
// Make the JSON file name unique by having the Terraform group as a substring
jsonFilename := "cluster_toolkit_output-" + strings.ReplaceAll(tf.WorkingDir(), "/", ".") + ".json"
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
logging.Info("Writing to JSON file %s", jsonFilename)
jsonFile, err := os.Create(jsonFilename)
defer jsonFile.Close()
if err != nil {
logging.Info("Cannot create JSON output file %s", jsonFilename)
return err
}
tf.SetStdout(os.Stdout)
tf.SetStderr(os.Stderr)
if err := tf.ApplyJSON(context.Background(), jsonFile, planFileOpt); err != nil {
return err
}
tf.SetStdout(nil)
tf.SetStderr(nil)
return nil
}

func applyPlanConsoleOutput(tf *tfexec.Terraform, path string) error {
planFileOpt := tfexec.DirOrPlan(path)
logging.Info("Running terraform apply on deployment group %s", tf.WorkingDir())
Expand All @@ -240,7 +279,7 @@ func applyPlanConsoleOutput(tf *tfexec.Terraform, path string) error {
// generate a Terraform plan to apply or destroy a module
// recall "destroy" is just an alias for "apply -destroy"!
// apply the plan automatically or after prompting the user
func applyOrDestroy(tf *tfexec.Terraform, b ApplyBehavior, destroy bool) error {
func applyOrDestroy(tf *tfexec.Terraform, b ApplyBehavior, of OutputFormat, destroy bool) error {
action := "adding or changing"
pastTense := "applied"
if destroy {
Expand All @@ -256,14 +295,14 @@ func applyOrDestroy(tf *tfexec.Terraform, b ApplyBehavior, destroy bool) error {
// capture Terraform plan in a file
f, err := os.CreateTemp("", "plan-)")
if err != nil {
logging.Info("deploy.go.0000: applyOrDestroy()")
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
return err
}
defer os.Remove(f.Name())
wantsChange, err := planModule(tf, f.Name(), destroy)
if err != nil {
return err
}

var apply bool
if wantsChange {
logging.Info("Deployment group %s requires %s cloud infrastructure", tf.WorkingDir(), action)
Expand All @@ -276,15 +315,25 @@ func applyOrDestroy(tf *tfexec.Terraform, b ApplyBehavior, destroy bool) error {
return nil
}

if err := applyPlanConsoleOutput(tf, f.Name()); err != nil {
return err
switch of {

case JsonOutput:
if err := applyPlanJsonOutput(tf, f.Name()); err != nil {
return err
}
case TextOutput: // Text output to the console is also the default choice
fallthrough
nadig-google marked this conversation as resolved.
Show resolved Hide resolved
default:
if err := applyPlanConsoleOutput(tf, f.Name()); err != nil {
return err
}
}

return nil
}

func getOutputs(tf *tfexec.Terraform, b ApplyBehavior) (map[string]cty.Value, error) {
err := applyOrDestroy(tf, b, false)
func getOutputs(tf *tfexec.Terraform, b ApplyBehavior, o OutputFormat) (map[string]cty.Value, error) {
err := applyOrDestroy(tf, b, o, false)
if err != nil {
return nil, err
}
Expand All @@ -302,11 +351,11 @@ func outputsFile(artifactsDir string, group config.GroupName) string {

// ExportOutputs will run terraform output and capture data needed for
// subsequent deployment groups
func ExportOutputs(tf *tfexec.Terraform, artifactsDir string, applyBehavior ApplyBehavior) error {
func ExportOutputs(tf *tfexec.Terraform, artifactsDir string, applyBehavior ApplyBehavior, outputFormat OutputFormat) error {
thisGroup := config.GroupName(filepath.Base(tf.WorkingDir()))
filepath := outputsFile(artifactsDir, thisGroup)

outputValues, err := getOutputs(tf, applyBehavior)
outputValues, err := getOutputs(tf, applyBehavior, outputFormat)
if err != nil {
return err
}
Expand Down Expand Up @@ -428,8 +477,8 @@ func ImportInputs(groupDir string, artifactsDir string, bp config.Blueprint) err
}

// Destroy destroys all infrastructure in the module working directory
func Destroy(tf *tfexec.Terraform, b ApplyBehavior) error {
return applyOrDestroy(tf, b, true)
func Destroy(tf *tfexec.Terraform, b ApplyBehavior, o OutputFormat) error {
return applyOrDestroy(tf, b, o, true)
}

func TfVersion() (string, error) {
Expand Down