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 }