github.com/bigcommerce/nomad@v0.9.3-bc/helper/pluginutils/hclutils/util_test.go (about)

     1  package hclutils_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hashicorp/hcl2/hcldec"
     7  	"github.com/hashicorp/nomad/drivers/docker"
     8  	"github.com/hashicorp/nomad/helper/pluginutils/hclspecutils"
     9  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    10  	"github.com/hashicorp/nomad/plugins/drivers"
    11  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    12  	"github.com/kr/pretty"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/zclconf/go-cty/cty"
    15  )
    16  
    17  func TestParseHclInterface_Hcl(t *testing.T) {
    18  	dockerDriver := new(docker.Driver)
    19  	dockerSpec, err := dockerDriver.TaskConfigSchema()
    20  	require.NoError(t, err)
    21  	dockerDecSpec, diags := hclspecutils.Convert(dockerSpec)
    22  	require.False(t, diags.HasErrors())
    23  
    24  	vars := map[string]cty.Value{
    25  		"NOMAD_ALLOC_INDEX": cty.NumberIntVal(2),
    26  		"NOMAD_META_hello":  cty.StringVal("world"),
    27  	}
    28  
    29  	cases := []struct {
    30  		name         string
    31  		config       interface{}
    32  		spec         hcldec.Spec
    33  		vars         map[string]cty.Value
    34  		expected     interface{}
    35  		expectedType interface{}
    36  	}{
    37  		{
    38  			name: "single string attr",
    39  			config: hclutils.HclConfigToInterface(t, `
    40  			config {
    41  				image = "redis:3.2"
    42  			}`),
    43  			spec: dockerDecSpec,
    44  			expected: &docker.TaskConfig{
    45  				Image:   "redis:3.2",
    46  				Devices: []docker.DockerDevice{},
    47  				Mounts:  []docker.DockerMount{},
    48  			},
    49  			expectedType: &docker.TaskConfig{},
    50  		},
    51  		{
    52  			name: "single string attr json",
    53  			config: hclutils.JsonConfigToInterface(t, `
    54  						{
    55  							"Config": {
    56  								"image": "redis:3.2"
    57  			                }
    58  						}`),
    59  			spec: dockerDecSpec,
    60  			expected: &docker.TaskConfig{
    61  				Image:   "redis:3.2",
    62  				Devices: []docker.DockerDevice{},
    63  				Mounts:  []docker.DockerMount{},
    64  			},
    65  			expectedType: &docker.TaskConfig{},
    66  		},
    67  		{
    68  			name: "number attr",
    69  			config: hclutils.HclConfigToInterface(t, `
    70  						config {
    71  							image = "redis:3.2"
    72  							pids_limit  = 2
    73  						}`),
    74  			spec: dockerDecSpec,
    75  			expected: &docker.TaskConfig{
    76  				Image:     "redis:3.2",
    77  				PidsLimit: 2,
    78  				Devices:   []docker.DockerDevice{},
    79  				Mounts:    []docker.DockerMount{},
    80  			},
    81  			expectedType: &docker.TaskConfig{},
    82  		},
    83  		{
    84  			name: "number attr json",
    85  			config: hclutils.JsonConfigToInterface(t, `
    86  						{
    87  							"Config": {
    88  								"image": "redis:3.2",
    89  								"pids_limit": "2"
    90  			                }
    91  						}`),
    92  			spec: dockerDecSpec,
    93  			expected: &docker.TaskConfig{
    94  				Image:     "redis:3.2",
    95  				PidsLimit: 2,
    96  				Devices:   []docker.DockerDevice{},
    97  				Mounts:    []docker.DockerMount{},
    98  			},
    99  			expectedType: &docker.TaskConfig{},
   100  		},
   101  		{
   102  			name: "number attr interpolated",
   103  			config: hclutils.HclConfigToInterface(t, `
   104  						config {
   105  							image = "redis:3.2"
   106  							pids_limit  = "${2 + 2}"
   107  						}`),
   108  			spec: dockerDecSpec,
   109  			expected: &docker.TaskConfig{
   110  				Image:     "redis:3.2",
   111  				PidsLimit: 4,
   112  				Devices:   []docker.DockerDevice{},
   113  				Mounts:    []docker.DockerMount{},
   114  			},
   115  			expectedType: &docker.TaskConfig{},
   116  		},
   117  		{
   118  			name: "number attr interploated json",
   119  			config: hclutils.JsonConfigToInterface(t, `
   120  						{
   121  							"Config": {
   122  								"image": "redis:3.2",
   123  								"pids_limit": "${2 + 2}"
   124  			                }
   125  						}`),
   126  			spec: dockerDecSpec,
   127  			expected: &docker.TaskConfig{
   128  				Image:     "redis:3.2",
   129  				PidsLimit: 4,
   130  				Devices:   []docker.DockerDevice{},
   131  				Mounts:    []docker.DockerMount{},
   132  			},
   133  			expectedType: &docker.TaskConfig{},
   134  		},
   135  		{
   136  			name: "multi attr",
   137  			config: hclutils.HclConfigToInterface(t, `
   138  						config {
   139  							image = "redis:3.2"
   140  							args = ["foo", "bar"]
   141  						}`),
   142  			spec: dockerDecSpec,
   143  			expected: &docker.TaskConfig{
   144  				Image:   "redis:3.2",
   145  				Args:    []string{"foo", "bar"},
   146  				Devices: []docker.DockerDevice{},
   147  				Mounts:  []docker.DockerMount{},
   148  			},
   149  			expectedType: &docker.TaskConfig{},
   150  		},
   151  		{
   152  			name: "multi attr json",
   153  			config: hclutils.JsonConfigToInterface(t, `
   154  						{
   155  							"Config": {
   156  								"image": "redis:3.2",
   157  								"args": ["foo", "bar"]
   158  			                }
   159  						}`),
   160  			spec: dockerDecSpec,
   161  			expected: &docker.TaskConfig{
   162  				Image:   "redis:3.2",
   163  				Args:    []string{"foo", "bar"},
   164  				Devices: []docker.DockerDevice{},
   165  				Mounts:  []docker.DockerMount{},
   166  			},
   167  			expectedType: &docker.TaskConfig{},
   168  		},
   169  		{
   170  			name: "multi attr variables",
   171  			config: hclutils.HclConfigToInterface(t, `
   172  						config {
   173  							image = "redis:3.2"
   174  							args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"]
   175  							pids_limit = "${NOMAD_ALLOC_INDEX + 2}"
   176  						}`),
   177  			spec: dockerDecSpec,
   178  			vars: vars,
   179  			expected: &docker.TaskConfig{
   180  				Image:     "redis:3.2",
   181  				Args:      []string{"world", "2"},
   182  				PidsLimit: 4,
   183  				Devices:   []docker.DockerDevice{},
   184  				Mounts:    []docker.DockerMount{},
   185  			},
   186  			expectedType: &docker.TaskConfig{},
   187  		},
   188  		{
   189  			name: "multi attr variables json",
   190  			config: hclutils.JsonConfigToInterface(t, `
   191  						{
   192  							"Config": {
   193  								"image": "redis:3.2",
   194  								"args": ["foo", "bar"]
   195  			                }
   196  						}`),
   197  			spec: dockerDecSpec,
   198  			expected: &docker.TaskConfig{
   199  				Image:   "redis:3.2",
   200  				Args:    []string{"foo", "bar"},
   201  				Devices: []docker.DockerDevice{},
   202  				Mounts:  []docker.DockerMount{},
   203  			},
   204  			expectedType: &docker.TaskConfig{},
   205  		},
   206  		{
   207  			name: "port_map",
   208  			config: hclutils.HclConfigToInterface(t, `
   209  			config {
   210  				image = "redis:3.2"
   211  				port_map {
   212  					foo = 1234
   213  					bar = 5678
   214  				}
   215  			}`),
   216  			spec: dockerDecSpec,
   217  			expected: &docker.TaskConfig{
   218  				Image: "redis:3.2",
   219  				PortMap: map[string]int{
   220  					"foo": 1234,
   221  					"bar": 5678,
   222  				},
   223  				Devices: []docker.DockerDevice{},
   224  				Mounts:  []docker.DockerMount{},
   225  			},
   226  			expectedType: &docker.TaskConfig{},
   227  		},
   228  		{
   229  			name: "port_map json",
   230  			config: hclutils.JsonConfigToInterface(t, `
   231  							{
   232  								"Config": {
   233  									"image": "redis:3.2",
   234  									"port_map": [{
   235  										"foo": 1234,
   236  										"bar": 5678
   237  									}]
   238  				                }
   239  							}`),
   240  			spec: dockerDecSpec,
   241  			expected: &docker.TaskConfig{
   242  				Image: "redis:3.2",
   243  				PortMap: map[string]int{
   244  					"foo": 1234,
   245  					"bar": 5678,
   246  				},
   247  				Devices: []docker.DockerDevice{},
   248  				Mounts:  []docker.DockerMount{},
   249  			},
   250  			expectedType: &docker.TaskConfig{},
   251  		},
   252  		{
   253  			name: "devices",
   254  			config: hclutils.HclConfigToInterface(t, `
   255  						config {
   256  							image = "redis:3.2"
   257  							devices = [
   258  								{
   259  									host_path = "/dev/sda1"
   260  									container_path = "/dev/xvdc"
   261  									cgroup_permissions = "r"
   262  								},
   263  								{
   264  									host_path = "/dev/sda2"
   265  									container_path = "/dev/xvdd"
   266  								}
   267  							]
   268  						}`),
   269  			spec: dockerDecSpec,
   270  			expected: &docker.TaskConfig{
   271  				Image: "redis:3.2",
   272  				Devices: []docker.DockerDevice{
   273  					{
   274  						HostPath:          "/dev/sda1",
   275  						ContainerPath:     "/dev/xvdc",
   276  						CgroupPermissions: "r",
   277  					},
   278  					{
   279  						HostPath:      "/dev/sda2",
   280  						ContainerPath: "/dev/xvdd",
   281  					},
   282  				},
   283  				Mounts: []docker.DockerMount{},
   284  			},
   285  			expectedType: &docker.TaskConfig{},
   286  		},
   287  		{
   288  			name: "docker_logging",
   289  			config: hclutils.HclConfigToInterface(t, `
   290  				config {
   291  					image = "redis:3.2"
   292  					network_mode = "host"
   293  					dns_servers = ["169.254.1.1"]
   294  					logging {
   295  					    type = "syslog"
   296  					    config {
   297  						tag  = "driver-test"
   298  					    }
   299  					}
   300  				}`),
   301  			spec: dockerDecSpec,
   302  			expected: &docker.TaskConfig{
   303  				Image:       "redis:3.2",
   304  				NetworkMode: "host",
   305  				DNSServers:  []string{"169.254.1.1"},
   306  				Logging: docker.DockerLogging{
   307  					Type: "syslog",
   308  					Config: map[string]string{
   309  						"tag": "driver-test",
   310  					},
   311  				},
   312  				Devices: []docker.DockerDevice{},
   313  				Mounts:  []docker.DockerMount{},
   314  			},
   315  			expectedType: &docker.TaskConfig{},
   316  		},
   317  		{
   318  			name: "docker_json",
   319  			config: hclutils.JsonConfigToInterface(t, `
   320  					{
   321  						"Config": {
   322  							"image": "redis:3.2",
   323  							"devices": [
   324  								{
   325  									"host_path": "/dev/sda1",
   326  									"container_path": "/dev/xvdc",
   327  									"cgroup_permissions": "r"
   328  								},
   329  								{
   330  									"host_path": "/dev/sda2",
   331  									"container_path": "/dev/xvdd"
   332  								}
   333  							]
   334  				}
   335  					}`),
   336  			spec: dockerDecSpec,
   337  			expected: &docker.TaskConfig{
   338  				Image: "redis:3.2",
   339  				Devices: []docker.DockerDevice{
   340  					{
   341  						HostPath:          "/dev/sda1",
   342  						ContainerPath:     "/dev/xvdc",
   343  						CgroupPermissions: "r",
   344  					},
   345  					{
   346  						HostPath:      "/dev/sda2",
   347  						ContainerPath: "/dev/xvdd",
   348  					},
   349  				},
   350  				Mounts: []docker.DockerMount{},
   351  			},
   352  			expectedType: &docker.TaskConfig{},
   353  		},
   354  	}
   355  
   356  	for _, c := range cases {
   357  		c := c
   358  		t.Run(c.name, func(t *testing.T) {
   359  			t.Logf("Val: % #v", pretty.Formatter(c.config))
   360  			// Parse the interface
   361  			ctyValue, diag := hclutils.ParseHclInterface(c.config, c.spec, c.vars)
   362  			if diag.HasErrors() {
   363  				for _, err := range diag.Errs() {
   364  					t.Error(err)
   365  				}
   366  				t.FailNow()
   367  			}
   368  
   369  			// Test encoding
   370  			taskConfig := &drivers.TaskConfig{}
   371  			require.NoError(t, taskConfig.EncodeDriverConfig(ctyValue))
   372  
   373  			// Test decoding
   374  			require.NoError(t, taskConfig.DecodeDriverConfig(c.expectedType))
   375  
   376  			require.EqualValues(t, c.expected, c.expectedType)
   377  
   378  		})
   379  	}
   380  }
   381  
   382  func TestParseNullFields(t *testing.T) {
   383  	spec := hclspec.NewObject(map[string]*hclspec.Spec{
   384  		"array_field":   hclspec.NewAttr("array_field", "list(string)", false),
   385  		"string_field":  hclspec.NewAttr("string_field", "string", false),
   386  		"boolean_field": hclspec.NewAttr("boolean_field", "bool", false),
   387  		"number_field":  hclspec.NewAttr("number_field", "number", false),
   388  		"block_field": hclspec.NewBlock("block_field", false, hclspec.NewObject((map[string]*hclspec.Spec{
   389  			"f": hclspec.NewAttr("f", "string", true),
   390  		}))),
   391  		"block_list_field": hclspec.NewBlockList("block_list_field", hclspec.NewObject((map[string]*hclspec.Spec{
   392  			"f": hclspec.NewAttr("f", "string", true),
   393  		}))),
   394  	})
   395  
   396  	type Sub struct {
   397  		F string `codec:"f"`
   398  	}
   399  
   400  	type TaskConfig struct {
   401  		Array     []string `codec:"array_field"`
   402  		String    string   `codec:"string_field"`
   403  		Boolean   bool     `codec:"boolean_field"`
   404  		Number    int64    `codec:"number_field"`
   405  		Block     Sub      `codec:"block_field"`
   406  		BlockList []Sub    `codec:"block_list_field"`
   407  	}
   408  
   409  	cases := []struct {
   410  		name     string
   411  		json     string
   412  		expected TaskConfig
   413  	}{
   414  		{
   415  			"omitted fields",
   416  			`{"Config": {}}`,
   417  			TaskConfig{BlockList: []Sub{}},
   418  		},
   419  		{
   420  			"explicitly nil",
   421  			`{"Config": {
   422                              "array_field": null,
   423                              "string_field": null,
   424  			    "boolean_field": null,
   425                              "number_field": null,
   426                              "block_field": null,
   427                              "block_list_field": null}}`,
   428  			TaskConfig{BlockList: []Sub{}},
   429  		},
   430  		{
   431  			// for sanity checking that the fields are actually set
   432  			"explicitly set to not null",
   433  			`{"Config": {
   434                              "array_field": ["a"],
   435                              "string_field": "a",
   436                              "boolean_field": true,
   437                              "number_field": 5,
   438                              "block_field": [{"f": "a"}],
   439                              "block_list_field": [{"f": "a"}, {"f": "b"}]}}`,
   440  			TaskConfig{
   441  				Array:     []string{"a"},
   442  				String:    "a",
   443  				Boolean:   true,
   444  				Number:    5,
   445  				Block:     Sub{"a"},
   446  				BlockList: []Sub{{"a"}, {"b"}},
   447  			},
   448  		},
   449  	}
   450  
   451  	parser := hclutils.NewConfigParser(spec)
   452  	for _, c := range cases {
   453  		t.Run(c.name, func(t *testing.T) {
   454  			var tc TaskConfig
   455  			parser.ParseJson(t, c.json, &tc)
   456  
   457  			require.EqualValues(t, c.expected, tc)
   458  		})
   459  	}
   460  }