Files
infra-provisioner/resource.go

270 lines
8.2 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/navi-infra/infra-provisioner/v2/bindata"
)
type void struct{}
const TemplatesDir = "templates"
const InitScript = "./deploy.sh"
const deploymentPortalUrl = "deployment-portal-backend.cmd.navi-tech.in"
func getInfraVertical(vertical string) string {
return InfraVerticals[vertical]
}
func getDeploymentPortalHostUrl(vertical string) string {
env := os.Getenv("TESTING_ENV")
if env == "dev" {
return "http://localhost:8080"
}
return fmt.Sprintf("https://%s%s", getInfraVertical(vertical), deploymentPortalUrl)
}
func getDeploymentPortalUrl(infraVertical string, environment string, name string) string {
return fmt.Sprintf("%s/api/manifest/status/env/%s/name/%s",
getDeploymentPortalHostUrl(infraVertical),
environment,
name)
}
var extraResourcesDeploymentStatus ExtraResourcesDeployment
func provisionResource(resourceName, resourceDir string, manifest *Manifest) error {
err := templateResourceTf(resourceName, resourceDir, manifest, resourceDir)
if err != nil {
log.Fatalf("\nErr: %v", err)
return err
}
if !manifest.Actions.TemplateOnly {
err = executeResourceTf(resourceDir)
if err != nil {
log.Fatalf("\nErr: %v", err)
return err
}
deploymentStatus := true
if manifest.Actions.Destroy {
deploymentStatus = false
}
setResourceDeploymentStatus(resourceName, deploymentStatus, manifest)
}
return nil
}
func setExtraResourceStatus(resourceName string, deploymentStatus bool, resource *ExtraResourceData) {
resource.ResourceName = resourceName
resource.IsDeployed = deploymentStatus
}
func appendExtraResourceStatus(resourceName string, deploymentStatus bool, resource *[]ExtraResourceData) {
var extraResource ExtraResourceData
extraResource.ResourceName = resourceName
extraResource.IsDeployed = deploymentStatus
*resource = append(*resource, extraResource)
}
func setResourceDeploymentStatus(resourceName string, deploymentStatus bool, manifest *Manifest) {
defer handlePanic()
switch resourceName {
case RESOURCE_RDS:
setExtraResourceStatus(manifest.ExtraResources.Database.InstanceName, deploymentStatus,
&extraResourcesDeploymentStatus.Database)
case RESOURCE_AURORADB:
setExtraResourceStatus(manifest.ExtraResources.Database.InstanceName, deploymentStatus,
&extraResourcesDeploymentStatus.Database)
case RESOURCE_ELASTIC_CACHE:
setExtraResourceStatus(manifest.ExtraResources.ElasticCache.InstanceName, deploymentStatus,
&extraResourcesDeploymentStatus.ElasticCache)
case RESOURCE_DOCDB:
setExtraResourceStatus(manifest.ExtraResources.DocDb.InstanceName, deploymentStatus,
&extraResourcesDeploymentStatus.DocDb)
case RESOURCE_AWS_ROLES:
setExtraResourceStatus(manifest.ExtraResources.ServiceRole.RoleName, deploymentStatus,
&extraResourcesDeploymentStatus.AwsAccess)
case RESOURCE_S3_BUCKETS:
for _, bucket := range manifest.ExtraResources.S3Buckets {
appendExtraResourceStatus(bucket.BucketName, deploymentStatus, &extraResourcesDeploymentStatus.S3Buckets)
}
case RESOURCE_DYNAMODB:
for _, table := range manifest.ExtraResources.Dynamodb.Tables {
if table.DeleteFromAWS {
appendExtraResourceStatus(table.TableName, false, &extraResourcesDeploymentStatus.Dynamodb)
} else {
appendExtraResourceStatus(table.TableName, deploymentStatus, &extraResourcesDeploymentStatus.Dynamodb)
}
}
default:
log.Panicf("\nErr: %v", "Invalid Resource Name "+resourceName)
}
}
func handlePanic() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
}
}
// sendResourceDeploymentStatusBlackList is a list of verticals for which deployment status should not be sent
var sendResourceDeploymentStatusBlackList = map[string]void{
"ktk": {},
}
func sendResourceDeploymentStatus(manifest *Manifest) {
if _, ok := sendResourceDeploymentStatusBlackList[manifest.InfraVertical]; ok {
log.Printf("Skipping sending deployment status for vertical %s", manifest.InfraVertical)
return
}
defer handlePanic()
client := &http.Client{}
url := getDeploymentPortalUrl(manifest.InfraVertical, manifest.Environment, manifest.Name)
jsonData, err := json.Marshal(extraResourcesDeploymentStatus)
if err != nil {
log.Panicf("Error encoding JSON:#{err}")
return
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
log.Panicf("\nErr: #{err} \nFailed to create Get Request")
return
}
deploymentPortalToken, tokenStatus := os.LookupEnv("DEPLOYMENT_PORTAL_TOKEN")
if !tokenStatus {
log.Panicf("\nFailed to get Deployment Portal Token")
return
}
req.Header.Set("X_AUTH_TOKEN", deploymentPortalToken)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Panicf("\nErr: %v\nFailed to get response", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != 202 && resp.StatusCode != 404 {
log.Panicf("\nFailed to set deployment Status of Resources, Response Code: %v", resp.StatusCode)
}
}
func provisionAllResource(manifest *Manifest) error {
var extraResource ExtraResources
structType := getStructType(reflect.TypeOf(extraResource))
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
jsonName, jsonOk := field.Tag.Lookup("json")
modules, modOk := field.Tag.Lookup("module")
moduleDirs, modDirOk := field.Tag.Lookup("moduleDir")
if jsonOk && modOk && modDirOk && checkResourceExists(jsonName, manifest) {
if modules == "" || moduleDirs == "" {
continue
}
module, moduleDir := getModuleDir(jsonName, modules, moduleDirs, manifest)
err := provisionResource(module, moduleDir, manifest)
if err != nil {
log.Fatalf("\nErr: %v", err)
return err
}
}
}
if manifest.Actions.Apply && !manifest.Actions.TemplateOnly {
sendResourceDeploymentStatus(manifest)
}
return nil
}
func getModuleDir(jsonName, modules, moduleDirs string, manifest *Manifest) (string, string) {
listModule := strings.Split(modules, ",")
listModuleDir := strings.Split(moduleDirs, ",")
if jsonName == "database" {
return getDatabaseModule(listModule, listModuleDir, manifest)
}
return listModule[0], listModuleDir[0]
}
func getDatabaseModule(listModule, listModuleDir []string, manifest *Manifest) (string, string) {
if manifest.ExtraResources.Database.DbEngineType == "rds-aurora-postgres" {
return listModule[1], listModuleDir[1]
}
return listModule[0], listModuleDir[0]
}
func checkResourceExists(resourceName string, manifest *Manifest) bool {
var myMap map[string]interface{}
data, _ := json.Marshal(manifest.ExtraResources)
err := json.Unmarshal(data, &myMap)
if err != nil {
log.Fatalf("\nErr: %v", err)
}
val, ok := myMap[resourceName]
if ok && val != nil {
return true
}
return false
}
func templateResourceTf(templateName, resourceDir string, manifest *Manifest, destinationDir string) error {
log.Printf("Processing path %s", filepath.Join(TemplatesDir, resourceDir))
assetNames := bindata.AssetNames()
filteredAssets := []string{}
for _, assetName := range assetNames {
if strings.HasPrefix(assetName, filepath.Join(TemplatesDir, resourceDir)) {
filteredAssets = append(filteredAssets, assetName)
}
}
for _, asset := range filteredAssets {
tfBytes := bindata.MustAsset(asset)
t := template.Must(template.New(templateName).Funcs(sprig.TxtFuncMap()).Parse(string(tfBytes)))
directoryPath := filepath.Dir(filepath.Join(destinationDir, strings.TrimPrefix(asset, filepath.Join(TemplatesDir, resourceDir))))
tfOut, err := createFile(directoryPath+"/", filepath.Base(asset))
if err != nil {
log.Fatalf("\nErr: %v", err)
return err
}
err = t.Execute(tfOut, manifest)
if err != nil {
log.Fatalf("\nError while Templating %s. \nPlease ensure %s is present in the manifest, if present please check if the configurations are correct.\n"+
"Err: %v", resourceDir, templateDirMap[resourceDir], err)
return err
}
tfOut.Close()
}
return nil
}
func executeResourceTf(resourceDir string) error {
var cmd *exec.Cmd
log.Printf("Running terraform for %s", resourceDir)
cmd = exec.Command(InitScript)
cmd.Dir = resourceDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Fatalf("\nErr: %v", err)
return err
}
return err
}