def test_allow_most_keys_to_be_duplicates_for_overrides(self): yaml_config = """ namespace: prod stacks: - name: vpc class_path: blueprints.VPC variables: CIDR: 192.168.1.0/24 CIDR: 192.168.2.0/24 """ doc = parse(yaml_config) self.assertEqual( doc["stacks"][0]["variables"]["CIDR"], "192.168.2.0/24" ) yaml_config = """ default_variables: &default_variables CIDR: 192.168.1.0/24 namespace: prod stacks: - name: vpc class_path: blueprints.VPC variables: << : *default_variables CIDR: 192.168.2.0/24 """ doc = parse(yaml_config) self.assertEqual( doc["stacks"][0]["variables"]["CIDR"], "192.168.2.0/24" )
def test_raise_constructor_error_on_keyword_duplicate_key(self): """Some keys should never have a duplicate sibling. For example we treat `class_path` as a special "keyword" and disallow dupes.""" yaml_config = """ namespace: prod stacks: - name: vpc class_path: blueprints.VPC class_path: blueprints.Fake """ with self.assertRaises(ConstructorError): parse(yaml_config)
def test_parse_invalid_inner_keys(self): yaml_config = """ namespace: prod stacks: - name: vpc class_path: blueprints.VPC garbage: yes variables: Foo: bar """ with self.assertRaises(exceptions.InvalidConfig): parse(yaml_config)
def test_raise_construct_error_on_duplicate_stack_name_dict(self): """Some mappings should never have a duplicate children. For example we treat `stacks` as a special mapping and disallow dupe children keys.""" yaml_config = """ namespace: prod stacks: my_vpc: class_path: blueprints.VPC1 my_vpc: class_path: blueprints.VPC2 """ with self.assertRaises(ConstructorError): parse(yaml_config)
def test_parse_tags(self): config = parse(""" namespace: prod tags: "a:b": "c" "hello": 1 simple_tag: simple value """) self.assertEquals(config.tags, { "a:b": "c", "hello": "1", "simple_tag": "simple value"})
def test_parse_with_arbitrary_anchors(self): config = parse(""" namespace: prod common_variables: &common_variables Foo: bar stacks: - name: vpc class_path: blueprints.VPC variables: << : *common_variables """) stack = config.stacks[0] self.assertEquals(stack.variables, {"Foo": "bar"})
def test_parse_external_invalid(self): config = parse(""" namespace: prod stacks: - name: vpc class_path: blueprints.VPC parameters: Foo: bar - name: external-vpc stack_name: some-other-vpc external: yes """) with self.assertRaises(exceptions.InvalidConfig): config.validate()
def test_parse_with_deprecated_parameters(self): config = parse(""" namespace: prod stacks: - name: vpc class_path: blueprints.VPC parameters: Foo: bar """) with self.assertRaises(exceptions.InvalidConfig) as ex: config.validate() error = ex.exception.errors['stacks'][0]['parameters'][0] self.assertEquals( error.__str__(), "Stack definition vpc contains deprecated 'parameters', rather " "than 'variables'. Please update your config.")
def test_parse_with_deprecated_parameters(self): config = parse(""" namespace: prod stacks: - name: vpc class_path: blueprints.VPC parameters: Foo: bar """) with self.assertRaises(exceptions.InvalidConfig) as ex: config.validate() error = ex.exception.errors['stacks'][0]['parameters'][0] self.assertEquals( error.__str__(), "DEPRECATION: Stack definition vpc contains deprecated " "'parameters', rather than 'variables'. You are required to update" " your config. See https://stacker.readthedocs.io/en/latest/c" "onfig.html#variables for additional information.")
def test_parse_external(self): config = parse(""" namespace: prod stacks: - name: vpc stack_name: cool-vpc class_path: blueprints.VPC parameters: Foo: bar - name: external-vpc stack_name: other-cool-vpc external: yes """) local_stack, external_stack = config.stacks self.assertIsInstance(local_stack, Stack) self.assertEquals(local_stack.name, 'vpc') self.assertEquals(local_stack.stack_name, 'cool-vpc') self.assertIsInstance(external_stack, ExternalStack) self.assertEquals(external_stack.name, 'external-vpc') self.assertEquals(external_stack.stack_name, 'other-cool-vpc')
def test_parse(self): config_with_lists = """ namespace: prod stacker_bucket: stacker-prod pre_build: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_build: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com pre_destroy: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_destroy: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com package_sources: s3: - bucket: acmecorpbucket key: public/acmecorp-blueprints-v1.zip - bucket: examplecorpbucket key: public/examplecorp-blueprints-v2.tar.gz requester_pays: true - bucket: anotherexamplebucket key: example-blueprints-v3.tar.gz use_latest: false git: - uri: [email protected]:acmecorp/stacker_blueprints.git - uri: [email protected]:remind101/stacker_blueprints.git tag: 1.0.0 paths: - stacker_blueprints - uri: [email protected]:contoso/webapp.git branch: staging - uri: [email protected]:contoso/foo.git commit: 12345678 tags: environment: production stacks: - name: vpc class_path: blueprints.VPC variables: PrivateSubnets: - 10.0.0.0/24 - name: bastion class_path: blueprints.Bastion requires: ['vpc'] variables: VpcId: ${output vpc::VpcId} """ config_with_dicts = """ namespace: prod stacker_bucket: stacker-prod pre_build: prebuild_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_build: postbuild_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com pre_destroy: predestroy_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_destroy: postdestroy_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com package_sources: s3: - bucket: acmecorpbucket key: public/acmecorp-blueprints-v1.zip - bucket: examplecorpbucket key: public/examplecorp-blueprints-v2.tar.gz requester_pays: true - bucket: anotherexamplebucket key: example-blueprints-v3.tar.gz use_latest: false git: - uri: [email protected]:acmecorp/stacker_blueprints.git - uri: [email protected]:remind101/stacker_blueprints.git tag: 1.0.0 paths: - stacker_blueprints - uri: [email protected]:contoso/webapp.git branch: staging - uri: [email protected]:contoso/foo.git commit: 12345678 tags: environment: production stacks: vpc: class_path: blueprints.VPC variables: PrivateSubnets: - 10.0.0.0/24 bastion: class_path: blueprints.Bastion requires: ['vpc'] variables: VpcId: ${output vpc::VpcId} """ for raw_config in [config_with_lists, config_with_dicts]: config = parse(raw_config) config.validate() self.assertEqual(config.namespace, "prod") self.assertEqual(config.stacker_bucket, "stacker-prod") for hooks in [config.pre_build, config.post_build, config.pre_destroy, config.post_destroy]: self.assertEqual( hooks[0].path, "stacker.hooks.route53.create_domain") self.assertEqual( hooks[0].required, True) self.assertEqual( hooks[0].args, {"domain": "mydomain.com"}) self.assertEqual( config.package_sources.s3[0].bucket, "acmecorpbucket") self.assertEqual( config.package_sources.s3[0].key, "public/acmecorp-blueprints-v1.zip") self.assertEqual( config.package_sources.s3[1].bucket, "examplecorpbucket") self.assertEqual( config.package_sources.s3[1].key, "public/examplecorp-blueprints-v2.tar.gz") self.assertEqual( config.package_sources.s3[1].requester_pays, True) self.assertEqual( config.package_sources.s3[2].use_latest, False) self.assertEqual( config.package_sources.git[0].uri, "[email protected]:acmecorp/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].uri, "[email protected]:remind101/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].tag, "1.0.0") self.assertEqual( config.package_sources.git[1].paths, ["stacker_blueprints"]) self.assertEqual( config.package_sources.git[2].branch, "staging") self.assertEqual(config.tags, {"environment": "production"}) self.assertEqual(len(config.stacks), 2) vpc_index = next( i for (i, d) in enumerate(config.stacks) if d.name == "vpc" ) vpc = config.stacks[vpc_index] self.assertEqual(vpc.name, "vpc") self.assertEqual(vpc.class_path, "blueprints.VPC") self.assertEqual(vpc.requires, None) self.assertEqual(vpc.variables, {"PrivateSubnets": ["10.0.0.0/24"]}) bastion_index = next( i for (i, d) in enumerate(config.stacks) if d.name == "bastion" ) bastion = config.stacks[bastion_index] self.assertEqual(bastion.name, "bastion") self.assertEqual(bastion.class_path, "blueprints.Bastion") self.assertEqual(bastion.requires, ["vpc"]) self.assertEqual(bastion.variables, {"VpcId": "${output vpc::VpcId}"})
def test_parse(self): config = parse(""" namespace: prod stacker_bucket: stacker-prod pre_build: - path: stacker.hooks.route53.create_domain required: true args: domain: mydomain.com post_build: - path: stacker.hooks.route53.create_domain required: true args: domain: mydomain.com pre_destroy: - path: stacker.hooks.route53.create_domain required: true args: domain: mydomain.com post_destroy: - path: stacker.hooks.route53.create_domain required: true args: domain: mydomain.com package_sources: git: - uri: [email protected]:acmecorp/stacker_blueprints.git - uri: [email protected]:remind101/stacker_blueprints.git tag: 1.0.0 paths: - stacker_blueprints - uri: [email protected]:contoso/webapp.git branch: staging - uri: [email protected]:contoso/foo.git commit: 12345678 tags: environment: production stacks: - name: vpc class_path: blueprints.VPC variables: PrivateSubnets: - 10.0.0.0/24 - name: bastion class_path: blueprints.Bastion requires: ['vpc'] variables: VpcId: ${output vpc::VpcId} """) config.validate() self.assertEqual(config.namespace, "prod") self.assertEqual(config.stacker_bucket, "stacker-prod") for hooks in [config.pre_build, config.post_build, config.pre_destroy, config.post_destroy]: self.assertEqual( hooks[0].path, "stacker.hooks.route53.create_domain") self.assertEqual( hooks[0].required, True) self.assertEqual( hooks[0].args, {"domain": "mydomain.com"}) self.assertEqual( config.package_sources.git[0].uri, "[email protected]:acmecorp/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].uri, "[email protected]:remind101/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].tag, "1.0.0") self.assertEqual( config.package_sources.git[1].paths, ["stacker_blueprints"]) self.assertEqual( config.package_sources.git[2].branch, "staging") self.assertEqual(config.tags, {"environment": "production"}) self.assertEqual(len(config.stacks), 2) vpc = config.stacks[0] self.assertEqual(vpc.name, "vpc") self.assertEqual(vpc.class_path, "blueprints.VPC") self.assertEqual(vpc.requires, None) self.assertEqual(vpc.variables, {"PrivateSubnets": ["10.0.0.0/24"]}) bastion = config.stacks[1] self.assertEqual(bastion.name, "bastion") self.assertEqual(bastion.class_path, "blueprints.Bastion") self.assertEqual(bastion.requires, ["vpc"]) self.assertEqual(bastion.variables, {"VpcId": "${output vpc::VpcId}"})
def test_parse(self): config_with_lists = """ namespace: prod stacker_bucket: stacker-prod pre_build: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_build: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com pre_destroy: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_destroy: - path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com package_sources: s3: - bucket: acmecorpbucket key: public/acmecorp-blueprints-v1.zip - bucket: examplecorpbucket key: public/examplecorp-blueprints-v2.tar.gz requester_pays: true - bucket: anotherexamplebucket key: example-blueprints-v3.tar.gz use_latest: false paths: - foo configs: - foo/config.yml git: - uri: [email protected]:acmecorp/stacker_blueprints.git - uri: [email protected]:remind101/stacker_blueprints.git tag: 1.0.0 paths: - stacker_blueprints - uri: [email protected]:contoso/webapp.git branch: staging - uri: [email protected]:contoso/foo.git commit: 12345678 paths: - bar configs: - bar/moreconfig.yml tags: environment: production stacks: - name: vpc class_path: blueprints.VPC variables: PrivateSubnets: - 10.0.0.0/24 - name: bastion class_path: blueprints.Bastion requires: ['vpc'] variables: VpcId: ${output vpc::VpcId} """ config_with_dicts = """ namespace: prod stacker_bucket: stacker-prod pre_build: prebuild_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_build: postbuild_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com pre_destroy: predestroy_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com post_destroy: postdestroy_createdomain: path: stacker.hooks.route53.create_domain required: true enabled: true args: domain: mydomain.com package_sources: s3: - bucket: acmecorpbucket key: public/acmecorp-blueprints-v1.zip - bucket: examplecorpbucket key: public/examplecorp-blueprints-v2.tar.gz requester_pays: true - bucket: anotherexamplebucket key: example-blueprints-v3.tar.gz use_latest: false paths: - foo configs: - foo/config.yml git: - uri: [email protected]:acmecorp/stacker_blueprints.git - uri: [email protected]:remind101/stacker_blueprints.git tag: 1.0.0 paths: - stacker_blueprints - uri: [email protected]:contoso/webapp.git branch: staging - uri: [email protected]:contoso/foo.git commit: 12345678 paths: - bar configs: - bar/moreconfig.yml tags: environment: production stacks: vpc: class_path: blueprints.VPC variables: PrivateSubnets: - 10.0.0.0/24 bastion: class_path: blueprints.Bastion requires: ['vpc'] variables: VpcId: ${output vpc::VpcId} """ for raw_config in [config_with_lists, config_with_dicts]: config = parse(raw_config) config.validate() self.assertEqual(config.namespace, "prod") self.assertEqual(config.stacker_bucket, "stacker-prod") for hooks in [config.pre_build, config.post_build, config.pre_destroy, config.post_destroy]: self.assertEqual( hooks[0].path, "stacker.hooks.route53.create_domain") self.assertEqual( hooks[0].required, True) self.assertEqual( hooks[0].args, {"domain": "mydomain.com"}) self.assertEqual( config.package_sources.s3[0].bucket, "acmecorpbucket") self.assertEqual( config.package_sources.s3[0].key, "public/acmecorp-blueprints-v1.zip") self.assertEqual( config.package_sources.s3[1].bucket, "examplecorpbucket") self.assertEqual( config.package_sources.s3[1].key, "public/examplecorp-blueprints-v2.tar.gz") self.assertEqual( config.package_sources.s3[1].requester_pays, True) self.assertEqual( config.package_sources.s3[2].use_latest, False) self.assertEqual( config.package_sources.git[0].uri, "[email protected]:acmecorp/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].uri, "[email protected]:remind101/stacker_blueprints.git") self.assertEqual( config.package_sources.git[1].tag, "1.0.0") self.assertEqual( config.package_sources.git[1].paths, ["stacker_blueprints"]) self.assertEqual( config.package_sources.git[2].branch, "staging") self.assertEqual(config.tags, {"environment": "production"}) self.assertEqual(len(config.stacks), 2) vpc_index = next( i for (i, d) in enumerate(config.stacks) if d.name == "vpc" ) vpc = config.stacks[vpc_index] self.assertEqual(vpc.name, "vpc") self.assertEqual(vpc.class_path, "blueprints.VPC") self.assertEqual(vpc.requires, None) self.assertEqual(vpc.variables, {"PrivateSubnets": ["10.0.0.0/24"]}) bastion_index = next( i for (i, d) in enumerate(config.stacks) if d.name == "bastion" ) bastion = config.stacks[bastion_index] self.assertEqual(bastion.name, "bastion") self.assertEqual(bastion.class_path, "blueprints.Bastion") self.assertEqual(bastion.requires, ["vpc"]) self.assertEqual(bastion.variables, {"VpcId": "${output vpc::VpcId}"})