build 'pol-k8s'
build 'ro-k8s'
build 'ui-k8s'
+build 'keystone'
+build 'ng-ui'
+build 'pla'
\ No newline at end of file
options:
log_level: "INFO"
DATABASE_COMMONKEY: osm
+ auth-backend: keystone
annotations:
gui-x: 0
gui-y: -200
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"
- "mongodb-k8s:mongo"
- - 'ng-ui:nbi'
- 'nbi-k8s:nbi'
+ - - 'keystone:db'
+ - 'mariadb-k8s:mysql'
+ - - 'keystone:keystone'
+ - 'nbi-k8s:keystone'
options:
log_level: "INFO"
DATABASE_COMMONKEY: osm
+ auth-backend: keystone
annotations:
gui-x: 0
gui-y: -200
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"
- "mongodb-k8s:mongo"
- - 'ng-ui:nbi'
- 'nbi-k8s:nbi'
+ - - 'keystone:db'
+ - 'mariadb-k8s:mysql'
+ - - 'keystone:keystone'
+ - 'nbi-k8s:keystone'
--- /dev/null
+# 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
--- /dev/null
+# 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"))
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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/
--- /dev/null
+<!-- 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
+
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+#!/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)
--- /dev/null
+# 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}
ng-ui:
options:
image: opensourcemano/ng-ui:$TAG
+ keystone:
+ options:
+ image: opensourcemano/keystone:$TAG
EOF
mv /tmp/images-overlay.yaml $IMAGES_OVERLAY_FILE