github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/daemon/headless/daemon_test.go (about)

     1  package headless
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"testing"
     8  
     9  	"github.com/mitchellh/cli"
    10  	"github.com/replicatedhq/libyaml"
    11  	"github.com/replicatedhq/ship/pkg/api"
    12  	"github.com/replicatedhq/ship/pkg/constants"
    13  	"github.com/replicatedhq/ship/pkg/lifecycle/render/config/resolve"
    14  	"github.com/replicatedhq/ship/pkg/state"
    15  	"github.com/replicatedhq/ship/pkg/templates"
    16  	"github.com/replicatedhq/ship/pkg/testing/logger"
    17  	"github.com/spf13/afero"
    18  	"github.com/spf13/viper"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  type TestHeadless struct {
    23  	Name           string
    24  	State          []byte
    25  	Release        *api.Release
    26  	ExpectedConfig map[string]interface{}
    27  	ExpectedError  bool
    28  }
    29  
    30  func TestHeadlessDaemon(t *testing.T) {
    31  	tests := []TestHeadless{
    32  		{
    33  			Name:  "empty",
    34  			State: []byte(`{}`),
    35  
    36  			Release: &api.Release{
    37  				Spec: api.Spec{
    38  					Config: api.Config{
    39  						V1: []libyaml.ConfigGroup{},
    40  					},
    41  				},
    42  			},
    43  			ExpectedConfig: map[string]interface{}{},
    44  			ExpectedError:  false,
    45  		},
    46  		{
    47  			Name:  "one group one item, not required, no value",
    48  			State: []byte(`{"v1":{"config":{"alpha":""}}}`),
    49  			Release: &api.Release{
    50  				Spec: api.Spec{
    51  					Config: api.Config{
    52  						V1: []libyaml.ConfigGroup{{
    53  							Name: "testing",
    54  							Items: []*libyaml.ConfigItem{
    55  								{
    56  									Name:     "alpha",
    57  									Value:    "",
    58  									Default:  "",
    59  									Required: false,
    60  									Hidden:   false,
    61  								},
    62  							},
    63  						}},
    64  					},
    65  				},
    66  			},
    67  			ExpectedConfig: map[string]interface{}{"alpha": ""},
    68  			ExpectedError:  false,
    69  		},
    70  		{
    71  			Name:  "one group one item, required, no value",
    72  			State: []byte(`{"v1":{"config":{"alpha":""}}}`),
    73  			Release: &api.Release{
    74  				Spec: api.Spec{
    75  					Config: api.Config{
    76  						V1: []libyaml.ConfigGroup{{
    77  							Name: "testing",
    78  							Items: []*libyaml.ConfigItem{
    79  								{
    80  									Name:     "alpha",
    81  									Value:    "",
    82  									Default:  "",
    83  									Required: true,
    84  									Hidden:   false,
    85  								},
    86  							},
    87  						}},
    88  					},
    89  				},
    90  			},
    91  			ExpectedConfig: map[string]interface{}{},
    92  			ExpectedError:  true,
    93  		},
    94  		{
    95  			Name:  "one group one item, required, value, hidden",
    96  			State: []byte(`{}`),
    97  			Release: &api.Release{
    98  				Spec: api.Spec{
    99  					Config: api.Config{
   100  						V1: []libyaml.ConfigGroup{{
   101  							Name: "testing",
   102  							Items: []*libyaml.ConfigItem{
   103  								{
   104  									Name:     "alpha",
   105  									Value:    "100",
   106  									Default:  "",
   107  									Required: true,
   108  									Hidden:   true,
   109  								},
   110  							},
   111  						}},
   112  					},
   113  				},
   114  			},
   115  			ExpectedConfig: map[string]interface{}{"alpha": "100"},
   116  			ExpectedError:  false,
   117  		},
   118  		{
   119  			Name:  "one group one item, not required, no value, hidden",
   120  			State: []byte(`{}`),
   121  			Release: &api.Release{
   122  				Spec: api.Spec{
   123  					Config: api.Config{
   124  						V1: []libyaml.ConfigGroup{{
   125  							Name: "testing",
   126  							Items: []*libyaml.ConfigItem{
   127  								{
   128  									Name:     "alpha",
   129  									Value:    "100",
   130  									Default:  "",
   131  									Required: false,
   132  									Hidden:   true,
   133  								},
   134  							},
   135  						}},
   136  					},
   137  				},
   138  			},
   139  			ExpectedConfig: map[string]interface{}{"alpha": "100"},
   140  			ExpectedError:  false,
   141  		},
   142  		{
   143  			Name:  "one group one item, required, no value, hidden",
   144  			State: []byte(`{}`),
   145  			Release: &api.Release{
   146  				Spec: api.Spec{
   147  					Config: api.Config{
   148  						V1: []libyaml.ConfigGroup{{
   149  							Name: "testing",
   150  							Items: []*libyaml.ConfigItem{
   151  								{
   152  									Name:     "alpha",
   153  									Value:    "",
   154  									Default:  "",
   155  									Required: true,
   156  									Hidden:   true,
   157  								},
   158  							},
   159  						}},
   160  					},
   161  				},
   162  			},
   163  			ExpectedConfig: map[string]interface{}{"alpha": ""},
   164  			ExpectedError:  false,
   165  		},
   166  		{
   167  			Name:  "one group one item, required, no value, not hidden",
   168  			State: []byte(`{}`),
   169  			Release: &api.Release{
   170  				Spec: api.Spec{
   171  					Config: api.Config{
   172  						V1: []libyaml.ConfigGroup{{
   173  							Name: "testing",
   174  							Items: []*libyaml.ConfigItem{
   175  								{
   176  									Name:     "alpha",
   177  									Value:    "",
   178  									Default:  "",
   179  									Required: true,
   180  									Hidden:   true,
   181  								},
   182  							},
   183  						}},
   184  					},
   185  				},
   186  			},
   187  			ExpectedConfig: map[string]interface{}{"alpha": ""},
   188  			ExpectedError:  false,
   189  		},
   190  		{
   191  			Name:  "one group one item, required, value, not hidden",
   192  			State: []byte(`{"v1":{"config":{"alpha": "100"}}}`),
   193  			Release: &api.Release{
   194  				Spec: api.Spec{
   195  					Config: api.Config{
   196  						V1: []libyaml.ConfigGroup{{
   197  							Name: "testing",
   198  							Items: []*libyaml.ConfigItem{
   199  								{
   200  									Name:     "alpha",
   201  									Value:    "100",
   202  									Default:  "",
   203  									Required: true,
   204  									Hidden:   false,
   205  								},
   206  							},
   207  						}},
   208  					},
   209  				},
   210  			},
   211  			ExpectedConfig: map[string]interface{}{"alpha": "100"},
   212  			ExpectedError:  false,
   213  		},
   214  		{
   215  			Name:  "one group one item, not required, no value, not hidden",
   216  			State: []byte(`{"v1":{"config":{"alpha": ""}}}`),
   217  			Release: &api.Release{
   218  				Spec: api.Spec{
   219  					Config: api.Config{
   220  						V1: []libyaml.ConfigGroup{{
   221  							Name: "testing",
   222  							Items: []*libyaml.ConfigItem{
   223  								{
   224  									Name:     "alpha",
   225  									Value:    "",
   226  									Default:  "",
   227  									Required: false,
   228  									Hidden:   false,
   229  								},
   230  							},
   231  						}},
   232  					},
   233  				},
   234  			},
   235  			ExpectedConfig: map[string]interface{}{"alpha": ""},
   236  			ExpectedError:  false,
   237  		},
   238  		{
   239  			Name:  "one group one item, required, value, hidden",
   240  			State: []byte(`{}`),
   241  			Release: &api.Release{
   242  				Spec: api.Spec{
   243  					Config: api.Config{
   244  						V1: []libyaml.ConfigGroup{{
   245  							Name: "testing",
   246  							Items: []*libyaml.ConfigItem{
   247  								{
   248  									Name:     "alpha",
   249  									Value:    "100",
   250  									Default:  "",
   251  									Required: true,
   252  									Hidden:   true,
   253  								},
   254  							},
   255  						}},
   256  					},
   257  				},
   258  			},
   259  			ExpectedConfig: map[string]interface{}{"alpha": "100"},
   260  			ExpectedError:  false,
   261  		},
   262  		{
   263  			Name:  "one group two items, neither required, neither present",
   264  			State: []byte(`{"v1":{"config":{"alpha": "", "beta": ""}}}`),
   265  			Release: &api.Release{
   266  				Spec: api.Spec{
   267  					Config: api.Config{
   268  						V1: []libyaml.ConfigGroup{{
   269  							Name: "testing",
   270  							Items: []*libyaml.ConfigItem{
   271  								{
   272  									Name:     "alpha",
   273  									Value:    "",
   274  									Default:  "",
   275  									Required: false,
   276  									Hidden:   false,
   277  								},
   278  								{
   279  									Name:     "beta",
   280  									Value:    "",
   281  									Default:  "",
   282  									Required: false,
   283  									Hidden:   false,
   284  								},
   285  							},
   286  						}},
   287  					},
   288  				},
   289  			},
   290  			ExpectedConfig: map[string]interface{}{"alpha": "", "beta": ""},
   291  			ExpectedError:  false,
   292  		},
   293  		{
   294  			Name:  "one group two items, both required, neither present",
   295  			State: []byte(`{"v1":{"config":{"alpha": "", "beta": ""}}}`),
   296  			Release: &api.Release{
   297  				Spec: api.Spec{
   298  					Config: api.Config{
   299  						V1: []libyaml.ConfigGroup{{
   300  							Name: "testing",
   301  							Items: []*libyaml.ConfigItem{
   302  								{
   303  									Name:     "alpha",
   304  									Value:    "",
   305  									Default:  "",
   306  									Required: true,
   307  									Hidden:   false,
   308  								},
   309  								{
   310  									Name:     "beta",
   311  									Value:    "",
   312  									Default:  "",
   313  									Required: true,
   314  									Hidden:   false,
   315  								},
   316  							},
   317  						}},
   318  					},
   319  				},
   320  			},
   321  			ExpectedConfig: map[string]interface{}{"alpha": "", "beta": ""},
   322  			ExpectedError:  true,
   323  		},
   324  		{
   325  			Name:  "one group two items, both required, one present",
   326  			State: []byte(`{"v1":{"config":{"alpha":"", "beta": ""}}}`),
   327  			Release: &api.Release{
   328  				Spec: api.Spec{
   329  					Config: api.Config{
   330  						V1: []libyaml.ConfigGroup{{
   331  							Name: "testing",
   332  							Items: []*libyaml.ConfigItem{
   333  								{
   334  									Name:     "alpha",
   335  									Value:    "100",
   336  									Default:  "",
   337  									Required: true,
   338  									Hidden:   false,
   339  								},
   340  								{
   341  									Name:     "beta",
   342  									Value:    "",
   343  									Default:  "",
   344  									Required: true,
   345  									Hidden:   false,
   346  								},
   347  							},
   348  						}},
   349  					},
   350  				},
   351  			},
   352  			ExpectedConfig: map[string]interface{}{"alpha": "", "beta": ""},
   353  			ExpectedError:  true,
   354  		},
   355  		{
   356  			Name:  "one group two items, both required, both present",
   357  			State: []byte(`{}`),
   358  			Release: &api.Release{
   359  				Spec: api.Spec{
   360  					Config: api.Config{
   361  						V1: []libyaml.ConfigGroup{{
   362  							Name: "testing",
   363  							Items: []*libyaml.ConfigItem{
   364  								{
   365  									Name:     "alpha",
   366  									Value:    "100",
   367  									Default:  "",
   368  									Required: true,
   369  									Hidden:   false,
   370  								},
   371  								{
   372  									Name:     "beta",
   373  									Value:    "200",
   374  									Default:  "",
   375  									Required: true,
   376  									Hidden:   false,
   377  								},
   378  							},
   379  						}},
   380  					},
   381  				},
   382  			},
   383  			ExpectedConfig: map[string]interface{}{"alpha": "100", "beta": "200"},
   384  			ExpectedError:  false,
   385  		},
   386  		{
   387  			Name:  "beta value resolves to alpha value",
   388  			State: []byte(`{"v1":{"config":{"alpha": "101"}}}`),
   389  			Release: &api.Release{
   390  				Spec: api.Spec{
   391  					Config: api.Config{
   392  						V1: []libyaml.ConfigGroup{{
   393  							Name: "testing",
   394  							Items: []*libyaml.ConfigItem{
   395  								{
   396  									Name:     "alpha",
   397  									Value:    "100",
   398  									Default:  "",
   399  									Required: false,
   400  									Hidden:   false,
   401  								},
   402  								{
   403  									Name:     "beta",
   404  									Value:    `{{repl ConfigOption "alpha" }}`,
   405  									Default:  "",
   406  									Required: false,
   407  									ReadOnly: true,
   408  								},
   409  							},
   410  						}},
   411  					},
   412  				},
   413  			},
   414  			ExpectedConfig: map[string]interface{}{"alpha": "101", "beta": "101"},
   415  			ExpectedError:  false,
   416  		},
   417  		{
   418  			Name:  "beta value resolves to alpha value when wrong beta value is presented",
   419  			State: []byte(`{"v1":{"config":{"alpha": "101", "beta":"abc"}}}`),
   420  			Release: &api.Release{
   421  				Spec: api.Spec{
   422  					Config: api.Config{
   423  						V1: []libyaml.ConfigGroup{{
   424  							Name: "testing",
   425  							Items: []*libyaml.ConfigItem{
   426  								{
   427  									Name:     "alpha",
   428  									Value:    "100",
   429  									Default:  "",
   430  									Required: false,
   431  									Hidden:   false,
   432  								},
   433  								{
   434  									Name:     "beta",
   435  									Value:    `{{repl ConfigOption "alpha" }}`,
   436  									Default:  "",
   437  									Required: false,
   438  									ReadOnly: true,
   439  								},
   440  							},
   441  						}},
   442  					},
   443  				},
   444  			},
   445  			ExpectedConfig: map[string]interface{}{"alpha": "101", "beta": "101"},
   446  			ExpectedError:  false,
   447  		},
   448  		{
   449  			Name:  "charlie value resolves to beta value resolves to alpha value",
   450  			State: []byte(`{"v1":{"config":{"alpha":"100"}}}`),
   451  			Release: &api.Release{
   452  				Spec: api.Spec{
   453  					Config: api.Config{
   454  						V1: []libyaml.ConfigGroup{{
   455  							Name: "testing",
   456  							Items: []*libyaml.ConfigItem{
   457  								{
   458  									Name:     "alpha",
   459  									Value:    "100",
   460  									Default:  "",
   461  									Required: false,
   462  									Hidden:   false,
   463  								},
   464  								{
   465  									Name:     "beta",
   466  									Value:    `{{repl ConfigOption "alpha" }}`,
   467  									Default:  "",
   468  									Required: false,
   469  									ReadOnly: true,
   470  								},
   471  								{
   472  									Name:     "charlie",
   473  									Value:    `{{repl ConfigOption "beta" }}`,
   474  									Default:  "",
   475  									Required: false,
   476  									ReadOnly: true,
   477  								},
   478  							},
   479  						}},
   480  					},
   481  				},
   482  			},
   483  			ExpectedConfig: map[string]interface{}{"alpha": "100", "beta": "100", "charlie": "100"},
   484  			ExpectedError:  false,
   485  		},
   486  		{
   487  			Name:  "multiple groups with multiple items",
   488  			State: []byte(`{}`),
   489  			Release: &api.Release{
   490  				Spec: api.Spec{
   491  					Config: api.Config{
   492  						V1: []libyaml.ConfigGroup{
   493  							{
   494  								Name: "testing",
   495  								Items: []*libyaml.ConfigItem{
   496  									{
   497  										Name:     "cluster info",
   498  										Value:    "",
   499  										Default:  "",
   500  										Required: true,
   501  										Hidden:   false,
   502  									},
   503  									{
   504  										Name:     "worker replicas",
   505  										Value:    "",
   506  										Default:  "",
   507  										Required: true,
   508  										Hidden:   false,
   509  									},
   510  								},
   511  							},
   512  							{
   513  								Name: "testing",
   514  								Items: []*libyaml.ConfigItem{
   515  									{
   516  										Name:     "semver",
   517  										Value:    "",
   518  										Default:  "",
   519  										Required: true,
   520  										Hidden:   false,
   521  									},
   522  								},
   523  							},
   524  							{
   525  								Name: "testing",
   526  								Items: []*libyaml.ConfigItem{
   527  									{
   528  										Name:     "alpha",
   529  										Value:    "hello world",
   530  										Default:  "",
   531  										Required: false,
   532  										Hidden:   false,
   533  									},
   534  									{
   535  										Name:     "beta",
   536  										Value:    `{{repl ConfigOption "alpha" }}`,
   537  										Default:  "",
   538  										Required: false,
   539  										ReadOnly: true,
   540  										Hidden:   true,
   541  									},
   542  									{
   543  										Name:     "charlie",
   544  										Value:    `{{repl ConfigOption "beta" }}`,
   545  										Default:  "",
   546  										Required: false,
   547  										ReadOnly: true,
   548  										Hidden:   false,
   549  									},
   550  								},
   551  							},
   552  						},
   553  					},
   554  				},
   555  			},
   556  			ExpectedConfig: map[string]interface{}{"alpha": "100", "beta": "100", "charlie": "100"},
   557  			ExpectedError:  true,
   558  		},
   559  		{
   560  			Name:  "beta value resolves to alpha default",
   561  			State: []byte(`{"v1":{"config":{}}}`),
   562  			Release: &api.Release{
   563  				Spec: api.Spec{
   564  					Config: api.Config{
   565  						V1: []libyaml.ConfigGroup{{
   566  							Name: "testing",
   567  							Items: []*libyaml.ConfigItem{
   568  								{
   569  									Name:     "alpha",
   570  									Value:    "",
   571  									Default:  "100",
   572  									Required: false,
   573  									Hidden:   false,
   574  								},
   575  								{
   576  									Name:     "beta",
   577  									Value:    `{{repl ConfigOption "alpha" }}`,
   578  									Default:  "",
   579  									Required: false,
   580  									ReadOnly: true,
   581  								},
   582  							},
   583  						}},
   584  					},
   585  				},
   586  			},
   587  			ExpectedConfig: map[string]interface{}{"alpha": "100", "beta": "100"},
   588  			ExpectedError:  false,
   589  		},
   590  	}
   591  
   592  	for _, test := range tests {
   593  		t.Run(test.Name, func(t *testing.T) {
   594  			req := require.New(t)
   595  
   596  			fakeFS := afero.Afero{Fs: afero.NewMemMapFs()}
   597  
   598  			err := fakeFS.WriteFile(constants.StatePath, test.State, 0666)
   599  			req.NoError(err)
   600  
   601  			v := viper.New()
   602  			testLogger := &logger.TestLogger{T: t}
   603  
   604  			builder := &templates.BuilderBuilder{
   605  				Logger: testLogger,
   606  				Viper:  v,
   607  			}
   608  
   609  			manager, err := state.NewDisposableManager(testLogger, fakeFS, v)
   610  			req.NoError(err)
   611  
   612  			resolver := &resolve.APIConfigRenderer{
   613  				Logger:         testLogger,
   614  				Viper:          v,
   615  				BuilderBuilder: builder,
   616  			}
   617  
   618  			daemon := &HeadlessDaemon{
   619  				StateManager:   manager,
   620  				Logger:         testLogger,
   621  				ConfigRenderer: resolver,
   622  				UI:             cli.NewMockUi(),
   623  			}
   624  
   625  			ctx := context.Background()
   626  
   627  			err = daemon.HeadlessResolve(ctx, test.Release)
   628  			if test.ExpectedError {
   629  				req.Error(err)
   630  			} else {
   631  				resolvedConfig := daemon.ResolvedConfig
   632  				req.Equal(test.ExpectedConfig, resolvedConfig)
   633  			}
   634  
   635  			err = manager.CommitState()
   636  			req.NoError(err)
   637  
   638  			updatedState, err := fakeFS.ReadFile(constants.StatePath)
   639  			req.NoError(err)
   640  
   641  			var indented bytes.Buffer
   642  			err = json.Indent(&indented, test.State, "", "  ")
   643  			req.NoError(err)
   644  
   645  			req.Equal(indented.Bytes(), updatedState)
   646  		})
   647  	}
   648  }