Skip to content

Commit

Permalink
refactor: parse state directly instead of using tf show (#391)
Browse files Browse the repository at this point in the history
also:

- enable pprof
- add tracing
- remove readonly logic (was no longer being used, as we lock every time)
  • Loading branch information
goncalo-rodrigues authored Aug 3, 2022
1 parent 9d615ed commit f84ef6e
Show file tree
Hide file tree
Showing 19 changed files with 182 additions and 188 deletions.
46 changes: 21 additions & 25 deletions api/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"github.com/multycloud/multy/api/errors"
"github.com/multycloud/multy/api/util"
"github.com/multycloud/multy/db"
"github.com/multycloud/multy/flags"
"github.com/multycloud/multy/resources"
"github.com/multycloud/multy/resources/output"
Expand Down Expand Up @@ -40,14 +41,14 @@ func NewDeploymentExecutor() DeploymentExecutor {
return DeploymentExecutor{TfCmd: terraformCmd{}}
}

func (d DeploymentExecutor) Deploy(ctx context.Context, c *resources.MultyConfig, prev resources.Resource, curr resources.Resource) (state *output.TfState, rollbackFn func(), err error) {
tmpDir := GetTempDirForUser(false, c.GetUserId())
encoded, err := d.EncodeAndStoreTfFile(ctx, c, prev, curr, false)
func (d DeploymentExecutor) Deploy(ctx context.Context, c *resources.MultyConfig, prev resources.Resource, curr resources.Resource) (rollbackFn func(), err error) {
tmpDir := GetTempDirForUser(c.GetUserId())
encoded, err := d.EncodeAndStoreTfFile(ctx, c, prev, curr)
if err != nil {
return
}

err = d.MaybeInit(ctx, c.GetUserId(), false)
err = d.MaybeInit(ctx, c.GetUserId())
if err != nil {
return
}
Expand All @@ -67,7 +68,7 @@ func (d DeploymentExecutor) Deploy(ctx context.Context, c *resources.MultyConfig
log.Printf("[ERROR] Rollback unsuccessful: %s\n", err2)
return
}
_, err2 = d.EncodeAndStoreTfFile(ctx, originalC, curr, prev, false)
_, err2 = d.EncodeAndStoreTfFile(ctx, originalC, curr, prev)
if err2 != nil {
log.Printf("[ERROR] Rollback unsuccessful: %s\n", err2)
return
Expand All @@ -92,15 +93,10 @@ func (d DeploymentExecutor) Deploy(ctx context.Context, c *resources.MultyConfig
return
}

state, err = d.GetState(ctx, c.GetUserId(), false)
if err != nil {
return state, rollbackFn, errors.InternalServerErrorWithMessage("error parsing state", err)
}

return
}

func (d DeploymentExecutor) EncodeAndStoreTfFile(ctx context.Context, c *resources.MultyConfig, prev resources.Resource, curr resources.Resource, readonly bool) (EncodedResources, error) {
func (d DeploymentExecutor) EncodeAndStoreTfFile(ctx context.Context, c *resources.MultyConfig, prev resources.Resource, curr resources.Resource) (EncodedResources, error) {
credentials, err := util.ExtractCloudCredentials(ctx)
if err != nil {
return EncodedResources{}, err
Expand All @@ -118,7 +114,7 @@ func (d DeploymentExecutor) EncodeAndStoreTfFile(ctx context.Context, c *resourc
// TODO: move this to a proper place
hclOutput := tfBlock + encoded.HclString

tmpDir := GetTempDirForUser(readonly, c.GetUserId())
tmpDir := GetTempDirForUser(c.GetUserId())
err = os.MkdirAll(tmpDir, os.ModeDir|(os.ModePerm&0775))
if err != nil {
return EncodedResources{}, err
Expand All @@ -127,8 +123,8 @@ func (d DeploymentExecutor) EncodeAndStoreTfFile(ctx context.Context, c *resourc
return encoded, err
}

func (d DeploymentExecutor) MaybeInit(ctx context.Context, userId string, readonly bool) error {
tmpDir := GetTempDirForUser(readonly, userId)
func (d DeploymentExecutor) MaybeInit(ctx context.Context, userId string) error {
tmpDir := GetTempDirForUser(userId)
_, err := os.Stat(filepath.Join(tmpDir, tfDir))
if os.IsNotExist(err) {
start := time.Now()
Expand All @@ -151,22 +147,26 @@ func (d DeploymentExecutor) MaybeInit(ctx context.Context, userId string, readon
return nil
}

func (d DeploymentExecutor) GetState(ctx context.Context, userId string, readonly bool) (*output.TfState, error) {
tmpDir := GetTempDirForUser(readonly, userId)
return d.TfCmd.GetState(ctx, tmpDir)
func (d DeploymentExecutor) GetState(ctx context.Context, userId string, client db.TfStateReader) (*output.TfState, error) {
return d.TfCmd.GetState(ctx, userId, client)
}

func (d DeploymentExecutor) RefreshState(ctx context.Context, userId string, c *resources.MultyConfig) error {
_, err := d.EncodeAndStoreTfFile(ctx, c, nil, nil, true)
_, err := d.EncodeAndStoreTfFile(ctx, c, nil, nil)
if err != nil {
return err
}

err = d.MaybeInit(ctx, userId, true)
err = d.MaybeInit(ctx, userId)
if err != nil {
return err
}

start := time.Now()
defer func() {
log.Printf("[DEBUG] refresh finished in %s", time.Since(start))
}()

return d.refresh(ctx, userId)
}

Expand All @@ -176,20 +176,16 @@ func (d DeploymentExecutor) refresh(ctx context.Context, userId string) error {
log.Printf("[DEBUG] refresh finished in %s", time.Since(start))
}()

tmpDir := GetTempDirForUser(true, userId)
tmpDir := GetTempDirForUser(userId)
return d.TfCmd.Refresh(ctx, tmpDir)
}

func GetTempDirForUser(readonly bool, userId string) string {
func GetTempDirForUser(userId string) string {
tmpDir := filepath.Join(os.TempDir(), "multy", userId)

if flags.Environment == flags.Local {
tmpDir = filepath.Join(tmpDir, "local")
}

if readonly {
tmpDir = filepath.Join(tmpDir, "readonly")
}

return tmpDir
}
6 changes: 3 additions & 3 deletions api/deploy/provider_versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deploy

import (
"fmt"
"github.com/multycloud/multy/db"
"github.com/multycloud/multy/flags"
"os"
"path"
Expand All @@ -14,7 +15,6 @@ const (
randomProviderVersion = "3.1.3"
localProviderVersion = "2.2.2"
tfStateRegion = "eu-west-2"
tfState = "terraform.tfstate"
)

func GetTerraformBlock(userId string) (string, error) {
Expand All @@ -29,7 +29,7 @@ func GetTerraformBlock(userId string) (string, error) {
}

func getLocalStateBlock(userId string) string {
p := path.Join(os.TempDir(), "multy", userId, "local", tfState)
p := path.Join(os.TempDir(), "multy", userId, "local", db.TfState)
return fmt.Sprintf(
`backend "local" {
path = "%s"
Expand All @@ -41,7 +41,7 @@ func getRemoteStateBlock(userId, userStorageName string) string {
key = "%s/%s"
region = "%s"
profile = "multy"
}`, userStorageName, userId, tfState, tfStateRegion)
}`, userStorageName, userId, db.TfState, tfStateRegion)
}

func getTerraformBlock(providerBlock string) (string, error) {
Expand Down
37 changes: 25 additions & 12 deletions api/deploy/terraform_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import (
"encoding/json"
"fmt"
"github.com/multycloud/multy/api/errors"
"github.com/multycloud/multy/db"
"github.com/multycloud/multy/flags"
"github.com/multycloud/multy/resources/output"
"io"
"log"
"os"
"os/exec"
"runtime/trace"
)

type TerraformCommand interface {
Init(ctx context.Context, dir string) error
Apply(ctx context.Context, dir string, resources []string) error
Refresh(ctx context.Context, dir string) error
GetState(ctx context.Context, dir string) (*output.TfState, error)
GetState(ctx context.Context, userId string, dir db.TfStateReader) (*output.TfState, error)
}

type terraformCmd struct {
Expand All @@ -38,13 +40,18 @@ func (c terraformCmd) Apply(ctx context.Context, dir string, resources []string)
if len(resources) == 0 {
return nil
}

region := trace.StartRegion(ctx, "tf apply")
defer region.End()

var targetArgs []string

log.Println("[INFO] Running apply for targets:")
idsToPrint := ""
for _, id := range resources {
log.Printf("[INFO] %s", id)
idsToPrint += id + ", "
targetArgs = append(targetArgs, "-target="+id)
}
log.Printf("[INFO] Running apply for targets: %s", idsToPrint)
cmd := exec.CommandContext(ctx, "terraform", append([]string{"-chdir=" + dir, "apply", "-refresh=false", "-auto-approve", "--json"}, targetArgs...)...)
if flags.DryRun {
cmd = exec.CommandContext(ctx, "terraform", append([]string{"-chdir=" + dir, "plan", "-refresh=false", "--json"}, targetArgs...)...)
Expand All @@ -68,6 +75,9 @@ func (c terraformCmd) Apply(ctx context.Context, dir string, resources []string)
}

func (c terraformCmd) Init(ctx context.Context, dir string) error {
region := trace.StartRegion(ctx, "tf init")
defer region.End()

cmd := exec.CommandContext(ctx, "terraform", "-chdir="+dir, "init", "-reconfigure", "-lock-timeout", "1m")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand All @@ -79,6 +89,9 @@ func (c terraformCmd) Init(ctx context.Context, dir string) error {
}

func (c terraformCmd) Refresh(ctx context.Context, dir string) error {
region := trace.StartRegion(ctx, "tf refresh")
defer region.End()

outputJson := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, "terraform", "-chdir="+dir, "refresh", "-json")
cmd.Stdout = outputJson
Expand All @@ -97,18 +110,18 @@ func (c terraformCmd) Refresh(ctx context.Context, dir string) error {
return err
}

func (c terraformCmd) GetState(ctx context.Context, dir string) (*output.TfState, error) {
state := output.TfState{}
stateJson := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, "terraform", "-chdir="+dir, "show", "-json")
cmd.Stdout = stateJson
cmd.Stderr = os.Stderr
err := cmd.Run()
func (c terraformCmd) GetState(ctx context.Context, userId string, client db.TfStateReader) (*output.TfState, error) {
region := trace.StartRegion(ctx, "tf show")
defer region.End()

terraformState, err := client.LoadTerraformState(ctx, userId)
if err != nil {
return &state, err
return nil, err
}

err = json.Unmarshal(stateJson.Bytes(), &state)
state := output.TfState{}

err = json.Unmarshal([]byte(terraformState), &state)
if err != nil {
return nil, err
}
Expand Down
19 changes: 19 additions & 0 deletions api/interceptors/trace_interceptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package interceptors

import (
"context"
"google.golang.org/grpc"
"runtime/trace"
)

func TraceUnaryInterceptor() grpc.UnaryServerInterceptor {
return TraceInterceptor
}

func TraceInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
ctx, task := trace.NewTask(ctx, "request")
trace.Log(ctx, "method", info.FullMethod)
defer task.End()
// Invoke the original method call
return handler(ctx, req)
}
20 changes: 10 additions & 10 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
aws_client "github.com/multycloud/multy/api/aws"
"github.com/multycloud/multy/api/deploy"
"github.com/multycloud/multy/api/errors"
"github.com/multycloud/multy/api/interceptors"
"github.com/multycloud/multy/api/proto"
"github.com/multycloud/multy/api/proto/commonpb"
"github.com/multycloud/multy/api/proto/resourcespb"
"github.com/multycloud/multy/api/service_context"
"github.com/multycloud/multy/api/services"
"github.com/multycloud/multy/api/util"
"github.com/multycloud/multy/db"
"github.com/multycloud/multy/flags"
"github.com/multycloud/multy/resources"
"github.com/multycloud/multy/resources/types/metadata"
"google.golang.org/grpc"
Expand Down Expand Up @@ -57,9 +57,9 @@ func RunServer(ctx context.Context, port int) {
}

var s *grpc.Server
if flags.Environment == flags.Local {
log.Println("[INFO] running in local mode")
s = grpc.NewServer()
if port != 443 {
log.Println("[INFO] Running in HTTP mode, port is not 443")
s = grpc.NewServer(grpc.UnaryInterceptor(interceptors.TraceUnaryInterceptor()))
} else {
endpoint, exists := os.LookupEnv("MULTY_API_ENDPOINT")
if !exists {
Expand All @@ -72,7 +72,7 @@ func RunServer(ctx context.Context, port int) {
if err != nil {
log.Fatalf("unable to read certificate (%s)", err.Error())
}
s = grpc.NewServer(grpc.Creds(creds))
s = grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptors.TraceUnaryInterceptor()))
}

go func() {
Expand Down Expand Up @@ -379,7 +379,7 @@ func (s *Server) refresh(ctx context.Context, _ *commonpb.Empty) (*commonpb.Empt
}
defer s.Database.UnlockConfig(ctx, lock)

c, err := s.Database.LoadUserConfig(userId, lock)
c, err := s.Database.LoadUserConfig(ctx, userId, lock)
if err != nil {
return nil, err
}
Expand All @@ -393,7 +393,7 @@ func (s *Server) refresh(ctx context.Context, _ *commonpb.Empty) (*commonpb.Empt
return nil, err
}

err = s.Database.StoreUserConfig(c, lock)
err = s.Database.StoreUserConfig(ctx, c, lock)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -422,7 +422,7 @@ func (s *Server) list(ctx context.Context, _ *commonpb.Empty) (*commonpb.ListRes
return nil, err
}

c, err := s.Database.LoadUserConfig(userId, nil)
c, err := s.Database.LoadUserConfig(ctx, userId, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -467,7 +467,7 @@ func (s *Server) deleteResource(ctx context.Context, req *proto.DeleteResourceRe
return nil, err
}
defer s.Database.UnlockConfig(ctx, lock)
c, err := s.Database.LoadUserConfig(userId, lock)
c, err := s.Database.LoadUserConfig(ctx, userId, lock)
if err != nil {
return nil, err
}
Expand All @@ -477,7 +477,7 @@ func (s *Server) deleteResource(ctx context.Context, req *proto.DeleteResourceRe
return nil, err
}

err = s.Database.StoreUserConfig(c, lock)
err = s.Database.StoreUserConfig(ctx, c, lock)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit f84ef6e

Please sign in to comment.