Files
kubevela/pkg/appfile/addon.go
roy wang 93ae8a9099 fix staticcheck issues
add staticcheck CI action

add staticcheck in Makefile

Signed-off-by: roywang <seiwy2010@gmail.com>
2021-01-29 18:42:03 +09:00

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
}