Add keystone charm and interface 42/9642/7 new-pipeline
authorDavid Garcia <david.garcia@canonical.com>
Thu, 27 Aug 2020 14:53:44 +0000 (16:53 +0200)
committerbeierlm <mark.beierl@canonical.com>
Wed, 9 Sep 2020 12:07:22 +0000 (14:07 +0200)
- Bundles updated
- Installer updated

Change-Id: I0f8e9aafd51e9579159f9166864eb8634292f99c
Signed-off-by: David Garcia <david.garcia@canonical.com>
15 files changed:
installers/charm/build.sh
installers/charm/bundles/osm-ha/bundle.yaml
installers/charm/bundles/osm/bundle.yaml
installers/charm/interfaces/keystone/interface.yaml [new file with mode: 0644]
installers/charm/interfaces/keystone/provides.py [new file with mode: 0644]
installers/charm/interfaces/keystone/requires.py [new file with mode: 0644]
installers/charm/keystone/.gitignore [new file with mode: 0644]
installers/charm/keystone/.yamllint.yaml [new file with mode: 0644]
installers/charm/keystone/README.md [new file with mode: 0644]
installers/charm/keystone/config.yaml [new file with mode: 0644]
installers/charm/keystone/metadata.yaml [new file with mode: 0644]
installers/charm/keystone/requirements.txt [new file with mode: 0644]
installers/charm/keystone/src/charm.py [new file with mode: 0755]
installers/charm/keystone/tox.ini [new file with mode: 0644]
installers/charmed_install.sh

index 17eea94..5d1c868 100755 (executable)
@@ -23,3 +23,6 @@ build 'nbi-k8s'
 build 'pol-k8s'
 build 'ro-k8s'
 build 'ui-k8s'
+build 'keystone'
+build 'ng-ui'
+build 'pla'
\ No newline at end of file
index ea3d7a1..8928028 100644 (file)
@@ -91,6 +91,7 @@ applications:
     options:
       log_level: "INFO"
       DATABASE_COMMONKEY: osm
+      auth-backend: keystone
     annotations:
       gui-x: 0
       gui-y: -200
@@ -214,6 +215,14 @@ applications:
     annotations:
       gui-x: 250
       gui-y: 550
+  keystone:
+    charm: '%(prefix)s/keystone%(suffix)s'
+    channel: '%(channel)s'
+    scale: 1
+    series: kubernetes
+    annotations:
+      gui-x: -250
+      gui-y: 550
 
 relations:
   - - "kafka-k8s:zookeeper"
@@ -254,3 +263,7 @@ relations:
     - "mongodb-k8s:mongo"
   - - 'ng-ui:nbi'
     - 'nbi-k8s:nbi'
+  - - 'keystone:db'
+    - 'mariadb-k8s:mysql'
+  - - 'keystone:keystone'
+    - 'nbi-k8s:keystone'
index c4567ac..a3ac408 100644 (file)
@@ -91,6 +91,7 @@ applications:
     options:
       log_level: "INFO"
       DATABASE_COMMONKEY: osm
+      auth-backend: keystone
     annotations:
       gui-x: 0
       gui-y: -200
@@ -214,6 +215,14 @@ applications:
     annotations:
       gui-x: 250
       gui-y: 550
+  keystone:
+    charm: '%(prefix)s/keystone%(suffix)s'
+    channel: '%(channel)s'
+    scale: 1
+    series: kubernetes
+    annotations:
+      gui-x: -250
+      gui-y: 550
 
 relations:
   - - "kafka-k8s:zookeeper"
@@ -254,3 +263,7 @@ relations:
     - "mongodb-k8s:mongo"
   - - 'ng-ui:nbi'
     - 'nbi-k8s:nbi'
+  - - 'keystone:db'
+    - 'mariadb-k8s:mysql'
+  - - 'keystone:keystone'
+    - 'nbi-k8s:keystone'
diff --git a/installers/charm/interfaces/keystone/interface.yaml b/installers/charm/interfaces/keystone/interface.yaml
new file mode 100644 (file)
index 0000000..be1d09b
--- /dev/null
@@ -0,0 +1,16 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+name: keystone
+summary: Keystone Interface
+version: 1
diff --git a/installers/charm/interfaces/keystone/provides.py b/installers/charm/interfaces/keystone/provides.py
new file mode 100644 (file)
index 0000000..bda5d2f
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+from charms.reactive import Endpoint
+from charms.reactive import when
+from charms.reactive import set_flag, clear_flag
+
+
+class KeystoneProvides(Endpoint):
+    @when("endpoint.{endpoint_name}.joined")
+    def _joined(self):
+        set_flag(self.expand_name("{endpoint_name}.joined"))
+
+    @when("endpoint.{endpoint_name}.changed")
+    def _changed(self):
+        set_flag(self.expand_name("{endpoint_name}.ready"))
+
+    @when("endpoint.{endpoint_name}.departed")
+    def _departed(self):
+        set_flag(self.expand_name("{endpoint_name}.departed"))
+        clear_flag(self.expand_name("{endpoint_name}.joined"))
+
+    def publish_info(
+        self,
+        host,
+        port,
+        keystone_db_password,
+        region_id,
+        user_domain_name,
+        project_domain_name,
+        admin_username,
+        admin_password,
+        admin_project_name,
+        username,
+        password,
+        service,
+    ):
+        for relation in self.relations:
+            relation.to_publish["host"] = host
+            relation.to_publish["port"] = port
+            relation.to_publish["keystone_db_password"] = keystone_db_password
+            relation.to_publish["region_id"] = region_id
+            relation.to_publish["user_domain_name"] = user_domain_name
+            relation.to_publish["project_domain_name"] = project_domain_name
+            relation.to_publish["admin_username"] = admin_username
+            relation.to_publish["admin_password"] = admin_password
+            relation.to_publish["admin_project_name"] = admin_project_name
+            relation.to_publish["username"] = username
+            relation.to_publish["password"] = password
+            relation.to_publish["service"] = service
+
+    def mark_complete(self):
+        clear_flag(self.expand_name("{endpoint_name}.joined"))
diff --git a/installers/charm/interfaces/keystone/requires.py b/installers/charm/interfaces/keystone/requires.py
new file mode 100644 (file)
index 0000000..c0d8d47
--- /dev/null
@@ -0,0 +1,72 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+from charms.reactive import Endpoint
+from charms.reactive import when
+from charms.reactive import set_flag, clear_flag
+
+
+class KeystoneRequires(Endpoint):
+    @when("endpoint.{endpoint_name}.joined")
+    def _joined(self):
+        set_flag(self.expand_name("{endpoint_name}.joined"))
+
+    @when("endpoint.{endpoint_name}.changed")
+    def _changed(self):
+        if len(self.keystones()) > 0:
+            set_flag(self.expand_name("{endpoint_name}.ready"))
+        else:
+            clear_flag(self.expand_name("{endpoint_name}.ready"))
+
+    @when("endpoint.{endpoint_name}.departed")
+    def _departed(self):
+        set_flag(self.expand_name("{endpoint_name}.departed"))
+        clear_flag(self.expand_name("{endpoint_name}.joined"))
+        clear_flag(self.expand_name("{endpoint_name}.ready"))
+
+    def keystones(self):
+        """
+        Return Keystone Data:
+        [{
+            'host': <host>,
+            'port': <port>,
+            'keystone_db_password: <keystone_db_password>,
+            'region_id: <region_id>,
+            'admin_username: <admin_username>,
+            'admin_password: <admin_password>,
+            'admin_project_name: <admin_project_name>,
+            'username: <username>,
+            'password: <password>,
+            'service: <service>
+        }]
+        """
+        keystones = []
+        for relation in self.relations:
+            for unit in relation.units:
+                data = {
+                    "host": unit.received["host"],
+                    "port": unit.received["port"],
+                    "keystone_db_password": unit.received["keystone_db_password"],
+                    "region_id": unit.received["region_id"],
+                    "user_domain_name": unit.received["user_domain_name"],
+                    "project_domain_name": unit.received["project_domain_name"],
+                    "admin_username": unit.received["admin_username"],
+                    "admin_password": unit.received["admin_password"],
+                    "admin_project_name": unit.received["admin_project_name"],
+                    "username": unit.received["username"],
+                    "password": unit.received["password"],
+                    "service": unit.received["service"],
+                }
+                if all(data.values()):
+                    keystones.append(data)
+        return keystones
diff --git a/installers/charm/keystone/.gitignore b/installers/charm/keystone/.gitignore
new file mode 100644 (file)
index 0000000..2545cca
--- /dev/null
@@ -0,0 +1,16 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+.vscode
+build
+keystone.charm
\ No newline at end of file
diff --git a/installers/charm/keystone/.yamllint.yaml b/installers/charm/keystone/.yamllint.yaml
new file mode 100644 (file)
index 0000000..08ab437
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+---
+extends: default
+
+yaml-files:
+  - "*.yaml"
+  - "*.yml"
+  - ".yamllint"
+ignore: |
+  .tox
+  build/
+  mod/
+  lib/
diff --git a/installers/charm/keystone/README.md b/installers/charm/keystone/README.md
new file mode 100644 (file)
index 0000000..1ca9764
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- Copyright 2020 Canonical Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License. -->
+# Keystone operator Charm for Kubernetes
+
+## Requirements
+
diff --git a/installers/charm/keystone/config.yaml b/installers/charm/keystone/config.yaml
new file mode 100644 (file)
index 0000000..c99d878
--- /dev/null
@@ -0,0 +1,117 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+options:
+  image:
+    type: string
+    default: opensourcemano/keystone:latest
+    description: The docker image to install.
+  image_username:
+    type: string
+    description: |
+      The username for accessing the registry specified in image.
+    default: ""
+  image_password:
+    type: string
+    description: |
+      The password associated with image_username for accessing
+      the registry specified in image.
+    default: ""
+  max_file_size:
+    type: int
+    description: |
+      The maximum file size, in megabytes.
+
+      If there is a reverse proxy in front of Keystone, it may
+      need to be configured to handle the requested size.
+    default: 5
+  ingress_whitelist_source_range:
+    type: string
+    description: |
+      A comma-separated list of CIDRs to store in the
+      ingress.kubernetes.io/whitelist-source-range annotation.
+
+      This can be used to lock down access to
+      Keystone based on source IP address.
+    default: ""
+  tls_secret_name:
+    type: string
+    description: TLS Secret name
+    default: ""
+  site_url:
+    type: string
+    description: Ingress URL
+    default: ""
+  ldap_enabled:
+    type: boolean
+    description: Boolean to enable/disable LDAP authentication
+    default: false
+  region_id:
+    type: string
+    description: Region ID to be created when starting the service
+    default: RegionOne
+  keystone_db_password:
+    type: string
+    description: Keystone DB Password
+    default: admin
+  admin_username:
+    type: string
+    description: Admin username to be created when starting the service
+    default: admin
+  admin_password:
+    type: string
+    description: Admin password to be created when starting the service
+    default: admin
+  admin_project:
+    type: string
+    description: Admin project to be created when starting the service
+    default: admin
+  service_username:
+    type: string
+    description: Service Username to be created when starting the service
+    default: nbi
+  service_password:
+    type: string
+    description: Service Password to be created when starting the service
+    default: nbi
+  service_project:
+    type: string
+    description: Service Project to be created when starting the service
+    default: service
+  user_domain_name:
+    type: string
+    description: User domain name (Hardcoded in the container start.sh script)
+    default: default
+  project_domain_name:
+    type: string
+    description: |
+      Project domain name (Hardcoded in the container start.sh script)
+    default: default
+
+    # ENV LDAP_AUTHENTICATION_DOMAIN_NAME     no default
+    # ENV LDAP_URL                            ldap://localhost
+    # ENV LDAP_BIND_USER                      no defauslt
+    # ENV LDAP_BIND_PASSWORD                  no default
+    # ENV LDAP_USER_TREE_DN                   no default
+    # ENV LDAP_USER_OBJECTCLASS               inetOrgPerson
+    # ENV LDAP_USER_ID_ATTRIBUTE              cn
+    # ENV LDAP_USER_NAME_ATTRIBUTE            sn
+    # ENV LDAP_USER_PASS_ATTRIBUTE            userPassword
+    # ENV LDAP_USER_FILTER                    no default
+    # ENV LDAP_USER_ENABLED_ATTRIBUTE         enabled
+    # ENV LDAP_USER_ENABLED_MASK              0
+    # ENV LDAP_USER_ENABLED_DEFAULT           true
+    # ENV LDAP_USER_ENABLED_INVERT            false
+    # ENV LDAP_USE_STARTTLS                   false
+    # ENV LDAP_TLS_CACERT_BASE64              no default
+    # ENV LDAP_TLS_REQ_CERT                   demand
diff --git a/installers/charm/keystone/metadata.yaml b/installers/charm/keystone/metadata.yaml
new file mode 100644 (file)
index 0000000..eb3c8ef
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+name: keystone
+summary: A Keystone K8s charm
+description: |
+    Transmission
+series:
+    - kubernetes
+min-juju-version: 2.8.0
+requires:
+    db:
+        interface: mysql
+        limit: 1
+provides:
+    keystone:
+        interface: keystone
+deployment:
+    type: stateless
+    service: cluster
diff --git a/installers/charm/keystone/requirements.txt b/installers/charm/keystone/requirements.txt
new file mode 100644 (file)
index 0000000..10ecdcd
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+ops
diff --git a/installers/charm/keystone/src/charm.py b/installers/charm/keystone/src/charm.py
new file mode 100755 (executable)
index 0000000..632e96a
--- /dev/null
@@ -0,0 +1,245 @@
+#!/usr/bin/env python3
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+
+import logging
+
+from urllib.parse import urlparse
+
+from ops.charm import CharmBase
+
+# from ops.framework import StoredState
+from ops.main import main
+from ops.model import (
+    ActiveStatus,
+    BlockedStatus,
+    # MaintenanceStatus,
+    WaitingStatus,
+    # ModelError,
+)
+from ops.framework import StoredState
+
+logger = logging.getLogger(__name__)
+
+REQUIRED_SETTINGS = []
+
+DATABASE_NAME = "keystone"  # This is hardcoded in the keystone container script
+# We expect the keystone container to use the default port
+KEYSTONE_PORT = 5000
+
+
+class KeystoneCharm(CharmBase):
+
+    state = StoredState()
+
+    def __init__(self, *args):
+        super().__init__(*args)
+
+        # Register all of the events we want to observe
+        self.framework.observe(self.on.config_changed, self.configure_pod)
+        self.framework.observe(self.on.start, self.configure_pod)
+        self.framework.observe(self.on.upgrade_charm, self.configure_pod)
+
+        # Register relation events
+        self.state.set_default(
+            db_host=None, db_port=None, db_user=None, db_password=None
+        )
+        self.framework.observe(
+            self.on.db_relation_changed, self._on_db_relation_changed
+        )
+        self.framework.observe(
+            self.on.keystone_relation_joined, self._publish_keystone_info
+        )
+
+    def _publish_keystone_info(self, event):
+        config = self.model.config
+        if self.unit.is_leader():
+            rel_data = {
+                "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
+                "port": str(KEYSTONE_PORT),
+                "keystone_db_password": config["keystone_db_password"],
+                "region_id": config["region_id"],
+                "user_domain_name": config["user_domain_name"],
+                "project_domain_name": config["project_domain_name"],
+                "admin_username": config["admin_username"],
+                "admin_password": config["admin_password"],
+                "admin_project_name": config["admin_project"],
+                "username": config["service_username"],
+                "password": config["service_password"],
+                "service": config["service_project"],
+            }
+            for k, v in rel_data.items():
+                event.relation.data[self.model.unit][k] = v
+
+    def _on_db_relation_changed(self, event):
+        self.state.db_host = event.relation.data[event.unit].get("host")
+        self.state.db_port = event.relation.data[event.unit].get("port", 3306)
+        self.state.db_user = "root"  # event.relation.data[event.unit].get("user")
+        self.state.db_password = event.relation.data[event.unit].get("root_password")
+        if self.state.db_host:
+            self.configure_pod(event)
+
+    def _check_settings(self):
+        problems = []
+        config = self.model.config
+
+        for setting in REQUIRED_SETTINGS:
+            if not config.get(setting):
+                problem = f"missing config {setting}"
+                problems.append(problem)
+
+        return ";".join(problems)
+
+    def _make_pod_image_details(self):
+        config = self.model.config
+        image_details = {
+            "imagePath": config["image"],
+        }
+        if config["image_username"]:
+            image_details.update(
+                {
+                    "username": config["image_username"],
+                    "password": config["image_password"],
+                }
+            )
+        return image_details
+
+    def _make_pod_ports(self):
+        return [
+            {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
+        ]
+
+    def _make_pod_envconfig(self):
+        config = self.model.config
+
+        return {
+            "DB_HOST": self.state.db_host,
+            "DB_PORT": self.state.db_port,
+            "ROOT_DB_USER": self.state.db_user,
+            "ROOT_DB_PASSWORD": self.state.db_password,
+            "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
+            "REGION_ID": config["region_id"],
+            "KEYSTONE_HOST": self.app.name,
+            "ADMIN_USERNAME": config["admin_username"],
+            "ADMIN_PASSWORD": config["admin_password"],
+            "ADMIN_PROJECT": config["admin_project"],
+            "SERVICE_USERNAME": config["service_username"],
+            "SERVICE_PASSWORD": config["service_password"],
+            "SERVICE_PROJECT": config["service_project"],
+        }
+
+    def _make_pod_ingress_resources(self):
+        site_url = self.model.config["site_url"]
+
+        if not site_url:
+            return
+
+        parsed = urlparse(site_url)
+
+        if not parsed.scheme.startswith("http"):
+            return
+
+        max_file_size = self.model.config["max_file_size"]
+        ingress_whitelist_source_range = self.model.config[
+            "ingress_whitelist_source_range"
+        ]
+
+        annotations = {
+            "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
+        }
+
+        if ingress_whitelist_source_range:
+            annotations[
+                "nginx.ingress.kubernetes.io/whitelist-source-range"
+            ] = ingress_whitelist_source_range
+
+        ingress_spec_tls = None
+
+        if parsed.scheme == "https":
+            ingress_spec_tls = [{"hosts": [parsed.hostname]}]
+            tls_secret_name = self.model.config["tls_secret_name"]
+            if tls_secret_name:
+                ingress_spec_tls[0]["secretName"] = tls_secret_name
+        else:
+            annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
+
+        ingress = {
+            "name": "{}-ingress".format(self.app.name),
+            "annotations": annotations,
+            "spec": {
+                "rules": [
+                    {
+                        "host": parsed.hostname,
+                        "http": {
+                            "paths": [
+                                {
+                                    "path": "/",
+                                    "backend": {
+                                        "serviceName": self.app.name,
+                                        "servicePort": KEYSTONE_PORT,
+                                    },
+                                }
+                            ]
+                        },
+                    }
+                ],
+            },
+        }
+        if ingress_spec_tls:
+            ingress["spec"]["tls"] = ingress_spec_tls
+
+        return [ingress]
+
+    def configure_pod(self, event):
+        """Assemble the pod spec and apply it, if possible."""
+
+        if not self.state.db_host:
+            self.unit.status = WaitingStatus("Waiting for database relation")
+            event.defer()
+            return
+
+        if not self.unit.is_leader():
+            self.unit.status = ActiveStatus()
+            return
+
+        # Check problems in the settings
+        problems = self._check_settings()
+        if problems:
+            self.unit.status = BlockedStatus(problems)
+            return
+
+        self.unit.status = BlockedStatus("Assembling pod spec")
+        image_details = self._make_pod_image_details()
+        ports = self._make_pod_ports()
+        env_config = self._make_pod_envconfig()
+        ingress_resources = self._make_pod_ingress_resources()
+
+        pod_spec = {
+            "version": 3,
+            "containers": [
+                {
+                    "name": self.framework.model.app.name,
+                    "imageDetails": image_details,
+                    "ports": ports,
+                    "envConfig": env_config,
+                }
+            ],
+            "kubernetesResources": {"ingressResources": ingress_resources or []},
+        }
+        self.model.pod.set_spec(pod_spec)
+        self.unit.status = ActiveStatus()
+
+
+if __name__ == "__main__":
+    main(KeystoneCharm)
diff --git a/installers/charm/keystone/tox.ini b/installers/charm/keystone/tox.ini
new file mode 100644 (file)
index 0000000..cff5193
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+[tox]
+envlist = pep8
+skipsdist = True
+
+[testenv]
+setenv = VIRTUAL_ENV={envdir}
+         PYTHONHASHSEED=0
+install_command =
+  pip install {opts} {packages}
+
+[testenv:build]
+basepython = python3
+passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
+whitelist_externals = charmcraft
+                      rm
+                      unzip
+commands =
+    rm -rf release
+    charmcraft build
+    unzip keystone.charm -d release
+
+[testenv:lint]
+basepython = python3
+deps =
+    black
+    yamllint
+    flake8
+commands =
+    black --check --diff . --exclude "build/|.tox/|mod/|lib/"
+    yamllint .
+    flake8 . --max-line-length=100 --exclude "build/ .tox/ mod/ lib/"
+
+[testenv:venv]
+commands = {posargs}
index 8900270..89b85cc 100755 (executable)
@@ -324,6 +324,9 @@ applications:
   ng-ui:
     options:
       image: opensourcemano/ng-ui:$TAG
+  keystone:
+    options:
+      image: opensourcemano/keystone:$TAG
 
 EOF
     mv /tmp/images-overlay.yaml $IMAGES_OVERLAY_FILE