diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea4642684..8cf48542c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,8 @@ The basic process is pretty simple: * Fork the Project * Create your Feature Branch
`git checkout -b feature/AmazingFeature` * Commit your Changes
`git commit -m 'Add some AmazingFeature` -* Make sure Terraform linting is ok (hint: `terraform format`) * Make sure tests pass!
`pytest # in the root folder` +* Make sure Terraform linting is ok (hint: `terraform fmt -recursive` in the root folder) * Make sure any changes to variables and outputs are reflected in READMEs
`./tools/tfdoc.py [changed folder]` * Push to the Branch
`git push origin feature/AmazingFeature` * Open a Pull Request diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/README.md b/cloud-operations/scheduled-asset-inventory-export-bq/README.md index 1abecdd94..ef8bca889 100644 --- a/cloud-operations/scheduled-asset-inventory-export-bq/README.md +++ b/cloud-operations/scheduled-asset-inventory-export-bq/README.md @@ -43,7 +43,7 @@ You can also create a dashboard connecting [Datalab](https://datastudio.google.c | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| cai_config | Cloud Asset inventory export config. | object({...}) | ✓ | | +| cai_config | Cloud Asset inventory export config. | object({...}) | ✓ | | | project_id | Project id that references existing project. | string | ✓ | | | *billing_account* | Billing account id used as default for new projects. | string | | null | | *bundle_path* | Path used to write the intermediate Cloud Function code bundle. | string | | ./bundle.zip | diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/cf/main.py b/cloud-operations/scheduled-asset-inventory-export-bq/cf/main.py index 77768190c..ad97c3262 100755 --- a/cloud-operations/scheduled-asset-inventory-export-bq/cf/main.py +++ b/cloud-operations/scheduled-asset-inventory-export-bq/cf/main.py @@ -50,17 +50,18 @@ def _configure_logging(verbose=True): @click.option('--bq-project', required=True, help='Bigquery project to use.') @click.option('--bq-dataset', required=True, help='Bigquery dataset to use.') @click.option('--bq-table', required=True, help='Bigquery table name to use.') +@click.option('--target-node', required=True, help='Node in Google Cloud resource hierarchy.') @click.option('--read-time', required=False, help=( 'Day to take an asset snapshot in \'YYYYMMDD\' format, uses current day ' ' as default. Export will run at midnight of the specified day.')) @click.option('--verbose', is_flag=True, help='Verbose output') -def main_cli(project=None, bq_project=None, bq_dataset=None, bq_table=None, +def main_cli(project=None, bq_project=None, bq_dataset=None, bq_table=None, target_node=None, read_time=None, verbose=False): '''Trigger Cloud Asset inventory export to Bigquery. Data will be stored in the dataset specified on a dated table with the name specified. ''' try: - _main(project, bq_project, bq_dataset, bq_table, read_time, verbose) + _main(project, bq_project, bq_dataset, bq_table, target_node, read_time, verbose) except RuntimeError: logging.exception('exception raised') @@ -78,25 +79,25 @@ def main(event, context): logging.exception('exception in cloud function entry point') -def _main(project=None, bq_project=None, bq_dataset=None, bq_table=None, read_time=None, verbose=False): +def _main(project=None, bq_project=None, bq_dataset=None, bq_table=None, target_node=None, read_time=None, verbose=False): 'Module entry point used by cli and cloud function wrappers.' _configure_logging(verbose) if not read_time: read_time = datetime.datetime.now() client = asset_v1.AssetServiceClient() - parent = 'projects/%s' % project content_type = asset_v1.ContentType.RESOURCE output_config = asset_v1.OutputConfig() output_config.bigquery_destination.dataset = 'projects/%s/datasets/%s' % ( bq_project, bq_dataset) output_config.bigquery_destination.table = '%s_%s' % ( bq_table, read_time.strftime('%Y%m%d')) + output_config.bigquery_destination.separate_tables_per_asset_type = True output_config.bigquery_destination.force = True try: response = client.export_assets( request={ - 'parent': parent, + 'parent': target_node, 'read_time': read_time, 'content_type': content_type, 'output_config': output_config @@ -105,7 +106,7 @@ def _main(project=None, bq_project=None, bq_dataset=None, bq_table=None, read_ti except (GoogleAPIError, googleapiclient.errors.HttpError) as e: logging.debug('API Error: %s', e, exc_info=True) raise RuntimeError( - 'Error fetching Asset Inventory entries (project: %s)' % parent, e) + 'Error fetching Asset Inventory entries (resource manager node: %s)' % target_node, e) if __name__ == '__main__': diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/main.tf b/cloud-operations/scheduled-asset-inventory-export-bq/main.tf index 1b5306c48..0052401d9 100644 --- a/cloud-operations/scheduled-asset-inventory-export-bq/main.tf +++ b/cloud-operations/scheduled-asset-inventory-export-bq/main.tf @@ -73,6 +73,7 @@ module "pubsub" { module "cf" { source = "../../modules/cloud-function" project_id = module.project.project_id + region = var.region name = var.name bucket_name = "${var.name}-${random_pet.random.id}" bucket_config = { @@ -108,8 +109,8 @@ resource "google_app_engine_application" "app" { resource "google_cloud_scheduler_job" "job" { project = google_app_engine_application.app.project region = var.region - name = "test-job" - description = "test http job" + name = "cai-export-job" + description = "CAI Export Job" schedule = "* 9 * * 1" time_zone = "Etc/UTC" @@ -117,10 +118,11 @@ resource "google_cloud_scheduler_job" "job" { attributes = {} topic_name = module.pubsub.topic.id data = base64encode(jsonencode({ - project = module.project.project_id - bq_project = module.project.project_id - bq_dataset = var.cai_config.bq_dataset - bq_table = var.cai_config.bq_table + project = module.project.project_id + bq_project = module.project.project_id + bq_dataset = var.cai_config.bq_dataset + bq_table = var.cai_config.bq_table + target_node = var.cai_config.target_node })) } } @@ -133,6 +135,7 @@ module "bq" { source = "../../modules/bigquery-dataset" project_id = module.project.project_id id = var.cai_config.bq_dataset + location = var.region access = { owner = { role = "OWNER", type = "user" } } diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/variables.tf b/cloud-operations/scheduled-asset-inventory-export-bq/variables.tf index 6f8217d33..5bb62166c 100644 --- a/cloud-operations/scheduled-asset-inventory-export-bq/variables.tf +++ b/cloud-operations/scheduled-asset-inventory-export-bq/variables.tf @@ -29,8 +29,9 @@ variable "bundle_path" { variable "cai_config" { description = "Cloud Asset inventory export config." type = object({ - bq_dataset = string - bq_table = string + bq_dataset = string + bq_table = string + target_node = string }) } diff --git a/modules/compute-mig/README.md b/modules/compute-mig/README.md index 4293604e3..f1651bc08 100644 --- a/modules/compute-mig/README.md +++ b/modules/compute-mig/README.md @@ -1,8 +1,10 @@ # GCE Managed Instance Group module -This module allows creating a managed instance group supporting one or more application versions via instance templates. A health check and an autoscaler can also be optionally created. +This module allows creating a managed instance group supporting one or more application versions via instance templates. Optionally, a health check and an autoscaler can be created, and the managed instance group can be configured to be stateful. -This module can be coupled with the [`compute-vm`](../compute-vm) module which can manage instance templates, and the [`net-ilb`](../net-ilb) module to assign the MIG to a backend wired to an Internal Load Balancer. The first use case is shown in the examples below. +This module can be coupled with the [`compute-vm`](../compute-vm) module which can manage instance templates, and the [`net-ilb`](../net-ilb) module to assign the MIG to a backend wired to an Internal Load Balancer. The first use case is shown in the examples below. + +Stateful disks can be created directly, as shown in the last example below. ## Examples @@ -266,6 +268,182 @@ module "nginx-mig" { # tftest:modules=2:resources=2 ``` +### Stateful MIGs - MIG Config + +Stateful MIGs have some limitations documented [here](https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-migs#limitations). Enforcement of these requirements is the responsibility of users of this module. + +You can configure a disk defined in the instance template to be stateful for all instances in the MIG by configuring in the MIG's stateful policy, using the `stateful_disk_mig` variable. Alternatively, you can also configure stateful persistent disks individually per instance of the MIG by setting the `stateful_disk_instance` variable. A discussion on these scenarios can be found in the [docs](https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-disks-in-migs). + +An example using only the configuration at the MIG level can be seen below. + +Note that when referencing the stateful disk, you use `device_name` and not `disk_name`. + + +```hcl +module "cos-nginx" { + source = "./modules/cloud-config-container/nginx" +} + +module "nginx-template" { + source = "./modules/compute-vm" + project_id = var.project_id + name = "nginx-template" + zone = "europe-west1-b" + tags = ["http-server", "ssh"] + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + nat = false + addresses = null + }] + boot_disk = { + image = "projects/cos-cloud/global/images/family/cos-stable" + type = "pd-ssd" + size = 10 + } + attached_disks = [{ + name = "repd-1" + size = null + source_type = "attach" + source = "regions/${var.region}/disks/repd-test-1" + options = { + mode = "READ_ONLY" + replica_zone = "${var.region}-c" + type = "PERSISTENT" + } + }] + create_template = true + metadata = { + user-data = module.cos-nginx.cloud_config + } +} + +module "nginx-mig" { + source = "./modules/compute-mig" + project_id = "my-project" + location = "europe-west1-b" + name = "mig-test" + target_size = 3 + default_version = { + instance_template = module.nginx-template.template.self_link + name = "default" + } + autoscaler_config = { + max_replicas = 3 + min_replicas = 1 + cooldown_period = 30 + cpu_utilization_target = 0.65 + load_balancing_utilization_target = null + metric = null + } + stateful_config = { + per_instance_config = {}, + mig_config = { + stateful_disks = { + persistent-disk-1 = { + delete_rule = "NEVER" + } + } + } + } +} +# tftest:modules=2:resources=3 + +``` + +### Stateful MIGs - Instance Config +Here is an example defining the stateful config at the instance level. + +Note that you will need to know the instance name in order to use this configuration. + +```hcl +module "cos-nginx" { + source = "./modules/cloud-config-container/nginx" +} + +module "nginx-template" { + source = "./modules/compute-vm" + project_id = var.project_id + name = "nginx-template" + zone = "europe-west1-b" + tags = ["http-server", "ssh"] + network_interfaces = [{ + network = var.vpc.self_link + subnetwork = var.subnet.self_link + nat = false + addresses = null + }] + boot_disk = { + image = "projects/cos-cloud/global/images/family/cos-stable" + type = "pd-ssd" + size = 10 + } + attached_disks = [{ + name = "repd-1" + size = null + source_type = "attach" + source = "regions/${var.region}/disks/repd-test-1" + options = { + mode = "READ_ONLY" + replica_zone = "${var.region}-c" + type = "PERSISTENT" + } + }] + create_template = true + metadata = { + user-data = module.cos-nginx.cloud_config + } +} + +module "nginx-mig" { + source = "./modules/compute-mig" + project_id = "my-project" + location = "europe-west1-b" + name = "mig-test" + target_size = 3 + default_version = { + instance_template = module.nginx-template.template.self_link + name = "default" + } + autoscaler_config = { + max_replicas = 3 + min_replicas = 1 + cooldown_period = 30 + cpu_utilization_target = 0.65 + load_balancing_utilization_target = null + metric = null + } + stateful_config = { + per_instance_config = { + # note that this needs to be the name of an existing instance within the Managed Instance Group + instance-1 = { + stateful_disks = { + persistent-disk-1 = { + source = "test-disk", + mode = "READ_ONLY", + delete_rule= "NEVER", + }, + }, + metadata = { + foo = "bar" + }, + update_config = { + minimal_action = "NONE", + most_disruptive_allowed_action = "REPLACE", + remove_instance_state_on_destroy = false, + }, + }, + }, + mig_config = { + stateful_disks = { + } + } + } +} +# tftest:modules=2:resources=4 + +``` + ## Variables @@ -280,6 +458,7 @@ module "nginx-mig" { | *health_check_config* | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({...}) | | null | | *named_ports* | Named ports. | map(number) | | null | | *regional* | Use regional instance group. When set, `location` should be set to the region. | bool | | false | +| *stateful_config* | Stateful configuration can be done by individual instances or for all instances in the MIG. They key in per_instance_config is the name of the specific instance. The key of the stateful_disks is the 'device_name' field of the resource. Please note that device_name is defined at the OS mount level, unlike the disk name. | object({...}) | | null | | *target_pools* | Optional list of URLs for target pools to which new instances in the group are added. | list(string) | | [] | | *target_size* | Group target size, leave null when using an autoscaler. | number | | null | | *update_policy* | Update policy. Type can be 'OPPORTUNISTIC' or 'PROACTIVE', action 'REPLACE' or 'restart', surge type 'fixed' or 'percent'. | object({...}) | | null | @@ -297,4 +476,4 @@ module "nginx-mig" { ## TODO -- [ ] add support for instance groups +- [✓] add support for instance groups diff --git a/modules/compute-mig/main.tf b/modules/compute-mig/main.tf index 75ab2d3dc..5b3a92425 100644 --- a/modules/compute-mig/main.tf +++ b/modules/compute-mig/main.tf @@ -84,6 +84,14 @@ resource "google_compute_instance_group_manager" "default" { initial_delay_sec = config.value.initial_delay_sec } } + dynamic "stateful_disk" { + for_each = try(var.stateful_config.mig_config.stateful_disks, {}) + iterator = config + content { + device_name = config.key + delete_rule = config.value.delete_rule + } + } dynamic "update_policy" { for_each = var.update_policy == null ? [] : [var.update_policy] iterator = config @@ -135,6 +143,43 @@ resource "google_compute_instance_group_manager" "default" { } } +locals { + instance_group_manager = ( + var.regional ? + google_compute_region_instance_group_manager.default : + google_compute_instance_group_manager.default + ) +} + +resource "google_compute_per_instance_config" "default" { + for_each = try(var.stateful_config.per_instance_config, {}) + #for_each = var.stateful_config && var.stateful_config.per_instance_config == null ? {} : length(var.stateful_config.per_instance_config) + zone = var.location + # terraform error, solved with locals + #instance_group_manager = var.regional ? google_compute_region_instance_group_manager.default : google_compute_instance_group_manager.default + instance_group_manager = local.instance_group_manager[0].id + name = each.key + project = var.project_id + minimal_action = try(each.value.update_config.minimal_action, null) + most_disruptive_allowed_action = try(each.value.update_config.most_disruptive_allowed_action, null) + remove_instance_state_on_destroy = try(each.value.update_config.remove_instance_state_on_destroy, null) + preserved_state { + + metadata = each.value.metadata + + dynamic "disk" { + for_each = try(each.value.stateful_disks, {}) + #for_each = var.stateful_config.mig_config.stateful_disks == null ? {} : var.stateful_config.mig_config.stateful_disks + iterator = config + content { + device_name = config.key + source = config.value.source + mode = config.value.mode + delete_rule = config.value.delete_rule + } + } + } +} resource "google_compute_region_autoscaler" "default" { provider = google-beta @@ -206,6 +251,15 @@ resource "google_compute_region_instance_group_manager" "default" { initial_delay_sec = config.value.initial_delay_sec } } + dynamic "stateful_disk" { + for_each = try(var.stateful_config.mig_config.stateful_disks, {}) + iterator = config + content { + device_name = config.key + delete_rule = config.value.delete_rule + } + } + dynamic "update_policy" { for_each = var.update_policy == null ? [] : [var.update_policy] iterator = config diff --git a/modules/compute-mig/variables.tf b/modules/compute-mig/variables.tf index def5a74cc..31da4aa34 100644 --- a/modules/compute-mig/variables.tf +++ b/modules/compute-mig/variables.tf @@ -65,7 +65,6 @@ variable "location" { description = "Compute zone, or region if `regional` is set to true." type = string } - variable "name" { description = "Managed group name." type = string @@ -88,6 +87,37 @@ variable "regional" { default = false } +variable "stateful_config" { + description = "Stateful configuration can be done by individual instances or for all instances in the MIG. They key in per_instance_config is the name of the specific instance. The key of the stateful_disks is the 'device_name' field of the resource. Please note that device_name is defined at the OS mount level, unlike the disk name." + type = object({ + per_instance_config = map(object({ + #name is the key + #name = string + stateful_disks = map(object({ + #device_name is the key + source = string + mode = string # READ_WRITE | READ_ONLY + delete_rule = string # NEVER | ON_PERMANENT_INSTANCE_DELETION + })) + metadata = map(string) + update_config = object({ + minimal_action = string # NONE | REPLACE | RESTART | REFRESH + most_disruptive_allowed_action = string # REPLACE | RESTART | REFRESH | NONE + remove_instance_state_on_destroy = bool + }) + })) + + mig_config = object({ + stateful_disks = map(object({ + #device_name is the key + delete_rule = string # NEVER | ON_PERMANENT_INSTANCE_DELETION + })) + }) + + }) + default = null +} + variable "target_pools" { description = "Optional list of URLs for target pools to which new instances in the group are added." type = list(string) diff --git a/tests/cloud_operations/scheduled_asset_inventory_export_bq/fixture/variables.tf b/tests/cloud_operations/scheduled_asset_inventory_export_bq/fixture/variables.tf index 67d2624b4..1d70f8272 100644 --- a/tests/cloud_operations/scheduled_asset_inventory_export_bq/fixture/variables.tf +++ b/tests/cloud_operations/scheduled_asset_inventory_export_bq/fixture/variables.tf @@ -19,12 +19,14 @@ variable "billing_account" { variable "cai_config" { type = object({ - bq_dataset = string - bq_table = string + bq_dataset = string + bq_table = string + target_node = string }) default = { - bq_dataset = "my-dataset" - bq_table = "my_table" + bq_dataset = "my-dataset" + bq_table = "my_table" + target_node = "organization/1234567890" } } diff --git a/tests/conftest.py b/tests/conftest.py index 2a90f9256..33c63596c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,7 +30,7 @@ def _plan_runner(): "Runs Terraform plan and returns parsed output." tf = tftest.TerraformTest(fixture_path, BASEDIR, os.environ.get('TERRAFORM', 'terraform')) - tf.setup() + tf.setup(upgrade=True) return tf.plan(output=True, refresh=refresh, tf_vars=tf_vars, targets=targets) return run_plan @@ -94,7 +94,7 @@ def apply_runner(): "Runs Terraform apply and returns parsed output" tf = tftest.TerraformTest(fixture_path, BASEDIR, os.environ.get('TERRAFORM', 'terraform')) - tf.setup() + tf.setup(upgrade=True) apply = tf.apply(tf_vars=tf_vars) output = tf.output(json_format=True) return apply, output diff --git a/tests/modules/compute_mig/fixture/main.tf b/tests/modules/compute_mig/fixture/main.tf index 4d3a53c41..a18b237eb 100644 --- a/tests/modules/compute_mig/fixture/main.tf +++ b/tests/modules/compute_mig/fixture/main.tf @@ -14,6 +14,15 @@ * limitations under the License. */ +# Used in stateful disk test +resource "google_compute_disk" "default" { + name = "test-disk" + type = "pd-ssd" + zone = "europe-west1-c" + image = "debian-9-stretch-v20200805" + physical_block_size_bytes = 4096 +} + module "test" { source = "../../../../modules/compute-mig" project_id = "my-project" @@ -28,6 +37,8 @@ module "test" { health_check_config = var.health_check_config named_ports = var.named_ports regional = var.regional - update_policy = var.update_policy - versions = var.versions + stateful_config = var.stateful_config + + update_policy = var.update_policy + versions = var.versions } diff --git a/tests/modules/compute_mig/fixture/variables.tf b/tests/modules/compute_mig/fixture/variables.tf index 2a02cb6c9..c025665cd 100644 --- a/tests/modules/compute_mig/fixture/variables.tf +++ b/tests/modules/compute_mig/fixture/variables.tf @@ -60,6 +60,37 @@ variable "regional" { default = false } +variable "stateful_config" { + description = "Stateful configuration can be done by individual instances or for all instances in the MIG. They key in per_instance_config is the name of the specific instance. The key of the stateful_disks is the 'device_name' field of the resource. Please note that device_name is defined at the OS mount level, unlike the disk name." + type = object({ + per_instance_config = map(object({ + #name is the key + #name = string + stateful_disks = map(object({ + #device_name is the key + source = string + mode = string # READ_WRITE | READ_ONLY + delete_rule = string # NEVER | ON_PERMANENT_INSTANCE_DELETION + })) + metadata = map(string) + update_config = object({ + minimal_action = string # NONE | REPLACE | RESTART | REFRESH + most_disruptive_allowed_action = string # REPLACE | RESTART | REFRESH | NONE + remove_instance_state_on_destroy = bool + }) + })) + + mig_config = object({ + stateful_disks = map(object({ + #device_name is the key + delete_rule = string # NEVER | ON_PERMANENT_INSTANCE_DELETION + })) + }) + + }) + default = null +} + variable "update_policy" { type = object({ type = string # OPPORTUNISTIC | PROACTIVE diff --git a/tests/modules/compute_mig/test_plan.py b/tests/modules/compute_mig/test_plan.py index 81e6a313a..a6987904c 100644 --- a/tests/modules/compute_mig/test_plan.py +++ b/tests/modules/compute_mig/test_plan.py @@ -24,6 +24,7 @@ def test_defaults(plan_runner): "Test variable defaults." _, resources = plan_runner(FIXTURES_DIR) assert len(resources) == 1 + print(resources[0]['type']) mig = resources[0] assert mig['type'] == 'google_compute_instance_group_manager' assert mig['values']['target_size'] == 2 @@ -35,7 +36,6 @@ def test_defaults(plan_runner): assert mig['values']['target_size'] == 2 assert mig['values']['region'] - def test_health_check(plan_runner): "Test health check resource." health_check_config = '{type="tcp", check={port=80}, config=null, logging=false}' @@ -75,3 +75,81 @@ def test_autoscaler(plan_runner): assert len(resources) == 2 autoscaler = resources[0] assert autoscaler['type'] == 'google_compute_region_autoscaler' + +def test_stateful_mig(plan_runner): + "Test stateful instances - mig." + + stateful_config = ( + '{' + 'per_instance_config = {},' + 'mig_config = {' + 'stateful_disks = {' + 'persistent-disk-1 = {delete_rule="NEVER"}' + '}' + '}' + '}' + ) + _, resources = plan_runner( + FIXTURES_DIR, stateful_config=stateful_config) + assert len(resources) == 1 + statefuldisk = resources[0] + assert statefuldisk['type'] == 'google_compute_instance_group_manager' + assert statefuldisk['values']['stateful_disk'] == [{ + 'device_name': 'persistent-disk-1', + 'delete_rule': 'NEVER', + }] + +def test_stateful_instance(plan_runner): + "Test stateful instances - instance." + stateful_config = ( + '{' + 'per_instance_config = {' + 'instance-1 = {' + 'stateful_disks = {' + 'persistent-disk-1 = {' + 'source = "test-disk",' + 'mode = "READ_ONLY",' + 'delete_rule= "NEVER",' + '},' + '},' + 'metadata = {' + 'foo = "bar"' + '},' + 'update_config = {' + 'minimal_action = "NONE",' + 'most_disruptive_allowed_action = "REPLACE",' + 'remove_instance_state_on_destroy = false,' + + '},' + '},' + '},' + 'mig_config = {' + 'stateful_disks = {' + 'persistent-disk-1 = {delete_rule="NEVER"}' + '}' + '}' + '}' + ) + _, resources = plan_runner( + FIXTURES_DIR, stateful_config=stateful_config) + assert len(resources) == 2 + instanceconfig = resources[0] + assert instanceconfig['type'] == 'google_compute_instance_group_manager' + instanceconfig = resources[1] + assert instanceconfig['type'] == 'google_compute_per_instance_config' + + assert instanceconfig['values']['preserved_state'] == [{ + 'disk': [{ + 'device_name': 'persistent-disk-1', + 'delete_rule': 'NEVER', + 'source': 'test-disk', + 'mode': 'READ_ONLY', + }], + 'metadata': { + 'foo': 'bar' + } + }] + + assert instanceconfig['values']['minimal_action'] == 'NONE' + assert instanceconfig['values']['most_disruptive_allowed_action'] == 'REPLACE' + assert instanceconfig['values']['remove_instance_state_on_destroy'] == False \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt index 1b4d95451..480dbeb5b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,4 @@ pytest>=4.6.0 PyYAML>=5.3 -tftest>=1.5.2 +tftest>=1.6.1 marko>=0.9.1