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 }