github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/terraform/terraformer_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	log2 "github.com/go-kit/kit/log"
    15  	"github.com/golang/mock/gomock"
    16  	"github.com/replicatedhq/ship/pkg/api"
    17  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon/daemontypes"
    18  	"github.com/replicatedhq/ship/pkg/state"
    19  	"github.com/replicatedhq/ship/pkg/test-mocks/daemon"
    20  	mocktf "github.com/replicatedhq/ship/pkg/test-mocks/tfplan"
    21  	"github.com/replicatedhq/ship/pkg/testing/logger"
    22  	"github.com/spf13/afero"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  type subproc struct {
    27  	Fail       bool
    28  	Stdout     string
    29  	Stderr     string
    30  	ExpectArgv []string
    31  }
    32  
    33  func TestTerraformer(t *testing.T) {
    34  	tests := []struct {
    35  		name              string
    36  		init              subproc
    37  		plan              subproc
    38  		apply             subproc
    39  		expectConfirmPlan bool
    40  		expectPlan        string
    41  		expectApply       bool
    42  		expectApplyOutput string
    43  		expectError       bool
    44  	}{
    45  		{
    46  			name: "init plan apply success",
    47  			init: subproc{
    48  				ExpectArgv: []string{"init", "-input=false"},
    49  			},
    50  			plan: subproc{
    51  				ExpectArgv: []string{"plan", "-input=false", "-out=plan.tfplan"},
    52  				Stdout:     fmt.Sprintf("state%sCreating 1 cluster%show to apply", tfSep, tfSep),
    53  			},
    54  			apply: subproc{
    55  				ExpectArgv: []string{"apply", "-input=false", "-auto-approve=true", "plan.tfplan"},
    56  				Stdout:     "Applied",
    57  			},
    58  			expectConfirmPlan: true,
    59  			expectPlan:        `<div class="term-container">Creating 1 cluster</div>`,
    60  			expectApply:       true,
    61  			expectApplyOutput: `<div class="term-container">Applied</div>`,
    62  		},
    63  		{
    64  			name: "init fail",
    65  			init: subproc{
    66  				Fail: true,
    67  			},
    68  			expectError: true,
    69  		},
    70  		{
    71  			name: "plan no changes",
    72  			plan: subproc{
    73  				Stdout: fmt.Sprintf("\n\n%s\n\n", tfNoChanges),
    74  			},
    75  		},
    76  		{
    77  			name: "apply fail",
    78  			plan: subproc{
    79  				Stdout: fmt.Sprintf("state%sCreating 1 cluster%show to apply", tfSep, tfSep),
    80  			},
    81  			expectConfirmPlan: true,
    82  			expectPlan:        `<div class="term-container">Creating 1 cluster</div>`,
    83  			apply: subproc{
    84  				Fail: true,
    85  			},
    86  		},
    87  	}
    88  	for _, test := range tests {
    89  		t.Run(test.name, func(t *testing.T) {
    90  			req := require.New(t)
    91  			mc := gomock.NewController(t)
    92  			mockDaemon := daemon.NewMockDaemon(mc)
    93  			mockPlanner := mocktf.NewMockPlanConfirmer(mc)
    94  			mockFS := afero.Afero{Fs: afero.NewMemMapFs()}
    95  			tf := &ForkTerraformer{
    96  				Logger:        &logger.TestLogger{T: t},
    97  				Daemon:        mockDaemon,
    98  				PlanConfirmer: mockPlanner,
    99  				Terraform: func(string) *exec.Cmd {
   100  					cmd := exec.Command(os.Args[0], "-test.run=TestMockTerraform")
   101  
   102  					init, err := json.Marshal(test.init)
   103  					if err != nil {
   104  						log.Fatal(err)
   105  					}
   106  
   107  					plan, err := json.Marshal(test.plan)
   108  					if err != nil {
   109  						log.Fatal(err)
   110  					}
   111  
   112  					apply, err := json.Marshal(test.apply)
   113  					if err != nil {
   114  						log.Fatal(err)
   115  					}
   116  
   117  					cmd.Env = append(os.Environ(),
   118  						"GOTEST_SUBPROCESS_MOCK=1",
   119  						"TERRAFORM_INIT="+string(init),
   120  						"TERRAFORM_PLAN="+string(plan),
   121  						"TERRAFORM_APPLY="+string(apply),
   122  					)
   123  					return cmd
   124  				},
   125  				FS: mockFS,
   126  				// skip this for now, will be tested separately
   127  				StateSaver: func(debug log2.Logger, fs afero.Afero, statemanager state.Manager, dir string) error {
   128  					return nil
   129  				},
   130  				StateRestorer: func(debug log2.Logger, fs afero.Afero, statemanager state.Manager, dir string) error {
   131  					return nil
   132  				},
   133  			}
   134  
   135  			if test.expectConfirmPlan {
   136  				mockPlanner.
   137  					EXPECT().
   138  					ConfirmPlan(gomock.Any(), test.expectPlan, gomock.Any(), gomock.Any()).
   139  					Return(test.expectApply, nil)
   140  			}
   141  
   142  			if test.expectApply {
   143  				mockDaemon.
   144  					EXPECT().
   145  					PushStreamStep(gomock.Any(), gomock.Any())
   146  
   147  				msg := daemontypes.Message{
   148  					Contents:    test.expectApplyOutput,
   149  					TrustedHTML: true,
   150  				}
   151  				if test.apply.Fail {
   152  					msg.Level = "error"
   153  				}
   154  
   155  				mockDaemon.
   156  					EXPECT().
   157  					PushMessageStep(gomock.Any(), msg, gomock.Any()).
   158  					Return()
   159  
   160  				if test.apply.Fail {
   161  					ch := make(chan bool, 1)
   162  					ch <- false
   163  					mockDaemon.
   164  						EXPECT().
   165  						TerraformConfirmedChan().
   166  						Return(ch)
   167  				} else {
   168  					ch := make(chan string, 1)
   169  					ch <- ""
   170  					mockDaemon.
   171  						EXPECT().
   172  						MessageConfirmedChan().
   173  						Return(ch)
   174  				}
   175  			}
   176  
   177  			err := tf.Execute(
   178  				context.Background(),
   179  				api.Release{},
   180  				api.Terraform{},
   181  				make(chan bool),
   182  			)
   183  
   184  			if test.expectError {
   185  				req.Error(err)
   186  			} else {
   187  				req.NoError(err)
   188  			}
   189  
   190  			mc.Finish()
   191  		})
   192  	}
   193  }
   194  
   195  func TestMockTerraform(t *testing.T) {
   196  	if os.Getenv("GOTEST_SUBPROCESS_MOCK") == "" {
   197  		return
   198  	}
   199  
   200  	var env string
   201  	switch os.Args[2] {
   202  	case "init":
   203  		env = "TERRAFORM_INIT"
   204  	case "plan":
   205  		env = "TERRAFORM_PLAN"
   206  	case "apply":
   207  		env = "TERRAFORM_APPLY"
   208  	}
   209  
   210  	var config subproc
   211  	err := json.Unmarshal([]byte(os.Getenv(env)), &config)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	if len(config.ExpectArgv) > 0 {
   217  		receivedArgs := os.Args[2:]
   218  		if !reflect.DeepEqual(receivedArgs, config.ExpectArgv) {
   219  			fmt.Fprintf(os.Stderr, "; FAIL expected args %+v got %+v", config.ExpectArgv, receivedArgs)
   220  			os.Exit(2)
   221  		}
   222  	}
   223  
   224  	fmt.Fprintf(os.Stdout, config.Stdout)
   225  	fmt.Fprintf(os.Stderr, config.Stderr)
   226  
   227  	if config.Fail {
   228  		os.Exit(1)
   229  	}
   230  
   231  	os.Exit(0)
   232  }
   233  
   234  func TestForkTerraformerApply(t *testing.T) {
   235  	req := require.New(t)
   236  	mc := gomock.NewController(t)
   237  	mockDaemon := daemon.NewMockDaemon(mc)
   238  	mockFS := afero.Afero{Fs: afero.NewMemMapFs()}
   239  	ft := ForkTerraformer{
   240  		Daemon: mockDaemon,
   241  		Logger: &logger.TestLogger{T: t},
   242  		Terraform: func(string) *exec.Cmd {
   243  			cmd := exec.Command(os.Args[0], "-test.run=TestMockTerraformApply")
   244  			cmd.Env = append(os.Environ(),
   245  				"GOTEST_SUBPROCESS_MOCK=1",
   246  			)
   247  			return cmd
   248  		},
   249  		FS: mockFS,
   250  	}
   251  
   252  	msgs := make(chan daemontypes.Message, 10)
   253  	output, err := ft.apply("fakedir", msgs)
   254  	req.NoError(err)
   255  	req.Equal(output, `<div class="term-container">stdout1stderr1stdout2</div>`)
   256  
   257  	req.EqualValues(daemontypes.Message{
   258  		Contents:    `<div class="term-container">terraform apply</div>`,
   259  		TrustedHTML: true,
   260  	}, <-msgs)
   261  
   262  	req.EqualValues(daemontypes.Message{
   263  		Contents:    `<div class="term-container">stdout1</div>`,
   264  		TrustedHTML: true,
   265  	}, <-msgs)
   266  
   267  	req.EqualValues(daemontypes.Message{
   268  		Contents:    `<div class="term-container">stdout1stderr1</div>`,
   269  		TrustedHTML: true,
   270  	}, <-msgs)
   271  
   272  	req.EqualValues(daemontypes.Message{
   273  		Contents:    `<div class="term-container">stdout1stderr1stdout2</div>`,
   274  		TrustedHTML: true,
   275  	}, <-msgs)
   276  }
   277  
   278  func TestMockTerraformApply(t *testing.T) {
   279  	if os.Getenv("GOTEST_SUBPROCESS_MOCK") == "" {
   280  		return
   281  	}
   282  
   283  	fmt.Fprintf(os.Stdout, "stdout1")
   284  	time.Sleep(time.Millisecond)
   285  	fmt.Fprintf(os.Stderr, "stderr1")
   286  	time.Sleep(time.Millisecond)
   287  	fmt.Fprintf(os.Stdout, "stdout2")
   288  
   289  	os.Exit(0)
   290  }