mirror of
https://github.com/kubevela/kubevela.git
synced 2026-03-06 03:31:12 +00:00
add staticcheck CI action add staticcheck in Makefile Signed-off-by: roywang <seiwy2010@gmail.com>
181 lines
5.5 KiB
Go
181 lines
5.5 KiB
Go
package appfile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/commands/util"
|
|
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
|
"github.com/oam-dev/kubevela/pkg/utils/common"
|
|
)
|
|
|
|
const (
|
|
// TerraformBaseLocation is the base directory to store all Terraform JSON files
|
|
TerraformBaseLocation = ".vela/terraform/"
|
|
// TerraformLog is the logfile name for terraform
|
|
TerraformLog = "terraform.log"
|
|
)
|
|
|
|
// ApplyTerraform deploys addon resources
|
|
func ApplyTerraform(app *v1alpha2.Application, k8sClient client.Client, ioStream util.IOStreams, namespace string, dm discoverymapper.DiscoveryMapper) ([]v1alpha2.ApplicationComponent, error) {
|
|
// TODO(zzxwill) Need to check whether authentication credentials of a specific cloud provider are exported as environment variables, like `ALICLOUD_ACCESS_KEY`
|
|
var nativeVelaComponents []v1alpha2.ApplicationComponent
|
|
// parse template
|
|
appParser := NewApplicationParser(k8sClient, dm)
|
|
appFile, err := appParser.GenerateAppFile(app.Name, app)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse appfile: %w", err)
|
|
}
|
|
if appFile == nil {
|
|
return nil, fmt.Errorf("failed to parse appfile")
|
|
}
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, wl := range appFile.Workloads {
|
|
switch wl.CapabilityCategory {
|
|
case types.TerraformCategory:
|
|
name := wl.Name
|
|
ioStream.Infof("\nApplying cloud resources %s\n", name)
|
|
|
|
tf, err := getTerraformJSONFiles(k8sClient, wl, appFile.Name, namespace)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Terraform JSON files from workload %s: %w", name, err)
|
|
}
|
|
|
|
tfJSONDir := filepath.Join(TerraformBaseLocation, name)
|
|
if _, err = os.Stat(tfJSONDir); err != nil && os.IsNotExist(err) {
|
|
if err = os.MkdirAll(tfJSONDir, 0750); err != nil {
|
|
return nil, fmt.Errorf("failed to create directory for %s: %w", tfJSONDir, err)
|
|
}
|
|
}
|
|
if err := ioutil.WriteFile(filepath.Join(tfJSONDir, "main.tf.json"), tf, 0600); err != nil {
|
|
return nil, fmt.Errorf("failed to convert Terraform template: %w", err)
|
|
}
|
|
|
|
outputs, err := callTerraform(tfJSONDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := os.Chdir(cwd); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputList := strings.Split(strings.ReplaceAll(string(outputs), " ", ""), "\n")
|
|
if outputList[len(outputList)-1] == "" {
|
|
outputList = outputList[:len(outputList)-1]
|
|
}
|
|
if err := generateSecretFromTerraformOutput(k8sClient, outputList, name, namespace); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
nativeVelaComponents = append(nativeVelaComponents, app.Spec.Components[i])
|
|
}
|
|
|
|
}
|
|
return nativeVelaComponents, nil
|
|
}
|
|
|
|
func callTerraform(tfJSONDir string) ([]byte, error) {
|
|
if err := os.Chdir(tfJSONDir); err != nil {
|
|
return nil, err
|
|
}
|
|
var cmd *exec.Cmd
|
|
cmd = exec.Command("bash", "-c", "terraform init")
|
|
if err := common.RealtimePrintCommandOutput(cmd, TerraformLog); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd = exec.Command("bash", "-c", "terraform apply --auto-approve")
|
|
if err := common.RealtimePrintCommandOutput(cmd, TerraformLog); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get output from Terraform
|
|
cmd = exec.Command("bash", "-c", "terraform output")
|
|
outputs, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return outputs, nil
|
|
}
|
|
|
|
// generateSecretFromTerraformOutput generates secret from Terraform output
|
|
func generateSecretFromTerraformOutput(k8sClient client.Client, outputList []string, name, namespace string) error {
|
|
ctx := context.TODO()
|
|
err := k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})
|
|
if err == nil {
|
|
return fmt.Errorf("namespace %s doesn't exist", namespace)
|
|
}
|
|
var cmData = make(map[string]string, len(outputList))
|
|
for _, i := range outputList {
|
|
line := strings.Split(i, "=")
|
|
if len(line) != 2 {
|
|
return fmt.Errorf("terraform output isn't in the right format")
|
|
}
|
|
k := strings.TrimSpace(line[0])
|
|
v := strings.TrimSpace(line[1])
|
|
if k != "" && v != "" {
|
|
cmData[k] = v
|
|
}
|
|
}
|
|
|
|
objectKey := client.ObjectKey{
|
|
Namespace: namespace,
|
|
Name: name,
|
|
}
|
|
var secret v1.Secret
|
|
if err := k8sClient.Get(ctx, objectKey, &secret); err != nil && !errors.IsNotFound(err) {
|
|
return fmt.Errorf("retrieving the secret from cloud resource %s hit an issue: %w", name, err)
|
|
} else if err == nil {
|
|
if err := k8sClient.Delete(ctx, &secret); err != nil {
|
|
return fmt.Errorf("failed to store cloud resource %s output to secret: %w", name, err)
|
|
}
|
|
}
|
|
|
|
secret = v1.Secret{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "v1",
|
|
Kind: "Secret",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
Name: name,
|
|
},
|
|
StringData: cmData,
|
|
}
|
|
|
|
if err := k8sClient.Create(ctx, &secret); err != nil {
|
|
return fmt.Errorf("failed to store cloud resource %s output to secret: %w", name, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getTerraformJSONFiles gets Terraform JSON files or modules from workload
|
|
func getTerraformJSONFiles(k8sClient client.Client, wl *Workload, applicationName string, namespace string) ([]byte, error) {
|
|
pCtx, err := PrepareProcessContext(k8sClient, wl, applicationName, namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
base, _ := pCtx.Output()
|
|
tf, err := base.Compile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return tf, nil
|
|
}
|