github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cloudprovider/terraform.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package cloudprovider 21 22 import ( 23 "context" 24 "encoding/json" 25 "io" 26 "os" 27 "path/filepath" 28 "time" 29 30 "github.com/hashicorp/go-version" 31 "github.com/hashicorp/hc-install/product" 32 "github.com/hashicorp/hc-install/releases" 33 "github.com/hashicorp/terraform-exec/tfexec" 34 "github.com/pkg/errors" 35 36 "github.com/1aal/kubeblocks/pkg/cli/util" 37 ) 38 39 const ( 40 tfStateFileName = "terraform.tfstate" 41 ) 42 43 type TFPlugin struct { 44 Name string 45 Registry string 46 Source string 47 Version string 48 } 49 50 var ( 51 TFExecPath string 52 ) 53 54 func initTerraform() error { 55 cliHomeDir, err := util.GetCliHomeDir() 56 if err != nil { 57 return err 58 } 59 60 // check if terraform exists 61 TFExecPath = filepath.Join(cliHomeDir, product.Terraform.BinaryName()) 62 v, err := product.Terraform.GetVersion(context.Background(), TFExecPath) 63 if err == nil && v != nil { 64 return nil 65 } 66 67 // does not exist, install it to cli home dir 68 installer := &releases.ExactVersion{ 69 Product: product.Terraform, 70 Version: version.Must(version.NewVersion("1.3.9")), 71 Timeout: 180 * time.Second, 72 SkipChecksumVerification: true, 73 InstallDir: cliHomeDir, 74 } 75 execPath, err := installer.Install(context.Background()) 76 if err != nil { 77 return err 78 } 79 TFExecPath = execPath 80 return nil 81 } 82 83 func tfInitAndApply(workingDir string, stdout, stderr io.Writer, opts ...tfexec.ApplyOption) error { 84 ctx := context.Background() 85 tf, err := newTerraform(workingDir, stdout, stderr) 86 if err != nil { 87 return err 88 } 89 90 if err = tf.Init(ctx, tfexec.Upgrade(false)); err != nil { 91 return err 92 } 93 94 if err = tf.Apply(ctx, opts...); err != nil { 95 return err 96 } 97 return nil 98 } 99 100 func tfInitAndDestroy(workingDir string, stdout, stderr io.Writer, opts ...tfexec.DestroyOption) error { 101 ctx := context.Background() 102 tf, err := newTerraform(workingDir, stdout, stderr) 103 if err != nil { 104 return err 105 } 106 107 if err = tf.Init(ctx, tfexec.Upgrade(false)); err != nil { 108 return err 109 } 110 111 return tf.Destroy(ctx, opts...) 112 } 113 114 func newTerraform(workingDir string, stdout, stderr io.Writer) (*tfexec.Terraform, error) { 115 tf, err := tfexec.NewTerraform(workingDir, TFExecPath) 116 if err != nil { 117 return nil, err 118 } 119 120 tf.SetStdout(stdout) 121 tf.SetStderr(stderr) 122 return tf, nil 123 } 124 125 func getOutputValues(tfPath string, keys ...outputKey) ([]string, error) { 126 stateFile := filepath.Join(tfPath, tfStateFileName) 127 content, err := os.ReadFile(stateFile) 128 if err != nil && !errors.Is(err, os.ErrNotExist) { 129 return nil, err 130 } 131 132 var state map[string]interface{} 133 if err = json.Unmarshal(content, &state); err != nil { 134 return nil, err 135 } 136 outputs, ok := state["outputs"].(map[string]interface{}) 137 if !ok { 138 return nil, nil 139 } 140 141 vals := make([]string, len(keys)) 142 for i, k := range keys { 143 v, ok := outputs[string(k)].(map[string]interface{}) 144 if !ok { 145 continue 146 } 147 vals[i] = v["value"].(string) 148 } 149 return vals, nil 150 }