github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/distgo/pkg/slsspec/sls_test.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package slsspec_test
    16  
    17  import (
    18  	"fmt"
    19  	"io/ioutil"
    20  	"os"
    21  	"path"
    22  	"regexp"
    23  	"testing"
    24  
    25  	"github.com/nmiyake/pkg/dirs"
    26  	"github.com/palantir/pkg/matcher"
    27  	"github.com/palantir/pkg/specdir"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/palantir/godel/apps/distgo/pkg/slsspec"
    32  )
    33  
    34  func TestSLSSpecCreateDirectoryStructureFailMissingValues(t *testing.T) {
    35  	tmp, cleanup, err := dirs.TempDir("", "")
    36  	defer cleanup()
    37  	require.NoError(t, err)
    38  
    39  	spec := slsspec.New()
    40  	err = spec.CreateDirectoryStructure(tmp, nil, false)
    41  	require.Error(t, err)
    42  
    43  	assert.Regexp(t, regexp.MustCompile("required template keys were missing: got map[], missing [ServiceName ServiceVersion]"), err.Error())
    44  }
    45  
    46  func TestSLSSpecCreateDirectoryStructureFail(t *testing.T) {
    47  	tmp, cleanup, err := dirs.TempDir("", "")
    48  	defer cleanup()
    49  	require.NoError(t, err)
    50  
    51  	spec := slsspec.New()
    52  	err = spec.CreateDirectoryStructure(tmp, specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}, false)
    53  	require.Error(t, err)
    54  
    55  	assert.Regexp(t, regexp.MustCompile(`.+ is not a path to foo-1.0.0`), err.Error())
    56  }
    57  
    58  func TestSLSSpecCreateDirectoryStructure(t *testing.T) {
    59  	tmp, cleanup, err := dirs.TempDir("", "")
    60  	defer cleanup()
    61  	require.NoError(t, err)
    62  
    63  	rootDir := path.Join(tmp, "foo-1.0.0")
    64  	err = os.Mkdir(rootDir, 0755)
    65  	require.NoError(t, err)
    66  
    67  	spec := slsspec.New()
    68  	values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
    69  	err = spec.CreateDirectoryStructure(rootDir, values, false)
    70  	require.NoError(t, err)
    71  	err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte("test"), 0644)
    72  	require.NoError(t, err)
    73  	err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
    74  	require.NoError(t, err)
    75  
    76  	specDir, err := specdir.New(rootDir, spec, values, specdir.Validate)
    77  	require.NoError(t, err)
    78  
    79  	assert.Equal(t, path.Join(rootDir, "deployment"), specDir.Path(slsspec.Deployment))
    80  	assert.Equal(t, path.Join(rootDir, "deployment", "manifest.yml"), specDir.Path(slsspec.Manifest))
    81  	assert.Equal(t, path.Join(rootDir, "service"), specDir.Path(slsspec.Service))
    82  	assert.Equal(t, path.Join(rootDir, "service", "bin"), specDir.Path(slsspec.ServiceBin))
    83  	assert.Equal(t, path.Join(rootDir, "service", "bin", "init.sh"), specDir.Path(slsspec.InitSh))
    84  }
    85  
    86  func TestValidate(t *testing.T) {
    87  	const invalidYML = `[}`
    88  
    89  	tmp, cleanup, err := dirs.TempDir("", "")
    90  	defer cleanup()
    91  	require.NoError(t, err)
    92  
    93  	for i, currCase := range []struct {
    94  		name      string
    95  		createDir func(rootDir string)
    96  		skipYML   matcher.Matcher
    97  	}{
    98  		{
    99  			name: "valid structure",
   100  			createDir: func(rootDir string) {
   101  				spec := slsspec.New()
   102  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   103  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   104  				require.NoError(t, err)
   105  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   106  				require.NoError(t, err)
   107  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte("key: value"), 0644)
   108  				require.NoError(t, err)
   109  			},
   110  		},
   111  		{
   112  			name: "complex YML is valid",
   113  			createDir: func(rootDir string) {
   114  				spec := slsspec.New()
   115  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   116  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   117  				require.NoError(t, err)
   118  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   119  				require.NoError(t, err)
   120  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte(`
   121  # Comments in YAML look like this.
   122  
   123  ################
   124  # SCALAR TYPES #
   125  ################
   126  
   127  # Our root object (which continues for the entire document) will be a map,
   128  # which is equivalent to a dictionary, hash or object in other languages.
   129  key: value
   130  another_key: Another value goes here.
   131  a_number_value: 100
   132  scientific_notation: 1e+12
   133  # The number 1 will be interpreted as a number, not a boolean. if you want
   134  # it to be intepreted as a boolean, use true
   135  boolean: true
   136  null_value: null
   137  key with spaces: value
   138  # Notice that strings don't need to be quoted. However, they can be.
   139  however: "A string, enclosed in quotes."
   140  "Keys can be quoted too.": "Useful if you want to put a ':' in your key."
   141  
   142  # Multiple-line strings can be written either as a 'literal block' (using |),
   143  # or a 'folded block' (using '>').
   144  literal_block: |
   145      This entire block of text will be the value of the 'literal_block' key,
   146      with line breaks being preserved.
   147  
   148      The literal continues until de-dented, and the leading indentation is
   149      stripped.
   150  
   151          Any lines that are 'more-indented' keep the rest of their indentation -
   152          these lines will be indented by 4 spaces.
   153  folded_style: >
   154      This entire block of text will be the value of 'folded_style', but this
   155      time, all newlines will be replaced with a single space.
   156  
   157      Blank lines, like above, are converted to a newline character.
   158  
   159          'More-indented' lines keep their newlines, too -
   160          this text will appear over two lines.
   161  
   162  ####################
   163  # COLLECTION TYPES #
   164  ####################
   165  
   166  # Nesting is achieved by indentation.
   167  a_nested_map:
   168      key: value
   169      another_key: Another Value
   170      another_nested_map:
   171          hello: hello
   172  
   173  # Maps don't have to have string keys.
   174  0.25: a float key
   175  
   176  # Keys can also be complex, like multi-line objects
   177  # We use ? followed by a space to indicate the start of a complex key.
   178  ? |
   179      This is a key
   180      that has multiple lines
   181  : and this is its value
   182  
   183  # Sequences (equivalent to lists or arrays) look like this:
   184  a_sequence:
   185      - Item 1
   186      - Item 2
   187      - 0.5 # sequences can contain disparate types.
   188      - Item 4
   189      - key: value
   190        another_key: another_value
   191      -
   192          - This is a sequence
   193          - inside another sequence
   194  
   195  # Since YAML is a superset of JSON, you can also write JSON-style maps and
   196  # sequences:
   197  json_map: {"key": "value"}
   198  json_seq: [3, 2, 1, "takeoff"]
   199  
   200  #######################
   201  # EXTRA YAML FEATURES #
   202  #######################
   203  
   204  # YAML also has a handy feature called 'anchors', which let you easily duplicate
   205  # content across your document. Both of these keys will have the same value:
   206  anchored_content: &anchor_name This string will appear as the value of two keys.
   207  other_anchor: *anchor_name
   208  
   209  # Anchors can be used to duplicate/inherit properties
   210  base: &base
   211      name: Everyone has same name
   212  
   213  foo: &foo
   214      <<: *base
   215      age: 10
   216  
   217  bar: &bar
   218      <<: *base
   219      age: 20
   220  
   221  # foo and bar would also have name: Everyone has same name
   222  
   223  # YAML also has tags, which you can use to explicitly declare types.
   224  explicit_string: !!str 0.5
   225  # Some parsers implement language specific tags, like this one for Python's
   226  # complex number type.
   227  python_complex_number: !!python/complex 1+2j
   228  
   229  ####################
   230  # EXTRA YAML TYPES #
   231  ####################
   232  
   233  # Strings and numbers aren't the only scalars that YAML can understand.
   234  # ISO-formatted date and datetime literals are also parsed.
   235  datetime: 2001-12-15T02:59:43.1Z
   236  datetime_with_spaces: 2001-12-14 21:59:43.10 -5
   237  date: 2002-12-14
   238  
   239  # The !!binary tag indicates that a string is actually a base64-encoded
   240  # representation of a binary blob.
   241  gif_file: !!binary |
   242      R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
   243      OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
   244      +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
   245      AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
   246  
   247  # YAML also has a set type, which looks like this:
   248  set:
   249      ? item1
   250      ? item2
   251      ? item3
   252  
   253  # Like Python, sets are just maps with null values; the above is equivalent to:
   254  set2:
   255      item1: null
   256      item2: null
   257      item3: null`), 0644)
   258  				require.NoError(t, err)
   259  			},
   260  		},
   261  		{
   262  			name: "invalid yml file OK if excluded by matcher",
   263  			createDir: func(rootDir string) {
   264  				spec := slsspec.New()
   265  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   266  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   267  				require.NoError(t, err)
   268  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   269  				require.NoError(t, err)
   270  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte("key: value"), 0644)
   271  				require.NoError(t, err)
   272  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "invalid.yaml"), []byte(invalidYML), 0644)
   273  				require.NoError(t, err)
   274  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "valid.yml"), []byte("key: value"), 0644)
   275  				require.NoError(t, err)
   276  			},
   277  			skipYML: matcher.Path("service"),
   278  		},
   279  	} {
   280  		currTmp, err := ioutil.TempDir(tmp, "")
   281  		require.NoError(t, err)
   282  
   283  		rootDir := path.Join(currTmp, "foo-1.0.0")
   284  		err = os.Mkdir(rootDir, 0755)
   285  		require.NoError(t, err)
   286  
   287  		currCase.createDir(rootDir)
   288  		err = slsspec.Validate(rootDir, slsspec.TemplateValues("foo", "1.0.0"), currCase.skipYML)
   289  		assert.NoError(t, err, "Case %d: %s", i, currCase.name)
   290  	}
   291  }
   292  
   293  func TestValidateFail(t *testing.T) {
   294  	const invalidYML = `[}`
   295  
   296  	tmp, cleanup, err := dirs.TempDir("", "")
   297  	defer cleanup()
   298  	require.NoError(t, err)
   299  
   300  	for i, currCase := range []struct {
   301  		name      string
   302  		createDir func(rootDir string)
   303  		want      string
   304  	}{
   305  		{
   306  			name: "missing manifest.yml",
   307  			createDir: func(rootDir string) {
   308  				spec := slsspec.New()
   309  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   310  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   311  				require.NoError(t, err)
   312  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   313  				require.NoError(t, err)
   314  			},
   315  			want: "foo-1.0.0/deployment/manifest.yml does not exist",
   316  		},
   317  		{
   318  			name: "missing init.sh",
   319  			createDir: func(rootDir string) {
   320  				spec := slsspec.New()
   321  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   322  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   323  				require.NoError(t, err)
   324  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte("test"), 0644)
   325  				require.NoError(t, err)
   326  			},
   327  			want: "foo-1.0.0/service/bin/init.sh does not exist",
   328  		},
   329  		{
   330  			name: "invalid manifest.yml",
   331  			createDir: func(rootDir string) {
   332  				spec := slsspec.New()
   333  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   334  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   335  				require.NoError(t, err)
   336  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   337  				require.NoError(t, err)
   338  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte(invalidYML), 0644)
   339  				require.NoError(t, err)
   340  			},
   341  			want: "invalid YML files: [foo-1.0.0/deployment/manifest.yml]\nIf these files are known to be correct, exclude them from validation using the SLS YML validation exclude matcher.",
   342  		},
   343  		{
   344  			name: "multiple invalid yml files",
   345  			createDir: func(rootDir string) {
   346  				spec := slsspec.New()
   347  				values := specdir.TemplateValues{"ServiceName": "foo", "ServiceVersion": "1.0.0"}
   348  				err = spec.CreateDirectoryStructure(rootDir, values, false)
   349  				require.NoError(t, err)
   350  				err = ioutil.WriteFile(path.Join(rootDir, "service", "bin", "init.sh"), []byte("test"), 0644)
   351  				require.NoError(t, err)
   352  				err = ioutil.WriteFile(path.Join(rootDir, "deployment", "manifest.yml"), []byte(invalidYML), 0644)
   353  				require.NoError(t, err)
   354  				err = os.MkdirAll(path.Join(rootDir, "var"), 0755)
   355  				require.NoError(t, err)
   356  				err = ioutil.WriteFile(path.Join(rootDir, "var", "invalid.yaml"), []byte(invalidYML), 0644)
   357  				require.NoError(t, err)
   358  				err = ioutil.WriteFile(path.Join(rootDir, "var", "valid.yml"), []byte("key: value"), 0644)
   359  				require.NoError(t, err)
   360  			},
   361  			want: "invalid YML files: [foo-1.0.0/deployment/manifest.yml foo-1.0.0/var/invalid.yaml]\nIf these files are known to be correct, exclude them from validation using the SLS YML validation exclude matcher.",
   362  		},
   363  	} {
   364  		currTmp, err := ioutil.TempDir(tmp, "")
   365  		require.NoError(t, err)
   366  
   367  		rootDir := path.Join(currTmp, "foo-1.0.0")
   368  		err = os.Mkdir(rootDir, 0755)
   369  		require.NoError(t, err)
   370  
   371  		currCase.createDir(rootDir)
   372  		err = slsspec.Validate(rootDir, slsspec.TemplateValues("foo", "1.0.0"), nil)
   373  		require.Error(t, err, fmt.Sprintf("Case %d: %s", i, currCase.name))
   374  
   375  		assert.EqualError(t, err, currCase.want, "Case %d: %s", i, currCase.name)
   376  	}
   377  }