Finalize configuring power instances

Configuration API is bit different than the regular VPC instances, so we they
ended up a separate hard-coded config environment named "power".
diff --git a/README.md b/README.md
index 421bbc1..a19e9cd 100644
--- a/README.md
+++ b/README.md
@@ -76,8 +76,20 @@
 
 Create a `~/.couchdb-infra-cm.cfg` file that contains the following options:
 
-    [ibmcloud]
+    [ibmcloud.<environment>]
     api_key = <REDACTED>
+    api_url = https://us-south.iaas.cloud.ibm.com/v1
+    crn = crn:v1:...
+    instance_id = 123-abc...
+
+
+`<environment>` is a tag used to differentiate multiple environments. It allows
+fetching instances from more than one IBM Cloud accounts. If `api_url` is
+provided, it will be used to fetch VPC instances. By default is uses
+`"https://us-south.iaas.cloud.ibm.com/v1"`. The `crn` field will be added as a
+`CRN: <crn>` header if provided. `instance_id` is used only by the `power`
+environment. (See `Power Instances` section for more details).
+
 
 The `tools/gen-config` script can then be used to generate our `production`
 inventory and `ssh.cfg` configuration:
@@ -115,3 +127,23 @@
 ```bash
 $ ssh -F ssh.cfg couchdb-worker-x86-64-debian-dal-1-01
 ```
+
+
+Power Instances
+---
+
+Power isntances are configured in a special hard-coded `power` environment.
+This name is hard-coded. The `power` environemnt section, besides the
+`api_key`, should contain additional fields:
+
+```ini
+[ibmcloud.power]
+api_key = ...
+api_url = https://lon.power-iaas.cloud.ibm.com/pcloud/v1/cloud-instances/<cloud_instance_id>
+crn = ...
+instance_id = ...
+```
+
+All those might have to be obtained after the instance is provisioned from the
+team which provisioned them.
+
diff --git a/tools/gen-config b/tools/gen-config
index e1e0932..0a6d708 100755
--- a/tools/gen-config
+++ b/tools/gen-config
@@ -17,7 +17,7 @@
 #
 ENV = {}
 
-IBM_CLOUD_URL = "https://us-south.iaas.cloud.ibm.com/v1/"
+IBM_CLOUD_URL = "https://us-south.iaas.cloud.ibm.com/v1"
 IAM_URL = "https://iam.cloud.ibm.com/identity/token"
 
 IBM_CLOUD_GENERATION = "2"
@@ -59,7 +59,10 @@
             "api_generation" : parser.get(section, "api_generation",
                                           fallback=IBM_CLOUD_GENERATION),
             "api_version" : parser.get(section, "api_version",
-                                       fallback=IBM_CLOUD_VERSION)
+                                       fallback=IBM_CLOUD_VERSION),
+            "crn" : parser.get(section, "crn", fallback=None),
+            "instance_id" : parser.get(section, "instance_id",
+                                       fallback=None)
         }
 
 
@@ -78,6 +81,8 @@
         body = resp.json()
         token = body["token_type"] + " " + body["access_token"]
         sess.headers["Authorization"] = token
+        for hk, hv in env_headers(env).items():
+            sess.headers[hk] = hv
         SESS[env] = sess
 
 
@@ -88,8 +93,10 @@
 
 def list_instances():
     for env in ENV:
-        yield from list_x86_instances(env)
-        #yield from list_power_instances(env)
+        if env != "power":
+            yield from list_x86_instances(env)
+        if env == "power":
+            yield from list_power_instances(env)
 
 
 def list_x86_instances(env):
@@ -107,25 +114,35 @@
 
 
 def list_power_instances(env):
-    url = ENV[env]["api_url"] + "/pvmInstances"
+    # It's really just one instance but to keep up with the shape of the rest
+    # of the code emit it as a generated item
+    if not ENV[env]["instance_id"]:
+        Exception("Power instances require an 'instance_id' setting")
+
+    instance_id = ENV[env]["instance_id"]
+    url = ENV[env]["api_url"] + "/pvm-instances/" + instance_id
     sess = SESS[env]
-    while url:
-        resp = sess.get(url, params=params(env))
-        body = resp.json()
-        for instance in body["instances"]:
-            interface_url = instance["primary_network_interface"]["href"]
-            resp = sess.get(interface_url, params=params(env))
-            instance["primary_network_interface"] = resp.json()
-            yield instance
-        url = body.get("next")
+    resp = sess.get(url, params=params(env))
+    instance = resp.json()
+    instance["name"] = instance["serverName"]
+    yield instance
+
 
 
 def params(env):
-    return {
-        "version": ENV[env]["api_version"],
-        "generation" : ENV[env]["api_generation"],
-        "limit": 100
-    }
+    params = {"limit": 100}
+    if ENV[env].get("api_version"):
+        params["version"] = ENV[env]["api_version"]
+    if ENV[env].get("api_generation"):
+        params["generation"] = ENV[env]["api_generation"]
+    return params
+
+
+def env_headers(env):
+    headers = {}
+    if ENV[env].get("crn"):
+        headers["crn"] = ENV[env]["crn"]
+    return headers
 
 
 def load_bastion(bastions, instance):
@@ -200,6 +217,38 @@
     }
 
 
+def load_power_ci_agent(ci_agents, instance):
+    if instance["status"] != "ACTIVE":
+        return
+
+    name = instance["name"]
+    if name in ci_agents:
+        print(f"Duplicate ci_agent found {name}")
+        exit(2)
+
+    ci_agents[name] = {
+        "instance": {
+            "id": instance["pvmInstanceID"],
+            "name": instance["serverName"],
+            "created_at": instance["creationDate"],
+            "profile": instance["procType"],
+            "vpc" : instance["sysType"],
+            "zone": instance["placementGroup"],
+            "subnet" : None
+        },
+        "ip_addrs": {
+            "bastion": None,
+            "public": instance["addresses"][0]["externalIP"],
+            "private": instance["addresses"][0]["ip"]
+        },
+        "system": {
+            "arch": "power",
+            "num_cpu": instance["virtualCores"]["assigned"],
+            "ram": instance["memory"]
+        }
+    }
+
+
 def get_private_ip(instance):
     ip = instance["primary_network_interface"]["primary_ipv4_address"]
     if ip:
@@ -220,6 +269,9 @@
         assert subnet not in subnets
         subnets[subnet] = ip_addr
     for (host, ci_agent) in ci_agents.items():
+        if ci_agent["system"]["arch"] == "power":
+            # Power systems use an external IP without bastions
+            continue
         subnet = ci_agent["instance"]["subnet"]
         assert subnet in subnets
         ci_agent["ip_addrs"]["bastion"] = subnets[subnet]
@@ -273,12 +325,20 @@
             entry = bastion_tmpl.format(**args)
             handle.write(entry)
         for host, info in sorted(ci_agents.items()):
-            args = {
-                "host": host,
-                "ip_addr": info["ip_addrs"]["private"],
-                "bastion_ip": info["ip_addrs"]["bastion"]
-            }
-            entry = ci_agent_tmpl.format(**args)
+            if info["system"]["arch"] == "power":
+                # Power systems use an external IP directly
+                args = {
+                    "host": host,
+                    "ip_addr": info["ip_addrs"]["public"]
+                }
+                entry = bastion_tmpl.format(**args)
+            else:
+                args = {
+                    "host": host,
+                    "ip_addr": info["ip_addrs"]["private"],
+                    "bastion_ip": info["ip_addrs"]["bastion"]
+                }
+                entry = ci_agent_tmpl.format(**args)
             handle.write(entry)
 
 
@@ -314,6 +374,8 @@
             load_bastion(bastions, instance)
         elif instance["name"].startswith("couchdb-worker"):
             load_ci_agent(ci_agents, instance)
+        elif instance["name"].startswith("couchdb-ci"):
+            load_power_ci_agent(ci_agents, instance)
 
     assign_bastions(bastions, ci_agents)