github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec2/parse_test.go (about)

     1  package jobspec2
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/jobspec"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func TestEquivalentToHCL1(t *testing.T) {
    15  	hclSpecDir := "../jobspec/test-fixtures/"
    16  	fis, err := ioutil.ReadDir(hclSpecDir)
    17  	require.NoError(t, err)
    18  
    19  	for _, fi := range fis {
    20  		name := fi.Name()
    21  
    22  		t.Run(name, func(t *testing.T) {
    23  			f, err := os.Open(hclSpecDir + name)
    24  			require.NoError(t, err)
    25  			defer f.Close()
    26  
    27  			job1, err := jobspec.Parse(f)
    28  			if err != nil {
    29  				t.Skip("file is not parsable in v1")
    30  			}
    31  
    32  			f.Seek(0, 0)
    33  
    34  			job2, err := Parse(name, f)
    35  			require.NoError(t, err)
    36  
    37  			require.Equal(t, job1, job2)
    38  		})
    39  	}
    40  }
    41  
    42  func TestEquivalentToHCL1_ComplexConfig(t *testing.T) {
    43  	name := "./test-fixtures/config-compatibility.hcl"
    44  	f, err := os.Open(name)
    45  	require.NoError(t, err)
    46  	defer f.Close()
    47  
    48  	job1, err := jobspec.Parse(f)
    49  	require.NoError(t, err)
    50  
    51  	f.Seek(0, 0)
    52  
    53  	job2, err := Parse(name, f)
    54  	require.NoError(t, err)
    55  
    56  	require.Equal(t, job1, job2)
    57  }
    58  
    59  func TestParse_VarsAndFunctions(t *testing.T) {
    60  	hcl := `
    61  variables {
    62    region_var = "default"
    63  }
    64  job "example" {
    65    datacenters = [for s in ["dc1", "dc2"] : upper(s)]
    66    region      = var.region_var
    67  }
    68  `
    69  
    70  	out, err := ParseWithConfig(&ParseConfig{
    71  		Path:    "input.hcl",
    72  		Body:    []byte(hcl),
    73  		ArgVars: []string{"region_var=aug"},
    74  		AllowFS: true,
    75  	})
    76  	require.NoError(t, err)
    77  
    78  	require.Equal(t, []string{"DC1", "DC2"}, out.Datacenters)
    79  	require.NotNil(t, out.Region)
    80  	require.Equal(t, "aug", *out.Region)
    81  }
    82  
    83  func TestParse_VariablesDefaultsAndSet(t *testing.T) {
    84  	hcl := `
    85  variables {
    86    region_var = "default_region"
    87  }
    88  
    89  variable "dc_var" {
    90    default = "default_dc"
    91  }
    92  
    93  job "example" {
    94    datacenters = [var.dc_var]
    95    region      = var.region_var
    96  }
    97  `
    98  
    99  	t.Run("defaults", func(t *testing.T) {
   100  		out, err := ParseWithConfig(&ParseConfig{
   101  			Path:    "input.hcl",
   102  			Body:    []byte(hcl),
   103  			AllowFS: true,
   104  		})
   105  		require.NoError(t, err)
   106  
   107  		require.Equal(t, []string{"default_dc"}, out.Datacenters)
   108  		require.NotNil(t, out.Region)
   109  		require.Equal(t, "default_region", *out.Region)
   110  	})
   111  
   112  	t.Run("set via -var args", func(t *testing.T) {
   113  		out, err := ParseWithConfig(&ParseConfig{
   114  			Path:    "input.hcl",
   115  			Body:    []byte(hcl),
   116  			ArgVars: []string{"dc_var=set_dc", "region_var=set_region"},
   117  			AllowFS: true,
   118  		})
   119  		require.NoError(t, err)
   120  
   121  		require.Equal(t, []string{"set_dc"}, out.Datacenters)
   122  		require.NotNil(t, out.Region)
   123  		require.Equal(t, "set_region", *out.Region)
   124  	})
   125  
   126  	t.Run("set via envvars", func(t *testing.T) {
   127  		out, err := ParseWithConfig(&ParseConfig{
   128  			Path: "input.hcl",
   129  			Body: []byte(hcl),
   130  			Envs: []string{
   131  				"NOMAD_VAR_dc_var=set_dc",
   132  				"NOMAD_VAR_region_var=set_region",
   133  			},
   134  			AllowFS: true,
   135  		})
   136  		require.NoError(t, err)
   137  
   138  		require.Equal(t, []string{"set_dc"}, out.Datacenters)
   139  		require.NotNil(t, out.Region)
   140  		require.Equal(t, "set_region", *out.Region)
   141  	})
   142  
   143  	t.Run("set via var-files", func(t *testing.T) {
   144  		varFile, err := ioutil.TempFile("", "")
   145  		require.NoError(t, err)
   146  		defer os.Remove(varFile.Name())
   147  
   148  		content := `dc_var = "set_dc"
   149  region_var = "set_region"`
   150  		_, err = varFile.WriteString(content)
   151  		require.NoError(t, err)
   152  
   153  		out, err := ParseWithConfig(&ParseConfig{
   154  			Path:     "input.hcl",
   155  			Body:     []byte(hcl),
   156  			VarFiles: []string{varFile.Name()},
   157  			AllowFS:  true,
   158  		})
   159  		require.NoError(t, err)
   160  
   161  		require.Equal(t, []string{"set_dc"}, out.Datacenters)
   162  		require.NotNil(t, out.Region)
   163  		require.Equal(t, "set_region", *out.Region)
   164  	})
   165  }
   166  
   167  // TestParse_UnknownVariables asserts that unknown variables are left intact for further processing
   168  func TestParse_UnknownVariables(t *testing.T) {
   169  	hcl := `
   170  variables {
   171    region_var = "default"
   172  }
   173  job "example" {
   174    datacenters = [for s in ["dc1", "dc2"] : upper(s)]
   175    region      = var.region_var
   176    meta {
   177      known_var   = "${var.region_var}"
   178      unknown_var = "${UNKNOWN}"
   179    }
   180  }
   181  `
   182  
   183  	out, err := ParseWithConfig(&ParseConfig{
   184  		Path:    "input.hcl",
   185  		Body:    []byte(hcl),
   186  		ArgVars: []string{"region_var=aug"},
   187  		AllowFS: true,
   188  	})
   189  	require.NoError(t, err)
   190  
   191  	meta := map[string]string{
   192  		"known_var":   "aug",
   193  		"unknown_var": "${UNKNOWN}",
   194  	}
   195  
   196  	require.Equal(t, meta, out.Meta)
   197  }
   198  
   199  // TestParse_UnsetVariables asserts that variables that have neither types nor
   200  // values return early instead of panicking.
   201  func TestParse_UnsetVariables(t *testing.T) {
   202  	hcl := `
   203  variable "region_var" {}
   204  job "example" {
   205    datacenters = [for s in ["dc1", "dc2"] : upper(s)]
   206    region      = var.region_var
   207  }
   208  `
   209  
   210  	_, err := ParseWithConfig(&ParseConfig{
   211  		Path:    "input.hcl",
   212  		Body:    []byte(hcl),
   213  		ArgVars: []string{},
   214  		AllowFS: true,
   215  	})
   216  
   217  	require.Error(t, err)
   218  	require.Contains(t, err.Error(), "Unset variable")
   219  }
   220  
   221  func TestParse_Locals(t *testing.T) {
   222  	hcl := `
   223  variables {
   224    region_var = "default_region"
   225  }
   226  
   227  locals {
   228    # literal local
   229    dc = "local_dc"
   230    # local that depends on a variable
   231    region = "${var.region_var}.example"
   232  }
   233  
   234  job "example" {
   235    datacenters = [local.dc]
   236    region      = local.region
   237  }
   238  `
   239  
   240  	t.Run("defaults", func(t *testing.T) {
   241  		out, err := ParseWithConfig(&ParseConfig{
   242  			Path:    "input.hcl",
   243  			Body:    []byte(hcl),
   244  			AllowFS: true,
   245  		})
   246  		require.NoError(t, err)
   247  
   248  		require.Equal(t, []string{"local_dc"}, out.Datacenters)
   249  		require.NotNil(t, out.Region)
   250  		require.Equal(t, "default_region.example", *out.Region)
   251  	})
   252  
   253  	t.Run("set via -var argments", func(t *testing.T) {
   254  		out, err := ParseWithConfig(&ParseConfig{
   255  			Path:    "input.hcl",
   256  			Body:    []byte(hcl),
   257  			ArgVars: []string{"region_var=set_region"},
   258  			AllowFS: true,
   259  		})
   260  		require.NoError(t, err)
   261  
   262  		require.Equal(t, []string{"local_dc"}, out.Datacenters)
   263  		require.NotNil(t, out.Region)
   264  		require.Equal(t, "set_region.example", *out.Region)
   265  	})
   266  }
   267  
   268  func TestParse_FileOperators(t *testing.T) {
   269  	hcl := `
   270  job "example" {
   271    region      = file("parse_test.go")
   272  }
   273  `
   274  
   275  	t.Run("enabled", func(t *testing.T) {
   276  		out, err := ParseWithConfig(&ParseConfig{
   277  			Path:    "input.hcl",
   278  			Body:    []byte(hcl),
   279  			ArgVars: nil,
   280  			AllowFS: true,
   281  		})
   282  		require.NoError(t, err)
   283  
   284  		expected, err := ioutil.ReadFile("parse_test.go")
   285  		require.NoError(t, err)
   286  
   287  		require.NotNil(t, out.Region)
   288  		require.Equal(t, string(expected), *out.Region)
   289  	})
   290  
   291  	t.Run("disabled", func(t *testing.T) {
   292  		_, err := ParseWithConfig(&ParseConfig{
   293  			Path:    "input.hcl",
   294  			Body:    []byte(hcl),
   295  			ArgVars: nil,
   296  			AllowFS: false,
   297  		})
   298  		require.Error(t, err)
   299  		require.Contains(t, err.Error(), "filesystem function disabled")
   300  	})
   301  }
   302  
   303  func TestParseDynamic(t *testing.T) {
   304  	hcl := `
   305  job "example" {
   306  
   307    dynamic "group" {
   308      for_each = [
   309        { name = "groupA", idx = 1 },
   310        { name = "groupB", idx = 2 },
   311        { name = "groupC", idx = 3 },
   312      ]
   313      labels   = [group.value.name]
   314  
   315      content {
   316        count = group.value.idx
   317  
   318        service {
   319          port = group.value.name
   320        }
   321  
   322        task "simple" {
   323          driver  = "raw_exec"
   324          config {
   325            command = group.value.name
   326          }
   327          meta {
   328            VERSION = group.value.idx
   329          }
   330          env {
   331            ID = format("id:%s", group.value.idx)
   332          }
   333          resources {
   334            cpu = group.value.idx
   335          }
   336        }
   337      }
   338    }
   339  }
   340  `
   341  	out, err := ParseWithConfig(&ParseConfig{
   342  		Path:    "input.hcl",
   343  		Body:    []byte(hcl),
   344  		ArgVars: nil,
   345  		AllowFS: false,
   346  	})
   347  	require.NoError(t, err)
   348  
   349  	require.Len(t, out.TaskGroups, 3)
   350  	require.Equal(t, "groupA", *out.TaskGroups[0].Name)
   351  	require.Equal(t, "groupB", *out.TaskGroups[1].Name)
   352  	require.Equal(t, "groupC", *out.TaskGroups[2].Name)
   353  	require.Equal(t, 1, *out.TaskGroups[0].Tasks[0].Resources.CPU)
   354  	require.Equal(t, "groupA", out.TaskGroups[0].Services[0].PortLabel)
   355  
   356  	// interpolation inside maps
   357  	require.Equal(t, "groupA", out.TaskGroups[0].Tasks[0].Config["command"])
   358  	require.Equal(t, "1", out.TaskGroups[0].Tasks[0].Meta["VERSION"])
   359  	require.Equal(t, "id:1", out.TaskGroups[0].Tasks[0].Env["ID"])
   360  	require.Equal(t, "id:2", out.TaskGroups[1].Tasks[0].Env["ID"])
   361  	require.Equal(t, "3", out.TaskGroups[2].Tasks[0].Meta["VERSION"])
   362  }
   363  
   364  func TestParse_InvalidScalingSyntax(t *testing.T) {
   365  	cases := []struct {
   366  		name        string
   367  		expectedErr string
   368  		hcl         string
   369  	}{
   370  		{
   371  			"valid",
   372  			"",
   373  			`
   374  job "example" {
   375    group "g1" {
   376      scaling {
   377        max  = 40
   378        type = "horizontal"
   379      }
   380  
   381      task "t1" {
   382        scaling "cpu" {
   383          max = 20
   384        }
   385        scaling "mem" {
   386          max = 15
   387        }
   388      }
   389    }
   390  }
   391  `,
   392  		},
   393  		{
   394  			"group missing max",
   395  			`argument "max" is required`,
   396  			`
   397  job "example" {
   398    group "g1" {
   399      scaling {
   400        #max  = 40
   401        type = "horizontal"
   402      }
   403  
   404      task "t1" {
   405        scaling "cpu" {
   406          max = 20
   407        }
   408        scaling "mem" {
   409          max = 15
   410        }
   411      }
   412    }
   413  }
   414  `,
   415  		},
   416  		{
   417  			"group invalid type",
   418  			`task group scaling policy had invalid type`,
   419  			`
   420  job "example" {
   421    group "g1" {
   422      scaling {
   423        max  = 40
   424        type = "invalid_type"
   425      }
   426  
   427      task "t1" {
   428        scaling "cpu" {
   429          max = 20
   430        }
   431        scaling "mem" {
   432          max = 15
   433        }
   434      }
   435    }
   436  }
   437  `,
   438  		},
   439  		{
   440  			"task invalid label",
   441  			`scaling policy name must be "cpu" or "mem"`,
   442  			`
   443  job "example" {
   444    group "g1" {
   445      scaling {
   446        max  = 40
   447        type = "horizontal"
   448      }
   449  
   450      task "t1" {
   451        scaling "not_cpu" {
   452          max = 20
   453        }
   454        scaling "mem" {
   455          max = 15
   456        }
   457      }
   458    }
   459  }
   460  `,
   461  		},
   462  		{
   463  			"task duplicate blocks",
   464  			`Duplicate scaling "cpu" block`,
   465  			`
   466  job "example" {
   467    group "g1" {
   468      scaling {
   469        max  = 40
   470        type = "horizontal"
   471      }
   472  
   473      task "t1" {
   474        scaling "cpu" {
   475          max = 20
   476        }
   477        scaling "cpu" {
   478          max = 15
   479        }
   480      }
   481    }
   482  }
   483  `,
   484  		},
   485  		{
   486  			"task invalid type",
   487  			`Invalid scaling policy type`,
   488  			`
   489  job "example" {
   490    group "g1" {
   491      scaling {
   492        max  = 40
   493        type = "horizontal"
   494      }
   495  
   496      task "t1" {
   497        scaling "cpu" {
   498          max  = 20
   499          type = "invalid"
   500        }
   501        scaling "mem" {
   502          max = 15
   503        }
   504      }
   505    }
   506  }
   507  `,
   508  		},
   509  	}
   510  
   511  	for _, c := range cases {
   512  		t.Run(c.name, func(t *testing.T) {
   513  			_, err := ParseWithConfig(&ParseConfig{
   514  				Path:    c.name + ".hcl",
   515  				Body:    []byte(c.hcl),
   516  				AllowFS: false,
   517  			})
   518  			if c.expectedErr == "" {
   519  				require.NoError(t, err)
   520  			} else {
   521  				require.Error(t, err)
   522  				require.Contains(t, err.Error(), c.expectedErr)
   523  			}
   524  		})
   525  	}
   526  }
   527  
   528  func TestParseJob_JobWithFunctionsAndLookups(t *testing.T) {
   529  	hcl := `
   530  variable "env" {
   531    description = "target environment for the job"
   532  }
   533  
   534  locals {
   535    environments = {
   536      prod    = { count = 20, dcs = ["prod-dc1", "prod-dc2"] },
   537      staging = { count = 3, dcs = ["dc1"] },
   538    }
   539  
   540    env = lookup(local.environments, var.env, { count = 0, dcs = [] })
   541  }
   542  
   543  job "job-webserver" {
   544    datacenters = local.env.dcs
   545    group "group-webserver" {
   546      count = local.env.count
   547  
   548      task "server" {
   549        driver = "docker"
   550  
   551        config {
   552          image = "hashicorp/http-echo"
   553          args  = ["-text", "Hello from ${var.env}"]
   554        }
   555      }
   556    }
   557  }
   558  `
   559  	cases := []struct {
   560  		env         string
   561  		expectedJob *api.Job
   562  	}{
   563  		{
   564  			"prod",
   565  			&api.Job{
   566  				ID:          stringToPtr("job-webserver"),
   567  				Name:        stringToPtr("job-webserver"),
   568  				Datacenters: []string{"prod-dc1", "prod-dc2"},
   569  				TaskGroups: []*api.TaskGroup{
   570  					{
   571  						Name:  stringToPtr("group-webserver"),
   572  						Count: intToPtr(20),
   573  
   574  						Tasks: []*api.Task{
   575  							{
   576  								Name:   "server",
   577  								Driver: "docker",
   578  
   579  								Config: map[string]interface{}{
   580  									"image": "hashicorp/http-echo",
   581  									"args":  []interface{}{"-text", "Hello from prod"},
   582  								},
   583  							},
   584  						},
   585  					},
   586  				},
   587  			},
   588  		},
   589  		{
   590  			"staging",
   591  			&api.Job{
   592  				ID:          stringToPtr("job-webserver"),
   593  				Name:        stringToPtr("job-webserver"),
   594  				Datacenters: []string{"dc1"},
   595  				TaskGroups: []*api.TaskGroup{
   596  					{
   597  						Name:  stringToPtr("group-webserver"),
   598  						Count: intToPtr(3),
   599  
   600  						Tasks: []*api.Task{
   601  							{
   602  								Name:   "server",
   603  								Driver: "docker",
   604  
   605  								Config: map[string]interface{}{
   606  									"image": "hashicorp/http-echo",
   607  									"args":  []interface{}{"-text", "Hello from staging"},
   608  								},
   609  							},
   610  						},
   611  					},
   612  				},
   613  			},
   614  		},
   615  		{
   616  			"unknown",
   617  			&api.Job{
   618  				ID:          stringToPtr("job-webserver"),
   619  				Name:        stringToPtr("job-webserver"),
   620  				Datacenters: []string{},
   621  				TaskGroups: []*api.TaskGroup{
   622  					{
   623  						Name:  stringToPtr("group-webserver"),
   624  						Count: intToPtr(0),
   625  
   626  						Tasks: []*api.Task{
   627  							{
   628  								Name:   "server",
   629  								Driver: "docker",
   630  
   631  								Config: map[string]interface{}{
   632  									"image": "hashicorp/http-echo",
   633  									"args":  []interface{}{"-text", "Hello from unknown"},
   634  								},
   635  							},
   636  						},
   637  					},
   638  				},
   639  			},
   640  		},
   641  	}
   642  
   643  	for _, c := range cases {
   644  		t.Run(c.env, func(t *testing.T) {
   645  			found, err := ParseWithConfig(&ParseConfig{
   646  				Path:    "example.hcl",
   647  				Body:    []byte(hcl),
   648  				AllowFS: false,
   649  				ArgVars: []string{"env=" + c.env},
   650  			})
   651  			require.NoError(t, err)
   652  			require.Equal(t, c.expectedJob, found)
   653  		})
   654  	}
   655  }
   656  
   657  func TestParse_TaskEnvs(t *testing.T) {
   658  	cases := []struct {
   659  		name       string
   660  		envSnippet string
   661  		expected   map[string]string
   662  	}{
   663  		{
   664  			"none",
   665  			``,
   666  			nil,
   667  		},
   668  		{
   669  			"block",
   670  			`
   671  env {
   672    key = "value"
   673  } `,
   674  			map[string]string{"key": "value"},
   675  		},
   676  		{
   677  			"attribute",
   678  			`
   679  env = {
   680    "key.dot"                = "val1"
   681    key_unquoted_without_dot = "val2"
   682  } `,
   683  			map[string]string{"key.dot": "val1", "key_unquoted_without_dot": "val2"},
   684  		},
   685  		{
   686  			"attribute_colons",
   687  			`env = {
   688    "key.dot" : "val1"
   689    key_unquoted_without_dot : "val2"
   690  } `,
   691  			map[string]string{"key.dot": "val1", "key_unquoted_without_dot": "val2"},
   692  		},
   693  		{
   694  			"attribute_empty",
   695  			`env = {}`,
   696  			map[string]string{},
   697  		},
   698  		{
   699  			"attribute_expression",
   700  			`env = {for k in ["a", "b"]: k => "val-${k}" }`,
   701  			map[string]string{"a": "val-a", "b": "val-b"},
   702  		},
   703  	}
   704  
   705  	for _, c := range cases {
   706  		t.Run(c.name, func(t *testing.T) {
   707  			hcl := `
   708  job "example" {
   709    group "group" {
   710      task "task" {
   711        driver = "docker"
   712        config {}
   713  
   714        ` + c.envSnippet + `
   715      }
   716    }
   717  }`
   718  
   719  			out, err := ParseWithConfig(&ParseConfig{
   720  				Path: "input.hcl",
   721  				Body: []byte(hcl),
   722  			})
   723  			require.NoError(t, err)
   724  
   725  			require.Equal(t, c.expected, out.TaskGroups[0].Tasks[0].Env)
   726  		})
   727  	}
   728  }
   729  
   730  func TestParse_TaskEnvs_Multiple(t *testing.T) {
   731  	hcl := `
   732  job "example" {
   733    group "group" {
   734      task "task" {
   735        driver = "docker"
   736        config {}
   737  
   738        env = {"a": "b"}
   739        env {
   740          c = "d"
   741        }
   742      }
   743    }
   744  }`
   745  
   746  	_, err := ParseWithConfig(&ParseConfig{
   747  		Path: "input.hcl",
   748  		Body: []byte(hcl),
   749  	})
   750  	require.Error(t, err)
   751  	require.Contains(t, err.Error(), "Duplicate env block")
   752  }
   753  
   754  func Test_TaskEnvs_Invalid(t *testing.T) {
   755  	cases := []struct {
   756  		name        string
   757  		envSnippet  string
   758  		expectedErr string
   759  	}{
   760  		{
   761  			"attr: invalid expression",
   762  			`env = { key = local.undefined_local }`,
   763  			`does not have an attribute named "undefined_local"`,
   764  		},
   765  		{
   766  			"block: invalid block expression",
   767  			`env {
   768    for k in ["a", "b"]: k => k
   769  }`,
   770  			"Invalid block definition",
   771  		},
   772  		{
   773  			"attr: not make sense",
   774  			`env = [ "a" ]`,
   775  			"Unsuitable value: map of string required",
   776  		},
   777  	}
   778  
   779  	for _, c := range cases {
   780  		t.Run(c.name, func(t *testing.T) {
   781  			hcl := `
   782  job "example" {
   783    group "group" {
   784      task "task" {
   785        driver = "docker"
   786        config {}
   787  
   788        ` + c.envSnippet + `
   789      }
   790    }
   791  }`
   792  			_, err := ParseWithConfig(&ParseConfig{
   793  				Path: "input.hcl",
   794  				Body: []byte(hcl),
   795  			})
   796  			require.Error(t, err)
   797  			require.Contains(t, err.Error(), c.expectedErr)
   798  		})
   799  	}
   800  }
   801  
   802  func TestParse_Meta_Alternatives(t *testing.T) {
   803  	hcl := ` job "example" {
   804    group "group" {
   805      task "task" {
   806        driver = "config"
   807        config {}
   808  
   809        meta {
   810          source = "task"
   811        }
   812      }
   813  
   814      meta {
   815        source = "group"
   816  
   817      }
   818    }
   819  
   820    meta {
   821      source = "job"
   822    }
   823  }
   824  `
   825  
   826  	asBlock, err := ParseWithConfig(&ParseConfig{
   827  		Path: "input.hcl",
   828  		Body: []byte(hcl),
   829  	})
   830  	require.NoError(t, err)
   831  
   832  	hclAsAttr := strings.ReplaceAll(hcl, "meta {", "meta = {")
   833  	require.Equal(t, 3, strings.Count(hclAsAttr, "meta = {"))
   834  
   835  	asAttr, err := ParseWithConfig(&ParseConfig{
   836  		Path: "input.hcl",
   837  		Body: []byte(hclAsAttr),
   838  	})
   839  	require.NoError(t, err)
   840  
   841  	require.Equal(t, asBlock, asAttr)
   842  	require.Equal(t, map[string]string{"source": "job"}, asBlock.Meta)
   843  	require.Equal(t, map[string]string{"source": "group"}, asBlock.TaskGroups[0].Meta)
   844  	require.Equal(t, map[string]string{"source": "task"}, asBlock.TaskGroups[0].Tasks[0].Meta)
   845  
   846  }