github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/config/daemonresolver_test.go (about)

     1  package config
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/mitchellh/cli"
    10  	"github.com/replicatedhq/libyaml"
    11  	"github.com/replicatedhq/ship/pkg/api"
    12  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon"
    13  	"github.com/replicatedhq/ship/pkg/lifecycle/daemon/headless"
    14  	"github.com/replicatedhq/ship/pkg/lifecycle/kustomize"
    15  	"github.com/replicatedhq/ship/pkg/lifecycle/render/config/resolve"
    16  	"github.com/replicatedhq/ship/pkg/state"
    17  	templates "github.com/replicatedhq/ship/pkg/templates"
    18  	"github.com/replicatedhq/ship/pkg/testing/logger"
    19  	"github.com/replicatedhq/ship/pkg/ui"
    20  	"github.com/spf13/afero"
    21  	"github.com/spf13/viper"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  type daemonResolverTestCase struct {
    26  	name         string
    27  	release      *api.Release
    28  	inputContext map[string]interface{}
    29  	posts        []func(t *testing.T) //nolint:structcheck
    30  	expect       func(t *testing.T, config map[string]interface{}, err error)
    31  }
    32  
    33  func TestDaemonResolver(t *testing.T) {
    34  	tests := []daemonResolverTestCase{
    35  		{
    36  			name: "test_resolve_noitems",
    37  			release: &api.Release{
    38  				Spec: api.Spec{
    39  					Lifecycle: api.Lifecycle{
    40  						V1: []api.Step{
    41  							{
    42  								Render: &api.Render{},
    43  							},
    44  						},
    45  					},
    46  					Config: api.Config{
    47  						V1: []libyaml.ConfigGroup{},
    48  					},
    49  				},
    50  			},
    51  			inputContext: map[string]interface{}{
    52  				"foo": "bar",
    53  			},
    54  			expect: func(t *testing.T, config map[string]interface{}, e error) {
    55  				req := require.New(t)
    56  				req.NoError(e)
    57  				actual, ok := config["foo"]
    58  				req.True(ok, "Expected to find key %s in resolved config", "foo")
    59  				req.Equal("bar", actual)
    60  			},
    61  		},
    62  		{
    63  			name: "test_resolve_timeout",
    64  			release: &api.Release{
    65  				Spec: api.Spec{
    66  					Lifecycle: api.Lifecycle{
    67  						V1: []api.Step{
    68  							{
    69  								Render: &api.Render{},
    70  							},
    71  						},
    72  					},
    73  					Config: api.Config{
    74  						V1: []libyaml.ConfigGroup{
    75  							{
    76  								Items: []*libyaml.ConfigItem{
    77  									{
    78  										Name: "k8s_namespace",
    79  										Type: "text",
    80  									},
    81  								},
    82  							},
    83  						},
    84  					},
    85  				},
    86  			},
    87  			inputContext: map[string]interface{}{},
    88  			expect: func(t *testing.T, i map[string]interface{}, e error) {
    89  				require.New(t).Error(e)
    90  			},
    91  		},
    92  		{
    93  			name: "test_single_item",
    94  			release: &api.Release{
    95  				Spec: api.Spec{
    96  					Lifecycle: api.Lifecycle{
    97  						V1: []api.Step{
    98  							{
    99  								Render: &api.Render{},
   100  							},
   101  						},
   102  					},
   103  					Config: api.Config{
   104  						V1: []libyaml.ConfigGroup{
   105  							{
   106  								Items: []*libyaml.ConfigItem{
   107  									{
   108  										Name: "k8s_namespace",
   109  										Type: "text",
   110  									},
   111  								},
   112  							},
   113  						},
   114  					},
   115  				},
   116  			},
   117  			inputContext: map[string]interface{}{},
   118  			posts: []func(t *testing.T){
   119  				func(t *testing.T) {
   120  					//http.Post("")
   121  
   122  				},
   123  			},
   124  			expect: func(t *testing.T, i map[string]interface{}, e error) {
   125  				// todo this should not fail
   126  				require.New(t).Error(e)
   127  			},
   128  		},
   129  	}
   130  	for _, test := range tests {
   131  		t.Run(test.name, func(t *testing.T) {
   132  			v := viper.New()
   133  
   134  			viper.Set("api-port", 0)
   135  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   136  			log := &logger.TestLogger{T: t}
   137  
   138  			daemon := &daemon.ShipDaemon{
   139  				Logger:       log,
   140  				WebUIFactory: daemon.WebUIFactoryFactory(log),
   141  				Viper:        v,
   142  				V1Routes: &daemon.V1Routes{
   143  					Logger: log,
   144  					Fs:     fs,
   145  					Viper:  v,
   146  
   147  					UI:             cli.NewMockUi(),
   148  					OpenWebConsole: func(ui cli.Ui, s string, b bool) error { return nil },
   149  				},
   150  				NavcycleRoutes: &daemon.NavcycleRoutes{
   151  					Kustomizer: &kustomize.Kustomizer{},
   152  					Shutdown:   make(chan interface{}),
   153  				},
   154  			}
   155  
   156  			daemonCtx, daemonCancelFunc := context.WithCancel(context.Background())
   157  			daemonCloseChan := make(chan struct{})
   158  
   159  			require.NoError(t, log.Log("starting daemon"))
   160  			go func(closeChan chan struct{}) {
   161  				_ = daemon.Serve(daemonCtx, test.release)
   162  				closeChan <- struct{}{}
   163  			}(daemonCloseChan)
   164  
   165  			resolver := &DaemonResolver{log, daemon}
   166  
   167  			resolveContext, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   168  			config, err := resolver.ResolveConfig(resolveContext, test.release, test.inputContext)
   169  
   170  			daemonCancelFunc()
   171  			cancel()
   172  
   173  			<-daemonCloseChan
   174  
   175  			test.expect(t, config, err)
   176  
   177  			//req.NoError(err)
   178  			//
   179  			//for key, expected := range test.expect {
   180  			//	actual, ok := config[key]
   181  			//	req.True(ok, "Expected to find key %s in resolved config", key)
   182  			//	req.Equal(expected, actual)
   183  			//}
   184  		})
   185  	}
   186  }
   187  
   188  func TestHeadlessResolver(t *testing.T) {
   189  	tests := []daemonResolverTestCase{
   190  		{
   191  			name: "test_resolve_noitems",
   192  			release: &api.Release{
   193  				Spec: api.Spec{
   194  					Lifecycle: api.Lifecycle{
   195  						V1: []api.Step{
   196  							{
   197  								Render: &api.Render{},
   198  							},
   199  						},
   200  					},
   201  					Config: api.Config{
   202  						V1: []libyaml.ConfigGroup{},
   203  					},
   204  				},
   205  			},
   206  			inputContext: map[string]interface{}{
   207  				"foo": "bar",
   208  			},
   209  			expect: func(t *testing.T, config map[string]interface{}, e error) {
   210  				req := require.New(t)
   211  				req.NoError(e)
   212  				actual, ok := config["foo"]
   213  				req.True(ok, "Expected to find key %s in resolved config", "foo")
   214  				req.Equal("bar", actual)
   215  			},
   216  		},
   217  		{
   218  			name: "test_config_item",
   219  			release: &api.Release{
   220  				Spec: api.Spec{
   221  					Lifecycle: api.Lifecycle{
   222  						V1: []api.Step{
   223  							{
   224  								Render: &api.Render{},
   225  							},
   226  						},
   227  					},
   228  					Config: api.Config{
   229  						V1: []libyaml.ConfigGroup{
   230  							{
   231  								Items: []*libyaml.ConfigItem{
   232  									{
   233  										Name:     "out",
   234  										Type:     "text",
   235  										ReadOnly: true,
   236  										Value:    `{{repl ConfigOption "foo"}}`,
   237  									},
   238  								},
   239  							},
   240  							{
   241  								Items: []*libyaml.ConfigItem{
   242  									{
   243  										Name:     "foo",
   244  										Type:     "text",
   245  										ReadOnly: false,
   246  										Value:    ``,
   247  									},
   248  								},
   249  							},
   250  						},
   251  					},
   252  				},
   253  			},
   254  			inputContext: map[string]interface{}{
   255  				"foo": "bar",
   256  			},
   257  			expect: func(t *testing.T, i map[string]interface{}, e error) {
   258  				req := require.New(t)
   259  				req.NoError(e)
   260  
   261  				expectContext := map[string]interface{}{
   262  					"foo": "bar",
   263  					"out": "bar",
   264  				}
   265  
   266  				req.Equal(expectContext, i)
   267  			},
   268  		},
   269  		{
   270  			name: "test_random_chain",
   271  			release: &api.Release{
   272  				Spec: api.Spec{
   273  					Lifecycle: api.Lifecycle{
   274  						V1: []api.Step{
   275  							{
   276  								Render: &api.Render{},
   277  							},
   278  						},
   279  					},
   280  					Config: api.Config{
   281  						V1: []libyaml.ConfigGroup{
   282  							{
   283  								Items: []*libyaml.ConfigItem{
   284  									{
   285  										Name:     "random_1",
   286  										Type:     "text",
   287  										ReadOnly: true,
   288  										Value:    `{{repl RandomString 32}}`,
   289  									},
   290  								},
   291  							},
   292  							{
   293  								Items: []*libyaml.ConfigItem{
   294  									{
   295  										Name:     "random_dependent",
   296  										Type:     "text",
   297  										ReadOnly: true,
   298  										Value:    `{{repl ConfigOption "random_1"}}`,
   299  									},
   300  								},
   301  							},
   302  						},
   303  					},
   304  				},
   305  			},
   306  			inputContext: map[string]interface{}{},
   307  			expect: func(t *testing.T, i map[string]interface{}, e error) {
   308  				req := require.New(t)
   309  				req.NoError(e)
   310  
   311  				random1, exists := i["random_1"]
   312  				req.True(exists, "'random_1' should exist")
   313  
   314  				randomDependent, exists := i["random_dependent"]
   315  				req.True(exists, "'random_dependent' should exist")
   316  
   317  				req.Equal(randomDependent, random1)
   318  			},
   319  		},
   320  		{
   321  			name: "test_deep_random_chain",
   322  			release: &api.Release{
   323  				Spec: api.Spec{
   324  					Lifecycle: api.Lifecycle{
   325  						V1: []api.Step{
   326  							{
   327  								Render: &api.Render{},
   328  							},
   329  						},
   330  					},
   331  					Config: api.Config{
   332  						V1: []libyaml.ConfigGroup{
   333  							{
   334  								Items: []*libyaml.ConfigItem{
   335  									{
   336  										Name:     "random_1",
   337  										Type:     "text",
   338  										ReadOnly: true,
   339  										Value:    `{{repl RandomString 32}}`,
   340  									},
   341  								},
   342  							},
   343  							{
   344  								Items: []*libyaml.ConfigItem{
   345  									{
   346  										Name:     "random_dependent",
   347  										Type:     "text",
   348  										ReadOnly: true,
   349  										Value:    `{{repl ConfigOption "random_1"}}`,
   350  									},
   351  								},
   352  							},
   353  							{
   354  								Items: []*libyaml.ConfigItem{
   355  									{
   356  										Name:     "random_dependent_child",
   357  										Type:     "text",
   358  										ReadOnly: true,
   359  										Value:    `{{repl ConfigOption "random_dependent"}}+{{repl ConfigOption "random_dependent"}}`,
   360  									},
   361  								},
   362  							},
   363  						},
   364  					},
   365  				},
   366  			},
   367  			inputContext: map[string]interface{}{},
   368  			expect: func(t *testing.T, i map[string]interface{}, e error) {
   369  				req := require.New(t)
   370  				req.NoError(e)
   371  
   372  				random1, exists := i["random_1"]
   373  				req.True(exists, "'random_1' should exist")
   374  
   375  				randomDependent, exists := i["random_dependent"]
   376  				req.True(exists, "'random_dependent' should exist")
   377  
   378  				randomDependentChild, exists := i["random_dependent_child"]
   379  				req.True(exists, "'random_dependent_child' should exist")
   380  
   381  				req.Equal(randomDependentChild, fmt.Sprintf("%s+%s", randomDependent, randomDependent), "constructed child should match")
   382  				req.Equal(randomDependent, random1, "raw child should match")
   383  			},
   384  		},
   385  		{
   386  			name: "ensure_randomstrings_differ",
   387  			release: &api.Release{
   388  				Spec: api.Spec{
   389  					Lifecycle: api.Lifecycle{
   390  						V1: []api.Step{
   391  							{
   392  								Render: &api.Render{},
   393  							},
   394  						},
   395  					},
   396  					Config: api.Config{
   397  						V1: []libyaml.ConfigGroup{
   398  							{
   399  								Items: []*libyaml.ConfigItem{
   400  									{
   401  										Name:     "random_1",
   402  										Type:     "text",
   403  										ReadOnly: true,
   404  										Value:    `{{repl RandomString 32}}`,
   405  									},
   406  								},
   407  							},
   408  							{
   409  								Items: []*libyaml.ConfigItem{
   410  									{
   411  										Name:     "random_dependent",
   412  										Type:     "text",
   413  										ReadOnly: true,
   414  										Value:    `{{repl ConfigOption "random_1"}}`,
   415  									},
   416  								},
   417  							},
   418  							{
   419  								Items: []*libyaml.ConfigItem{
   420  									{
   421  										Name:     "random_2",
   422  										Type:     "text",
   423  										ReadOnly: true,
   424  										Value:    `{{repl RandomString 32}}`,
   425  									},
   426  								},
   427  							},
   428  							{
   429  								Items: []*libyaml.ConfigItem{
   430  									{
   431  										Name:     "random_dependent_2",
   432  										Type:     "text",
   433  										ReadOnly: true,
   434  										Value:    `{{repl ConfigOption "random_2"}}`,
   435  									},
   436  								},
   437  							},
   438  						},
   439  					},
   440  				},
   441  			},
   442  			inputContext: map[string]interface{}{},
   443  			expect: func(t *testing.T, i map[string]interface{}, e error) {
   444  				req := require.New(t)
   445  				req.NoError(e)
   446  
   447  				random1, exists := i["random_1"]
   448  				req.True(exists, "'random_1' should exist")
   449  
   450  				randomDependent, exists := i["random_dependent"]
   451  				req.True(exists, "'random_dependent' should exist")
   452  
   453  				req.Equal(randomDependent, random1)
   454  
   455  				random2, exists := i["random_2"]
   456  				req.True(exists, "'random_2' should exist")
   457  
   458  				randomDependent2, exists := i["random_dependent_2"]
   459  				req.True(exists, "'random_dependent_2' should exist")
   460  
   461  				req.Equal(randomDependent2, random2)
   462  
   463  				req.NotEqual(random1, random2)
   464  			},
   465  		},
   466  	}
   467  	for _, test := range tests {
   468  		t.Run(test.name, func(t *testing.T) {
   469  			req := require.New(t)
   470  			v := viper.New()
   471  
   472  			viper.Set("api-port", 0)
   473  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   474  			log := &logger.TestLogger{T: t}
   475  
   476  			manager, err := state.NewDisposableManager(log, fs, v)
   477  			req.NoError(err)
   478  
   479  			builderBuilder := templates.NewBuilderBuilder(log, v, manager)
   480  
   481  			renderer := resolve.NewRenderer(log, v, builderBuilder)
   482  
   483  			headlessDaemon := headless.HeadlessDaemon{
   484  				StateManager:      manager,
   485  				Logger:            log,
   486  				UI:                ui.FromViper(v),
   487  				ConfigRenderer:    renderer,
   488  				FS:                fs,
   489  				ResolvedConfig:    test.inputContext,
   490  				YesApplyTerraform: false,
   491  			}
   492  
   493  			resolver := &DaemonResolver{log, &headlessDaemon}
   494  
   495  			resolveContext, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   496  
   497  			config, err := resolver.ResolveConfig(resolveContext, test.release, test.inputContext)
   498  
   499  			test.expect(t, config, err)
   500  			cancel()
   501  		})
   502  	}
   503  }