Feature 11073. Enhanced OSM declarative modelling for applications. App as first class citizen
Change-Id: I1edf6f674170f2c00d62c69cbba46a2bda401e0f
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
diff --git a/osmclient/cli_commands/app.py b/osmclient/cli_commands/app.py
new file mode 100755
index 0000000..98811fd
--- /dev/null
+++ b/osmclient/cli_commands/app.py
@@ -0,0 +1,330 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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
+import click
+import yaml
+from osmclient.cli_commands import common, utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common import print_output
+
+logger = logging.getLogger("osmclient")
+
+
+def get_oka_id(ctx, oka_name):
+ """Get the ID of an OKA by name or ID"""
+ logger.debug("")
+ resp = ctx.obj.oka.get(oka_name)
+ logger.debug("OKA obtained: %s", resp)
+ if "_id" in resp:
+ return resp["_id"]
+ else:
+ raise ClientException("Unexpected failure when reading the OKA")
+
+
+def get_profile_id(ctx, profile, profile_type):
+ """Get the ID of a profile of a specifc profile_type by name or ID"""
+ logger.debug("")
+ get_function = {
+ "infra-controller-profile": ctx.obj.infra_controller_profile.get,
+ "infra-config-profile": ctx.obj.infra_config_profile.get,
+ "app-profile": ctx.obj.app_profile.get,
+ "resource-profile": ctx.obj.resource_profile.get,
+ }
+ resp = get_function[profile_type](profile)
+ logger.debug("Profile obtained: %s", resp)
+ if "_id" in resp:
+ return resp["_id"]
+ else:
+ raise ClientException("Unexpected failure when reading the profile")
+
+
+@click.command(name="app-create", short_help="creates an App Instance")
+@utils.require_hostname
+@click.argument("name")
+@click.option("--description", default="", help="human readable description")
+@click.option(
+ "--oka",
+ default="",
+ help="name or ID of the OKA that the App Instance will be based on",
+)
+@click.option(
+ "--sw-catalog-path",
+ default="",
+ help="folder in the SW catalog (git repo) that the App Instance will be based on",
+)
+@click.option(
+ "--profile",
+ default="",
+ help="name or ID of the profile the App Instance will belong to",
+)
+@click.option(
+ "--profile-type",
+ type=click.Choice(
+ [
+ "infra-controller-profile",
+ "infra-config-profile",
+ "app-profile",
+ "resource-profile",
+ ]
+ ),
+ default="app-profile",
+ help="type of profile. Default: app-profile",
+)
+@click.option(
+ "--model",
+ default=None,
+ help="specific yaml file with App Instance model file (to be merged with App blueprint model file)",
+)
+@click.option(
+ "--params",
+ default=None,
+ help="specific yaml file with clear params for the App Instance",
+)
+@click.option(
+ "--secret-params",
+ default=None,
+ help="specific yaml file with secret params for the App Instance",
+)
+@click.pass_context
+def app_create(
+ ctx,
+ name,
+ description,
+ oka,
+ sw_catalog_path,
+ profile,
+ profile_type,
+ model,
+ params,
+ secret_params,
+):
+ """creates an App Instance
+
+ NAME: name of the App Instance to be created
+ """
+ logger.debug("")
+ profile_type_mapping = {
+ "infra-controller-profile": "infra_controller_profiles",
+ "infra-config-profile": "infra_config_profiles",
+ "app-profile": "app_profiles",
+ "resource-profile": "resource_profiles",
+ }
+ app = {}
+ app["name"] = name
+ if description:
+ app["description"] = description
+ if oka and sw_catalog_path:
+ raise ClientException(
+ '"--oka" option is incompatible with "--sw-catalog-path" option'
+ )
+ if oka:
+ app["oka"] = get_oka_id(ctx, oka)
+ if sw_catalog_path:
+ app["sw_catalog_path"] = sw_catalog_path
+ app["profile_type"] = profile_type_mapping[profile_type]
+ app["profile"] = get_profile_id(ctx, profile, profile_type)
+ if model:
+ with open(model, "r", encoding="utf-8") as cf:
+ model_data = cf.read()
+ try:
+ app["model"] = yaml.safe_load(model_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ if params:
+ with open(params, "r", encoding="utf-8") as cf:
+ params_data = cf.read()
+ try:
+ app["params"] = yaml.safe_load(params_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ if secret_params:
+ with open(secret_params, "r", encoding="utf-8") as cf:
+ secret_params_data = cf.read()
+ try:
+ app["secret_params"] = yaml.safe_load(secret_params_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ ctx.obj.app.create(name, content_dict=app)
+
+
+@click.command(name="app-delete", short_help="deletes an App Instance cluster")
+@utils.require_hostname
+@click.argument("name")
+@click.option(
+ "--force", is_flag=True, help="forces the deletion from the DB (not recommended)"
+)
+@click.pass_context
+def app_delete(ctx, name, force):
+ """deletes an App Instance
+
+ NAME: name or ID of the App Instance to be deleted
+ """
+ logger.debug("")
+ ctx.obj.app.delete(name, force=force)
+
+
+@click.command(name="app-list")
+@utils.require_hostname
+@click.option(
+ "--filter",
+ help="restricts the list to the items matching the filter",
+)
+@print_output.output_option
+@click.pass_context
+def app_list(ctx, filter, output):
+ """list all App instances"""
+ logger.debug("")
+ common.generic_list(callback=ctx.obj.app.list, filter=filter, format=output)
+
+
+@click.command(name="app-show", short_help="shows the details of an app instance")
+@utils.require_hostname
+@click.argument("name")
+@print_output.output_option
+@click.pass_context
+def app_show(ctx, name, output):
+ """shows the details of an App instance
+
+ NAME: name or ID of the App instance to be shown
+ """
+ logger.debug("")
+ common.generic_show(callback=ctx.obj.app.get, name=name, format=output)
+
+
+@click.command(
+ name="app-edit", short_help="updates name or description of an App Instance"
+)
+@utils.require_hostname
+@click.argument("name")
+@click.option("--newname", help="New name for the App Instance")
+@click.option("--description", help="human readable description")
+@click.pass_context
+def app_edit(ctx, name, newname, description, **kwargs):
+ """updates the name or description of an App Instance
+
+ NAME: name or ID of the App Instance to be updated
+ """
+ logger.debug("")
+ app_changes = common.generic_update(newname, description, kwargs)
+ ctx.obj.app.update(name, changes_dict=app_changes)
+
+
+@click.command(name="app-update", short_help="updates an App Instance")
+@utils.require_hostname
+@click.argument("name")
+@click.option("--newname", help="New name for the App Instance")
+@click.option("--description", default="", help="human readable description")
+@click.option(
+ "--oka",
+ default="",
+ help="name or ID of the OKA that the App Instance will be based on",
+)
+@click.option(
+ "--sw-catalog-path",
+ default="",
+ help="folder in the SW catalog (git repo) that the App Instance will be based on",
+)
+@click.option(
+ "--profile",
+ default="",
+ help="name or ID of the profile the App Instance will belong to",
+)
+@click.option(
+ "--profile-type",
+ type=click.Choice(
+ [
+ "infra-controller-profile",
+ "infra-config-profile",
+ "app-profile",
+ "resource-profile",
+ ]
+ ),
+ default="app-profile",
+ help="type of profile. Default: app-profile",
+)
+@click.option(
+ "--model",
+ default=None,
+ help="specific yaml file with App Instance model file (to be merged with App blueprint model file)",
+)
+@click.option(
+ "--params",
+ default=None,
+ help="specific yaml file with clear params for the App Instance",
+)
+@click.option(
+ "--secret-params",
+ default=None,
+ help="specific yaml file with secret params for the App Instance",
+)
+@click.pass_context
+def app_update(
+ ctx,
+ name,
+ oka,
+ sw_catalog_path,
+ profile,
+ profile_type,
+ model,
+ params,
+ secret_params,
+):
+ """updates an App Instance
+
+ NAME: name or ID of the App Instance to be updated
+ """
+ logger.debug("")
+ app_changes = {}
+ profile_type_mapping = {
+ "infra-controller-profile": "infra_controller_profiles",
+ "infra-config-profile": "infra_config_profiles",
+ "app-profile": "app_profiles",
+ "resource-profile": "resource_profiles",
+ }
+ if oka and sw_catalog_path:
+ raise ClientException(
+ '"--oka" option is incompatible with "--sw-catalog-path" option'
+ )
+ if oka:
+ app_changes["oka"] = get_oka_id(ctx, oka)
+ if sw_catalog_path:
+ app_changes["sw_catalog_path"] = sw_catalog_path
+ app_changes["profile_type"] = profile_type_mapping[profile_type]
+ app_changes["profile"] = get_profile_id(ctx, profile, profile_type)
+ if model:
+ with open(model, "r", encoding="utf-8") as cf:
+ model_data = cf.read()
+ try:
+ app_changes["model"] = yaml.safe_load(model_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ if params:
+ with open(params, "r", encoding="utf-8") as cf:
+ params_data = cf.read()
+ try:
+ app_changes["params"] = yaml.safe_load(params_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ if secret_params:
+ with open(secret_params, "r", encoding="utf-8") as cf:
+ secret_params_data = cf.read()
+ try:
+ app_changes["secret_params"] = yaml.safe_load(secret_params_data)
+ except yaml.YAMLError as e:
+ raise ClientException(f"Error parsing YAML configuration: {e}") from e
+ ctx.obj.app.fullupdate(name, app_changes)
diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py
index b63d660..323a5b6 100755
--- a/osmclient/scripts/osm.py
+++ b/osmclient/scripts/osm.py
@@ -18,6 +18,7 @@
from osmclient.common.exceptions import ClientException, NotFound
from osmclient.cli_commands import (
alarms,
+ app,
app_profile,
cluster,
infra_config_profile,
@@ -179,6 +180,13 @@
cli_osm.add_command(ksu.ksu_show)
cli_osm.add_command(ksu.ksu_update)
+ cli_osm.add_command(app.app_create)
+ cli_osm.add_command(app.app_delete)
+ cli_osm.add_command(app.app_edit)
+ cli_osm.add_command(app.app_list)
+ cli_osm.add_command(app.app_show)
+ cli_osm.add_command(app.app_update)
+
cli_osm.add_command(cluster.cluster_create)
cli_osm.add_command(cluster.cluster_delete)
cli_osm.add_command(cluster.cluster_list)
diff --git a/osmclient/sol005/app.py b/osmclient/sol005/app.py
new file mode 100644
index 0000000..addb32e
--- /dev/null
+++ b/osmclient/sol005/app.py
@@ -0,0 +1,43 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+
+"""
+OSM App Instaance API handling
+"""
+
+from osmclient.sol005.osm_api_object import GenericOSMAPIObject
+
+
+class AppInstance(GenericOSMAPIObject):
+ def __init__(self, http=None, client=None):
+ super().__init__(http, client)
+ self._apiName = "/appinstance"
+ self._apiVersion = "/v1"
+ self._apiResource = "/appinstances"
+ self._logObjectName = "appinstance"
+ self._apiBase = "{}{}{}".format(
+ self._apiName, self._apiVersion, self._apiResource
+ )
+
+ def fullupdate(self, name, app_changes):
+ """
+ Updates an App Instance
+ """
+ self._logger.debug("")
+ item = self.get(name)
+ endpoint_suffix = f"{item['_id']}/update"
+ self.generic_operation(app_changes, endpoint_suffix=endpoint_suffix)
diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py
index bbe0536..0d90727 100644
--- a/osmclient/sol005/client.py
+++ b/osmclient/sol005/client.py
@@ -42,6 +42,7 @@
from osmclient.common import package_tool
from osmclient.sol005 import oka
from osmclient.sol005 import ksu
+from osmclient.sol005 import app
from osmclient.sol005 import infra_controller_profile
from osmclient.sol005 import infra_config_profile
from osmclient.sol005 import app_profile
@@ -113,6 +114,7 @@
self.package_tool = package_tool.PackageTool(client=self)
self.subscription = subscription.Subscription(self._http_client, client=self)
self.ksu = ksu.KSU(self._http_client, client=self)
+ self.app = app.AppInstance(self._http_client, client=self)
self.oka = oka.OKA(self._http_client, client=self)
self.infra_controller_profile = infra_controller_profile.InfraControllerProfile(
self._http_client, client=self