Skip to content

Commit 56e222d

Browse files
committed
[Draft] Trying to make HTTP backend work
1 parent 3b1958d commit 56e222d

File tree

3 files changed

+146
-5
lines changed

3 files changed

+146
-5
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package helper
2+
3+
import (
4+
"net/url"
5+
"path"
6+
"strings"
7+
)
8+
9+
func PlanAddressForHTTPBackend(BackendConfig map[string]interface{}) (string, error) {
10+
11+
if planAddress, ok := BackendConfig["plan_address"]; ok {
12+
return planAddress.(string), nil
13+
}
14+
15+
address := BackendConfig["address"].(string)
16+
// As Terraform HTTP backend don't implement workspaces, we need to define a
17+
// different URL to store the plan file.
18+
u, err := url.Parse(address)
19+
if err != nil {
20+
return "", err
21+
}
22+
23+
// Parse the URL and add -plan suffix to the filename
24+
baseURL, fFullName := path.Split(u.Path)
25+
fExt := path.Ext(u.Path)
26+
fName := strings.TrimSuffix(fFullName, fExt)
27+
28+
fFullName = fName + "-plan" + fExt
29+
u.Path = path.Join(baseURL, fFullName)
30+
return u.String(), nil
31+
}

src/terraform-resource/in/in.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
"github.com/ljfranklin/terraform-resource/encoder"
16+
"github.com/ljfranklin/terraform-resource/helper"
1617
"github.com/ljfranklin/terraform-resource/logger"
1718
"github.com/ljfranklin/terraform-resource/models"
1819
"github.com/ljfranklin/terraform-resource/storage"
@@ -101,11 +102,29 @@ func (r Runner) inWithBackend(req models.InRequest, tmpDir string) (models.InRes
101102

102103
targetEnvName := req.Version.EnvName
103104

105+
// If plan and http backend, use plan URL
106+
origBackendConfig := terraformModel.BackendConfig
107+
if req.Version.IsPlan() && terraformModel.BackendType == "http" {
108+
planAddress, err := helper.PlanAddressForHTTPBackend(terraformModel.BackendConfig)
109+
if err != nil {
110+
return models.InResponse{}, err
111+
}
112+
terraformModel.BackendConfig["address"] = planAddress
113+
}
114+
104115
client := terraform.NewClient(
105116
terraformModel,
106117
r.LogWriter,
107118
)
108119

120+
// Rollback address change in case of http backend and tfplan
121+
defer func() {
122+
if req.Version.IsPlan() && terraformModel.BackendType == "http" {
123+
terraformModel.BackendConfig = origBackendConfig
124+
client.SetModel(terraformModel)
125+
}
126+
}()
127+
109128
if err := client.InitWithBackend(); err != nil {
110129
return models.InResponse{}, err
111130
}
@@ -138,8 +157,16 @@ func (r Runner) inWithBackend(req models.InRequest, tmpDir string) (models.InRes
138157
}
139158

140159
func (r Runner) writeBackendOutputs(req models.InRequest, targetEnvName string, client terraform.Client) (models.InResponse, error) {
141-
if err := r.ensureEnvExistsInBackend(targetEnvName, client); err != nil {
142-
return models.InResponse{}, err
160+
terraformModel := req.Source.Terraform.Merge(req.Params.Terraform)
161+
if err := terraformModel.Validate(); err != nil {
162+
return models.InResponse{}, fmt.Errorf("Failed to validate terraform Model: %s", err)
163+
}
164+
terraformModel.Source = "."
165+
166+
if terraformModel.BackendType != "http" {
167+
if err := r.ensureEnvExistsInBackend(targetEnvName, client); err != nil {
168+
return models.InResponse{}, err
169+
}
143170
}
144171

145172
tfOutput, err := client.Output(targetEnvName)

src/terraform-resource/terraform/client.go

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"path/filepath"
1717
"strings"
1818

19+
"github.com/ljfranklin/terraform-resource/helper"
1920
"github.com/ljfranklin/terraform-resource/models"
2021
"github.com/ljfranklin/terraform-resource/runner"
2122
)
@@ -115,7 +116,16 @@ func (c *client) InitWithBackend() error {
115116
}
116117

117118
func (c *client) writeBackendConfig(outputDir string) (string, error) {
118-
configContents, err := json.Marshal(c.model.BackendConfig)
119+
bc := make(map[string]interface{})
120+
for key, value := range c.model.BackendConfig {
121+
bc[key] = value
122+
}
123+
124+
if _, ok := c.model.BackendConfig["plan_address"]; ok && c.model.BackendType == "http" {
125+
delete(bc, "plan_address")
126+
}
127+
128+
configContents, err := json.Marshal(bc)
119129
if err != nil {
120130
return "", err
121131
}
@@ -389,6 +399,9 @@ func (c *client) Output(envName string) (map[string]map[string]interface{}, erro
389399
"output",
390400
"-json",
391401
}
402+
if c.model.BackendType == "http" {
403+
envName = defaultWorkspace
404+
}
392405
outputCmd, err := c.terraformCmd(outputArgs, []string{
393406
fmt.Sprintf("TF_WORKSPACE=%s", envName),
394407
})
@@ -537,6 +550,9 @@ func (c *client) ImportWithLegacyStorage() error {
537550
}
538551

539552
func (c *client) WorkspaceList() ([]string, error) {
553+
if c.model.BackendType == "http" {
554+
return []string{defaultWorkspace}, nil
555+
}
540556
cmd, err := c.terraformCmd([]string{
541557
"workspace",
542558
"list",
@@ -563,6 +579,9 @@ func (c *client) WorkspaceList() ([]string, error) {
563579
}
564580

565581
func (c *client) WorkspaceSelect(envName string) error {
582+
if c.model.BackendType == "http" {
583+
return nil
584+
}
566585
cmd, err := c.terraformCmd([]string{
567586
"workspace",
568587
"select",
@@ -580,6 +599,10 @@ func (c *client) WorkspaceSelect(envName string) error {
580599
}
581600

582601
func (c *client) WorkspaceNewIfNotExists(envName string) error {
602+
if c.model.BackendType == "http" {
603+
return nil
604+
}
605+
583606
workspaces, err := c.WorkspaceList()
584607

585608
if err != nil {
@@ -614,6 +637,9 @@ func (c *client) WorkspaceNewIfNotExists(envName string) error {
614637
}
615638

616639
func (c *client) WorkspaceNewFromExistingStateFile(envName string, localStateFilePath string) error {
640+
if c.model.BackendType == "http" {
641+
return nil
642+
}
617643
cmd, err := c.terraformCmd([]string{
618644
"workspace",
619645
"new",
@@ -644,7 +670,7 @@ func (c *client) WorkspaceNewFromExistingStateFile(envName string, localStateFil
644670
}
645671

646672
func (c *client) WorkspaceDelete(envName string) error {
647-
if envName == defaultWorkspace {
673+
if envName == defaultWorkspace || c.model.BackendType == "http" {
648674
return nil
649675
}
650676

@@ -667,7 +693,7 @@ func (c *client) WorkspaceDelete(envName string) error {
667693
}
668694

669695
func (c *client) WorkspaceDeleteWithForce(envName string) error {
670-
if envName == defaultWorkspace {
696+
if envName == defaultWorkspace || c.model.BackendType == "http" {
671697
return nil
672698
}
673699

@@ -691,6 +717,9 @@ func (c *client) WorkspaceDeleteWithForce(envName string) error {
691717
}
692718

693719
func (c *client) StatePull(envName string) ([]byte, error) {
720+
if c.model.BackendType == "http" {
721+
envName = defaultWorkspace
722+
}
694723
cmd, err := c.terraformCmd([]string{
695724
"state",
696725
"pull",
@@ -762,6 +791,7 @@ func (c *client) SavePlanToBackend(planEnvName string) error {
762791
}
763792
origSource := c.model.Source
764793
origLogger := c.logWriter
794+
origBackendConfig := c.model.BackendConfig
765795

766796
err = os.Chdir(tmpDir)
767797
if err != nil {
@@ -781,6 +811,7 @@ func (c *client) SavePlanToBackend(planEnvName string) error {
781811
os.Chdir(origDir)
782812
c.model.Source = origSource
783813
c.logWriter = origLogger
814+
c.model.BackendConfig = origBackendConfig
784815
}()
785816

786817
// The /tmp/tf-plan.log file can contain credentials, so we tell the user to
@@ -791,6 +822,15 @@ func (c *client) SavePlanToBackend(planEnvName string) error {
791822
return fmt.Errorf(errPrefix, logPath, err)
792823
}
793824

825+
// Override the backendConfig for HTTP type in order to target a different "-plan"
826+
if c.model.BackendType == "http" {
827+
planAddress, err := helper.PlanAddressForHTTPBackend(c.model.BackendConfig)
828+
if err != nil {
829+
return err
830+
}
831+
c.model.BackendConfig["address"] = planAddress
832+
}
833+
794834
err = c.InitWithBackend()
795835
if err != nil {
796836
return fmt.Errorf(errPrefix, logPath, err)
@@ -810,6 +850,46 @@ func (c *client) SavePlanToBackend(planEnvName string) error {
810850
}
811851

812852
func (c *client) GetPlanFromBackend(planEnvName string) error {
853+
tmpDir, err := ioutil.TempDir("", "tf-resource-plan")
854+
if err != nil {
855+
return err
856+
}
857+
defer os.RemoveAll(tmpDir)
858+
859+
// TODO: this stateful set and reset isn't great
860+
origDir, err := os.Getwd()
861+
if err != nil {
862+
return err
863+
}
864+
origSource := c.model.Source
865+
origBackendConfig := c.model.BackendConfig
866+
867+
err = os.Chdir(tmpDir)
868+
if err != nil {
869+
return err
870+
}
871+
c.model.Source = tmpDir
872+
defer func() {
873+
os.Chdir(origDir)
874+
c.model.Source = origSource
875+
c.model.BackendConfig = origBackendConfig
876+
}()
877+
// Override the backendConfig for HTTP type in order to target a different "-plan"
878+
if c.model.BackendType == "http" {
879+
planAddress, err := helper.PlanAddressForHTTPBackend(c.model.BackendConfig)
880+
if err != nil {
881+
return err
882+
}
883+
c.model.BackendConfig["address"] = planAddress
884+
}
885+
886+
// Run again the init as we change directory.
887+
// This should init to get the tfplan, then let the regular workflow get back on the previously init dir due to the defer func()
888+
err = c.InitWithBackend()
889+
if err != nil {
890+
return fmt.Errorf("init failed %s", err)
891+
}
892+
813893
if err := c.WorkspaceSelect(planEnvName); err != nil {
814894
return err
815895
}
@@ -843,6 +923,9 @@ func (c *client) SetModel(model models.Terraform) {
843923
}
844924

845925
func (c *client) resourceExists(tfID string, envName string) (bool, error) {
926+
if c.model.BackendType == "http" {
927+
envName = defaultWorkspace
928+
}
846929
cmd, err := c.terraformCmd([]string{
847930
"state",
848931
"list",

0 commit comments

Comments
 (0)