From 382dbb15914d137d26fb43d0a6c7637ff5e31a0d Mon Sep 17 00:00:00 2001
From: Marco Emilio Poleggi <marco-emilio.poleggi@hesge.ch>
Date: Thu, 29 Sep 2022 14:28:02 +0200
Subject: [PATCH] Done lab refactoring on OpenStack

---
 SwitchEngines/README.md                 | 328 +++++++++++++++++-------
 SwitchEngines/conf/clouds.yaml.api_cred |   9 +-
 SwitchEngines/conf/clouds.yaml.app_cred |  10 +-
 SwitchEngines/main.tf.advnc             | Bin 1509 -> 2450 bytes
 SwitchEngines/main.tf.basic             |  32 +--
 SwitchEngines/outputs.tf                | Bin 290 -> 122 bytes
 SwitchEngines/scripts/add-ssh.yaml      |  16 ++
 SwitchEngines/variables.tf              |   6 +
 8 files changed, 270 insertions(+), 131 deletions(-)
 create mode 100644 SwitchEngines/scripts/add-ssh.yaml
 create mode 100644 SwitchEngines/variables.tf

diff --git a/SwitchEngines/README.md b/SwitchEngines/README.md
index 039df97..2265f49 100644
--- a/SwitchEngines/README.md
+++ b/SwitchEngines/README.md
@@ -41,31 +41,29 @@ Please, refer to your OS documentation for the proper way to do so
      v1.1.4. Skip the TF "Quick start tutorial" (Docker).
   1. [OpenStack
      CLI](https://docs.openstack.org/newton/user-guide/common/cli-install-openstack-command-line-clients.html)
-     version >= 5.7.0. It is recommended to install your original distribution
+     version >= 6.0.0. It is recommended to install your original distribution
      package named like `python-openstackclient`.
-  1. Grab or create a new project in your OpenStack account. The placeholder
-     used in this exercise is `<your-project-name>` with value `Cloud-MA-IaC`.
-  1. On the OpenStack Horizon dashboard, go to your project page, then
-     1. create new [Application
+  1. On the OpenStack dashboard, create a new project. The placeholder used in
+     this exercise is `<your-project-name>` with value `Cloud-MA-IaC`.
+  1. Go to your project page, then create new [Application
         Credentials](https://engines.switch.ch/horizon/identity/application_credentials/)
-        and save it as `~/.config/openstack/clouds.yaml`. With SwitchEngines, the
-        cloud name to use for this is `engines`. :warning: App credentials might
-        not work for some commands. A template is also available in repo file
-        [`conf/clouds.yaml.api_cred`](conf/clouds.yaml.api_cred). Alternatively,
+        and save it as `~/.config/openstack/clouds.yaml`. With SwitchEngines,
+        the cloud name to use for this is `engines`. :warning: App credentials
+        might not work for some commands. A template is also available in this
+        repo file
+        [`conf/clouds.yaml.app_cred`](conf/clouds.yaml.app_cred). Alternatively,
         you can use your [API
         credentials](https://engines.switch.ch/horizon/project/api_access/clouds.yaml)
-        with explicit project name/ID -- you'll have to add your API `password` from
-        your profile page's ["Credentials"
+        with explicit project name/ID -- you'll have to add your API
+        `password` from your profile page's ["Credentials"
         tab](https://engines.admin.switch.ch/users/profile). A template is
-        available in repo file
-        [`conf/clouds.yaml.app_cred`](conf/clouds.yaml.app_cred).
-    <!-- 1. download your [RC -->
-    <!--    file](https://engines.switch.ch/horizon/project/api_access/openrc/) and -->
-    <!--    install it (it asks for your API password): -->
-    <!--    ``` shell -->
-    <!--    $ source <your-project>-openrc.sh -->
-    <!--    ``` -->
-  1. Verify that your credentials are OK (:warning: it might reveal secrets!):
+        available in this repo file
+        [`conf/clouds.yaml.api_cred`](conf/clouds.yaml.app_cred). :warning:
+        Avoid mixing different authentication schemes in `clouds.yaml` or from
+        the environment (via sourcing so called OpenStack RC files).
+  1. Verify that your credentials are OK (:warning: it might reveal secrets!
+     If you have just one cloud configured, you can drop the switch
+     `--os-cloud=engines`, else adapt accordingly):
   ``` shell
   lcl$ $ openstack --os-cloud=engines [application] credential list
   ```
@@ -74,7 +72,7 @@ Please, refer to your OS documentation for the proper way to do so
 
 **Goal:** instruct TF to handle a single OpenStack instance.
 
-<a name="image-query"></a>Find out the smallest image to use for a Debian server:
+Find out the smallest image to use for a Debian server:
 
 ``` shell
 lcl$ openstack --os-cloud=engines image list --public --status=active --sort-column=Size -c ID -c Name -c Size --long
@@ -86,7 +84,7 @@ lcl$ openstack --os-cloud=engines image list --public --status=active --sort-col
 ```
 
 :bulb: We use the first ID found for the placeholder `<your-image-ID>`. In
-SwitchEngines this is 1.5GB.
+SwitchEngines, that has a size of ~1.5GB.
 
 Find out the smallest instance *flavor* that acommodates our Debian image.
 
@@ -106,6 +104,9 @@ Create a "sandbox" directory on your local machine
 in HCL language), the infrastructure *definition* file, with the following
 content (replace all `<...>` tokens with actual values):
 
+:bulb: Application credentials shall match those in file
+`~/.config/openstack/clouds.yaml`.
+
 ``` hcl
 terraform {
   required_version = ">= 0.14.9"
@@ -122,7 +123,7 @@ provider "openstack" {
   project_domain_id = "<your-project-ID>"
   application_credential_id     = "<your-app-cred-ID>"
   application_credential_secret = "<your-app-cred-secret>"
-  region = "ZH"
+  region = "<your-region>"
 }
 
 resource "openstack_compute_instance_v2" "app_server" {
@@ -229,8 +230,7 @@ Do you want to perform these actions?
 
   Enter a value: yes
 
-openstack_compute_instance_v2.app_server: Creating...
-openstack_compute_instance_v2.app_server: Still creating... [10s elapsed]
+...
 openstack_compute_instance_v2.app_server: Creation complete after 18s [id=f70aef53-51f9-4d12-a4a9-fe9a6cc5a58f]
 
 Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
@@ -295,7 +295,7 @@ not, why? We'll deal with that in Task #4 below.
 Now, stop the running instance (its ID is shown above ;-):
 
 ``` shell
-lcl$ $ openstack server stop f70aef53-51f9-4d12-a4a9-fe9a6cc5a58f
+lcl$ openstack --os-cloud=engines server stop f70aef53-51f9-4d12-a4a9-fe9a6cc5a58f
 ```
 
 wait some seconds and test again:
@@ -310,114 +310,254 @@ resource. So, let's refresh it, and check again:
 
 ``` shell
 lcl$ terraform refresh
-aws_instance.app_server: Refreshing state... [id=i-0155ba9d77ee0a854]
-lcl$ terraform show | grep instance_state
-    instance_state                       = "stopped"
+...
+lcl$ terraform show | grep power_state
+    power_state         = "shutoff"
 ```
 
 Ah-ha!
 
+Hold on a second: our TF plan does not (explicitly) specify the desired status
+of a resource. What happens if we reapply the plan? Lets' try:
 
-### Task #4: change your infrastructure ###
+``` shell
+lcl$ terraform apply -auto-approve
+openstack_compute_instance_v2.app_server: Refreshing state... [id=...]
+...
+Terraform will perform the following actions:
+...
+      ~ power_state         = "shutoff" -> "active"
+...
+Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
 
-Indeed, the instance doesn't have a (floating) *public* IP address, so we need
-to get one and associate it to our instance with the following snippet added
-to `main.tf`:
+lcl$ terraform show | grep power_state
+    power_state         = "active"
+```
 
-``` hcl
-resource "openstack_networking_floatingip_v2" "fip_1" {
-  pool = "public"
-}
+:warning: Apply was run in `-auto-approve` (non interactive) mode which
+assumes "yes" to all questions. Use with care!
+
+From the above commands' output we see that
+  * the local state is refreshed before doing anything,
+  * 1 change was applied: the instance has been switched on,
+  * OpenStack instances have an "active" default `power_state` :-)
 
-resource "openstack_compute_floatingip_associate_v2" "fip_1" {
-  # we use var/obj notation here ${x.y}
-  floating_ip = "${openstack_networking_floatingip_v2.fip_1.address}"
-  instance_id = "${openstack_compute_instance_v2.app_server.id}"
-  # to avoid getting "Resource not found"
-  wait_until_associated = true
-}
-```
 
-Let's see what happens:
+### Task #4: change your infrastructure ###
+
+**Goal:** modify the resource created before, and learn how to apply changes
+to a Terraform project.
+
+Replace the resource's `image_id` in `main.tf` with the second one found from
+the catalog query done above -- it should be a "Debian Bullseye 11
+(SWITCHengines)". Before applying our new plan, let's see what TF thinks of
+it:
+
 ``` shell
-lcl$ terraform apply -auto-approve
-openstack_compute_instance_v2.app_server: Refreshing state... [id=f70aef53-51f9-4d12-a4a9-fe9a6cc5a58f]
-...
+lcl$ terraform plan -out=change-image-ID.tfplan
+openstack_compute_instance_v2.app_server: Refreshing state... [id=1edad9e2-5459-4980-bbc5-a0c5b65bfb0d]
+
 Terraform will perform the following actions:
 
-  # openstack_compute_floatingip_associate_v2.fip_1 will be created
-  + resource "openstack_compute_floatingip_associate_v2" "fip_1" {
-      + floating_ip           = (known after apply)
-      ...
-    }
+  # openstack_compute_instance_v2.app_server will be updated in-place
+  ~ resource "openstack_compute_instance_v2" "app_server" {
+        id                  = "1edad9e2-5459-4980-bbc5-a0c5b65bfb0d"
+      ~ image_id            = "8674f1a5-f7d9-4975-af0b-d2e9e33c9152" -> "54ee4d6e-9155-4698-ab2b-45d9067e8e8e"
+        name                = "TF-managed"
+        tags                = []
+        # (13 unchanged attributes hidden)
 
-  # openstack_networking_floatingip_v2.fip_1 will be created
-  + resource "openstack_networking_floatingip_v2" "fip_1" {
-      + address    = (known after apply)
-      ...
+        # (1 unchanged block hidden)
     }
 
-Plan: 2 to add, 0 to change, 0 to destroy.
-openstack_networking_floatingip_v2.fip_1: Creating...
-openstack_networking_floatingip_v2.fip_1: Creation complete after 9s [id=81f7690f-e024-4eb8-bbbc-98a242c3b0c3]
-openstack_compute_floatingip_associate_v2.fip_1: Creating...
-openstack_compute_floatingip_associate_v2.fip_1: Creation complete after 6s [id=86.119.32.210/f70aef53-51f9-4d12-a4a9-fe9a6cc5a58f/]
+Plan: 0 to add, 1 to change, 0 to destroy.
+...
+Saved the plan to: change-image-ID.tfplan
+
+To perform exactly these actions, run the following command to apply:
+    terraform apply "change-image-ID.tfplan"
 ```
 
-Here we go. First, observe how TF had to refresh its local status before
-applying the new plan. Then, now the instance got the public IP address shown
-at the bottom of the command's output. However, a last bit is still missing,
-because the "default" *security group* only allows outbound traffic. Thus we
-need something like:
+:bulb: Remarks:
+  * The change we want to apply is *not* destructive.
+  * We saved our plan. :question: Why? It is not really necessary in a simple
+    scenario like ours, however a more complex IaC workflow might require plan
+    artifacts to be programmatically validated and versioned.
+
+Apply the saved plan:
+``` shell
+lcl$ terraform apply change-image-ID.tfplan
+$ terraform apply change-image-ID.tfplan
+...
+Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
+```
+
+:bulb: What? Not asking for confirmation? Indeed, a *saved* plan is intended for
+automated workflows! Moreover, a saved plan will come handy for rolling back a
+broken infrastructure to the last working setup.
+
+:question: What if we did not save our plan, and called a plain apply command?
+Would the result be the same?
+
+
+### Task #5: input variables ###
+
+**Goal:** make a TF plan more flexible via input variables.
+
+Our original plan has all its content hard-coded. Let's make it more flexible
+with some input variables stored in a separate `variables.tf` file inside your
+TF sandbox:
 
 ``` hcl
-resource "openstack_compute_instance_v2" "app_server" {
-  ... # as above
- security_groups = ["default", "secgroup_tf"]
+variable "instance_name" {
+  description = "Value of the instance's name tag"
+  type        = string
+  default     = "AnotherAppServerInstance"
 }
+```
 
-resource "openstack_networking_secgroup_v2" "secgroup_tf" {
-  name        = "secgroup_tf"
-}
+Then modify the `main.tf` as follows:
 
-resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_1" {
-  direction         = "ingress"
-  ethertype         = "IPv4"
-  protocol          = "tcp"
-  port_range_min    = 22
-  port_range_max    = 22
-  remote_ip_prefix  = "0.0.0.0/0"
-  security_group_id = "${openstack_networking_secgroup_v2.secgroup_tf.id}"
+``` hcl
+resource "openstack_compute_instance_v2" "app_server" {
+  ...
+  metadata = {
+    my_instance_name = var.instance_name
+  }
 }
+```
 
-resource "openstack_networking_floatingip_v2" "fip_1" ...
+Apply the changes:
+``` shell
+lcl$ terraform apply -auto-approve
+
+Plan: 0 to add, 1 to change, 0 to destroy.
 ...
+Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
 ```
 
-Now, our instance is reachable via SSH (only!). But can we login? The reply is
-in Task #7.
+You should see the new tag added to the "Metadata" section of the Horizon
+dashboard:
+`https://engines.switch.ch/horizon/project/instances/<instance-ID>/`.
+
+:bulb: **Exercise:** input variables can also be passed on the `apply` command
+line. Find how to do that with another different value for the variable
+`instance_name`. :question: Would this last change be persistent if we rerun a
+plain `terraform apply`?
 
-### Task #5: input variables ###
 
 ### Task #6: queries with outputs ###
 
-### Task #7: SSH provisioning with Cloud-Init ###
+**Goal:** use output values to query a provisioned infrastructure.
+
+We have seen in the previous tasks that the infrastructure's status can be
+displayed via `terraform show`: a rather clumsy way, if you just want to
+extract some specific information. A better programmatic way of querying your
+infrastructure makes use of "outputs". In your sandbox, put the following in a
+file called `outputs.tf`:
 
-@@@@@ OLD part below@@@@
+``` hcl
+output "instance_id" {
+  description = "ID of the instance"
+  value       = openstack_compute_instance_v2.app_server.id
+}
+```
 
-Also, prepare a `~/terraform/OpenStack/outputs.tf` file with the appropriate
-contents to get the instance's public IP address (via `instance_public_ip`).
+We have declared two outputs. As usual with TF, before querying their
+associated values, we need to apply the changes:
 
+``` shell
+lcl$ terraform apply -auto-approve
+...
+Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
+
+Outputs:
 
+instance_id = "93914f14-e521-4cc1-acfe-046bc3fa31be"
+```
 
-Then apply!
+So, we already got the needed information, but, within a workflow, it is more
+practical to do something like:
 
 ``` shell
-lcl$ terraform apply
+lcl$ terraform output instance_id
 ```
 
-Verify that you can SSH as user `debian` into your instance:
+:bulb: **Exercise:** Add an output item to display the instance metadata tag
+`my_instance_name`.
 
+:question: What if the `my_instance_name` tag is changed outside TF? Try f.i.:
 ``` shell
-lcl$ ssh debian@$(terraform output -raw instance_public_ip) -i ../tf-cloud-init
+lcl$ openstack --os-cloud=engines server set \
+       --property my_instance_name="Foo-Bar" 93914f14-e521-4cc1-acfe-046bc3fa31be
+```
+
+:question: What must be done to have TF respect that external change?
+
+:question: How to revert an external change via TF?
+
+
+### Task #7: networking and SSH provisioning with Cloud-Init ###
+
+**Goal:** use Cloud-Init to provision an SSH access to your TF-managed
+instance.
+
+Did you try to SSH into the instance you created via TF? It cannot work,
+because we did not instructed TF about networking, login, users, keys or
+anything else. **This is left entirely to you as an exercise.** With reference
+to the official TF documentation for the [OpenStack
+provider](https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/), you shall:
+
+  1. Destroy your infrastructure. Guess how ;-)
+  1. Create an SSH key pair `tf-cloud-init`.
+  1. Create a new cloud-init file
+     `~/terraform/OpenStack/scripts/add-ssh.yaml` with the following content:
+     ``` yaml
+     #cloud-config
+     #^^^^^^^^^^^^
+     # DO NOT TOUCH the first line!
+     ---
+     groups:
+       - debian: [root, sys]
+       - terraform
+
+     users:
+       - default
+       - name: terraform
+         gecos: terraform
+         primary_group: terraform
+         groups: users, admin
+         ssh_authorized_keys:
+           - <your-SSH-pub-key-on-one-line>
+     ```
+     :warning: **Mind that the first line of this file must spell exactly
+     `#cloud-config`**!
+  1. Modify the `main.tf` as follows:
+     1. add a resource block for a custom v2 network *security group*;
+     1. add resource blocks for v2 *security group rules* allowing ICMP ping
+        and TCP ingress ports 22 from anywhere with any egress port open. These
+        resources shall reference your security group;
+     1. add a resource block for a v2 *floating IP* from the public pool;
+     1. add a resource block for a v2 *floating IP association* referencing
+        your floating IP resource and your compute instance resource;
+     1. extend your `"app_server"` resource block to:
+        1. reference your custom security group as well as the default one,
+        1. associate a public IP address,
+        1. include the above cloud-init file,
+     1. add a TF output `"instance_public_ip"`.
+
+:bulb: In the above instruction, "reference" means that the keys shall take
+values dynamically from variable expansion, like:
+``` hcl
+  key = ${<object.attribute...>}
+```
+
+When done, *validate* your new plan and *apply* it. Verify that you can ping
+and connect via SSH as user `terraform` into your instance:
+
+``` shell
+lcl$ ping $(terraform output -raw instance_public_ip)
+...
+lcl$ ssh terraform@$(terraform output -raw instance_public_ip) -i /path/to/private/key/tf-cloud-init
+...
 ```
diff --git a/SwitchEngines/conf/clouds.yaml.api_cred b/SwitchEngines/conf/clouds.yaml.api_cred
index 32777c9..a07c765 100644
--- a/SwitchEngines/conf/clouds.yaml.api_cred
+++ b/SwitchEngines/conf/clouds.yaml.api_cred
@@ -1,10 +1,13 @@
-# Project-agnostic -- API credentials
+# -*- mode: yaml -*-
+# OpenStack conf file with *API* credentials --
+# <https://engines.switch.ch/horizon/project/api_access>
 clouds:
   engines:
     auth:
-      auth_url: https://keystone.cloud.switch.ch:5000/v3
+      auth_url: "https://keystone.cloud.switch.ch:5000/v3"
       username: "<your-user-name>"
-      password: "<your-password"
+      password: "<your-password>"
+      # project_name: "<your-project>"
       user_domain_name: "Default"
     interface: "public"
     identity_api_version: 3
diff --git a/SwitchEngines/conf/clouds.yaml.app_cred b/SwitchEngines/conf/clouds.yaml.app_cred
index 6021f0b..40a5b98 100644
--- a/SwitchEngines/conf/clouds.yaml.app_cred
+++ b/SwitchEngines/conf/clouds.yaml.app_cred
@@ -1,12 +1,12 @@
-# Project-specific -- application credentials.
+# -*- mode: yaml -*-
+# OpenStack conf file with *application* credentials.
 clouds:
-  Cloud-MA-IaC:
+  engines:
     auth:
-      auth_url: https://keystone.cloud.switch.ch:5000/v3
+      auth_url: "https://keystone.cloud.switch.ch:5000/v3"
       application_credential_id: "<your-app-cred-ID>"
       application_credential_secret: "<your-app-cred-secret>"
-      user_domain_name: Default
-      project_domain_name: Default
     interface: "public"
+    region: "<your-region>"
     identity_api_version: 3
     auth_type: "v3applicationcredential"
diff --git a/SwitchEngines/main.tf.advnc b/SwitchEngines/main.tf.advnc
index c5ace7adaf62c2938ad383b304302acd56fd4f13..d1fe209a99075405e0512e77a60d8307b3b6e30c 100644
GIT binary patch
literal 2450
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

literal 1509
zcmZQ@_Y83kiVO&0c>ZzuX~_ltnf~>0z6(TkT^<Vr-hQS%@#FbmVX2fd{n^EvFL0(8
z^Ei4p1zh|rzF%bGp-tbUraHR2gjKK}UcF+apS+EnlTEL}&I!*uIfM3U2VS1W%YFKU
zmhXS>d)3ZjT0&9zas6zP*53CIMfru^z9@Lpy}HXYK#<XG+gktB|E+i0!+&)}eS9Dh
z<<fMHe}l$mriK%Z0w3J(q&{Rlb@b598~ou@{@<4@7d{qLoSGJCP_^1d|J#Cd|EDDP
zEo3-9MMm%Bk|_7Bh9Zdy)7Sfp$@a-^DDE<fSS$1UBX?z|^epZ*d9DTDZHwORUHyvd
z=3%wU73WSDl&_inmCLwo700_veP)eck4$}H`j(;imst$&RYRXuZ;nXy3U1AswnK$y
z1Lr>(^F!O~QhRso6pG#y9PwlM?w{Md4=CSQxw>W1o)b-J0Zm6E0-HbTZ<4!_GkY6n
zq5XvgmBkkWCNR$a=Hu-o`R+d7YHrbILjEsQwEI^wRaHmCdhS_yp>D3lFRiCnoi-Q;
zdMrF9mg~lyC*HPJGjCVcL!pZ{-VemQs}o}?`QGpduIHF)+f|Sf)gi#}Yt}&<y&B(L
zLfT1t7dWo0Nc+)qEU?9Pcid@dX%-*#N4GW}dindpDq9<y3G6pHADf=PxM6js{D&t+
zQtRe2w#q8^G6XJ_ufOxbw^sdF{ocs@>QxrX>uc(?UU}`kY{;UVb=H?dd8vc)jW_I9
z{G=uO4$A24{q0c6cai5rZ1lB5U(Eg--N=*puE&yb?tR`?Wy6{2Qrit1ChYdw^qS@N
zbk#`<`CQ`Gt$fC>QIywnIQr+>=7Ww^6BHc}&fY%Vh`YON+m*MIQq42h1>P&%FPy{j
z&p3XSWrgLV75l%(_8M>}wAK1PubHcL&i{Rld-;U9d?8wid`72dO*wdP&&~6aZU?3R
zNX>1Ib6D%qaPywbmJ`=%(?5iER+O&d)=1N9*7kl;vc2cqgN0{0yM8@ZikIyujW^kL
z_4A!+c7o^j^)x&<mH&OouFDULYj$-ezq_B$@F)9d=O%%NO<TC9R<f3uyz;-qeqzEq
z?p3?uSsDWzWVm%EPwP5m`qSj@F|7=iuSK_YZ6ogp&1k%TOtCg~1#8Uv87ddP@*ep$
z$F2C;mor_#>CY2x>|jc5U;NL4<#L-q_v1~o?1b68C&zVa@4CbFT}a75D^;#OX>r!w
z%|^#HEf%y*pY#9Jne|`qI(N)tsJy@GnXBBdnkm{TTHCeXUaH+t@60yCY)RV5d6fcw
znKxrJ7L>5Dua-(TnIgh|Peb#>zEFuNU03gj_1ts_Zq;>ozCdPsmA+Ax`nvyTpB_K{
zu_^Cu!`@A)=XI|7+?{i@!{-r~xyqW{h4Z`a{LGtj?Mu5~ocwLRlwPL(&C|1YJ*X5i
zXE{^yudpw`M}+Ory-4L38VV&ghqpVZY}B0eT3s+;HuGdnF%GUu*QW9X1`oEi%WEyv
zdNys(LaVUo$HnQhg;u|GE;ZcTv~rK==dZW!u2p>cZ3fGWJIvoBAGP`R2dQYS`tW$;
zJ@e^eZBGsb8O@tead1gs++XSTSFRx^7wHSI@hr4B%A3K<_jc+t>p#JJbuV{_+%etD
z^zQgB;YKB^_5il7-=!zAUvbZr^gk=Oe7D{0)p{on%?xK<mtA7BxAE%c*ZdnEpSr(>
zr}-^Ix<bPnGtrcDtnXD1>|km>IIVxl-G5wssT=0xCznotm8#Nry8fwHr;>V@{mwp_
zEid0S#A`K6?rrP8(-!MJbH=n?FF7}U5Lz<Zc>n2wO(DT^^q40m@@!<~5HNYhwpZnz
z$I>GUDi>`Fetw$au&v8d?GMU9tE!wYzB0QxqvXWJf<>QG=F}=q?{dvrUt3}!`e^5z
z_r6~^cPJdJY7gh<nYH=FqV@j{8=snZF8O`u*_Ce>Z@8}?AtrI=(`4BzHpitS>x~6`
zSdw!$I0nVMYK^J2vkBta$ZNX8qiWiM^@4IC7JteL5_F}~gTzB?N;bX8)%jGsu}&z?
j^!MR-HG_25Fjt<~-D?!ioHd=1|7#`3x{Zn7S}h6yz4Y7}

diff --git a/SwitchEngines/main.tf.basic b/SwitchEngines/main.tf.basic
index 3bcab00..e6b5142 100644
--- a/SwitchEngines/main.tf.basic
+++ b/SwitchEngines/main.tf.basic
@@ -1,12 +1,12 @@
+# -*- mode: terraform -*-
 terraform {
-  required_version = ">= 0.14.9"
   required_providers {
     openstack = {
       source  = "terraform-provider-openstack/openstack"
-      # version = "~> 1.35.0"
       version = "~> 1.48.0"
     }
   }
+  required_version = ">= 0.15.0"
 }
 
 # Configure the OpenStack Provider
@@ -15,7 +15,7 @@ provider "openstack" {
   project_domain_id = "<your-project-ID>"
   application_credential_id     = "<your-app-cred-ID>"
   application_credential_secret = "<your-app-cred-secret>"
-  region = "LS"
+  region = "<your-region>"
 }
 
 resource "openstack_compute_instance_v2" "app_server" {
@@ -23,30 +23,4 @@ resource "openstack_compute_instance_v2" "app_server" {
   image_id        = "<your-image-ID>"
   flavor_name     = "<your-flavor>"
   key_pair        = "TF_lab"
-  security_groups = ["default", "secgroup_tf"]
-}
-
-resource "openstack_networking_secgroup_v2" "secgroup_tf" {
-  name        = "secgroup_tf"
-}
-
-resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_1" {
-  direction         = "ingress"
-  ethertype         = "IPv4"
-  protocol          = "tcp"
-  port_range_min    = 22
-  port_range_max    = 22
-  remote_ip_prefix  = "0.0.0.0/0"
-  security_group_id = "${openstack_networking_secgroup_v2.secgroup_tf.id}"
-}
-
-resource "openstack_networking_floatingip_v2" "fip_1" {
-  pool = "public"
-}
-
-resource "openstack_compute_floatingip_associate_v2" "fip_1" {
-  floating_ip = "${openstack_networking_floatingip_v2.fip_1.address}"
-  instance_id = "${openstack_compute_instance_v2.app_server.id}"
-  # to avoid getting "Resource not found"
-  wait_until_associated = true
 }
diff --git a/SwitchEngines/outputs.tf b/SwitchEngines/outputs.tf
index 87c3f8461141a6cc8e49d008c9acb3229e799c16..0019196b6f6cbdf52fec3bb2055c8685cfa26627 100644
GIT binary patch
literal 122
zcmc~VEh#81QBcavD=taQOHPf?Oi@y(=2B2dNi9w;$}A|!%+FJ>RZ#MDQOHkID9K1w
zfT>jCQcx&M%qdM(fB;*C{DM@FDaqOK$@#ejr6sBHFtzbzMtX?_1@XnHMP;c)dYLI)
GwOjz~WGSrx

literal 290
zcmZQ@_Y83kiVO&0h<kqLb?lTHo-nJahxt~Vew}??^YhGiGu_|v9WG5bF%powck<@n
zn7>oY=d??6=g7BxH(~IpW2w<z{WQ8|&8}@Twf9Qw-6EC$=u7ex{<Cj&RXo_3TWg<1
zhrIi>NOp7O^3tpoi&FGW4Js;4XP9(f`(m{sVU8*L@hu0{H|+^5(g@RGQ0e#LGd}x9
z%2joKsO`cvhA+-c`=DXX_)o!UfAF%T3%SdGZ!_>o-u~S#W#_`#n`Y_we0pf;%$>B!
zb!qs2gUq!F%1cBof4nM-x$hzRA!xb(g|j}JkM9g_WD{~N;7d~5_?vP2U!{d<Yae)9
zH9vW>HdI7|VRh)^D>fI*nvClAD>f#HUfZ($Y+kge_0`7cZOZ;D<L-DoTt3GJ00tF`
Al>h($

diff --git a/SwitchEngines/scripts/add-ssh.yaml b/SwitchEngines/scripts/add-ssh.yaml
new file mode 100644
index 0000000..88c19b1
--- /dev/null
+++ b/SwitchEngines/scripts/add-ssh.yaml
@@ -0,0 +1,16 @@
+#cloud-config
+#^^^^^^^^^^^^
+# DO NOT TOUCH the first line!
+---
+groups:
+  - debian: [root, sys]
+  - terraform
+
+users:
+  - default
+  - name: terraform
+    gecos: terraform
+    primary_group: terraform
+    groups: users, admin
+    ssh_authorized_keys:
+      - <your-SSH-pub-key-on-one-line>
diff --git a/SwitchEngines/variables.tf b/SwitchEngines/variables.tf
new file mode 100644
index 0000000..7f6dfe5
--- /dev/null
+++ b/SwitchEngines/variables.tf
@@ -0,0 +1,6 @@
+# -*- mode: terraform -*-
+variable "instance_name" {
+  description = "Value of the instance's name tag"
+  type        = string
+  default     = "TF-Lab-AppServerInstance"
+}
-- 
GitLab