diff --git a/senlinclient/common/http.py b/senlinclient/common/http.py new file mode 100644 index 00000000..08195f0c --- /dev/null +++ b/senlinclient/common/http.py @@ -0,0 +1,11 @@ +# 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. diff --git a/senlinclient/exc.py b/senlinclient/exc.py index a1e7bb18..84661d88 100644 --- a/senlinclient/exc.py +++ b/senlinclient/exc.py @@ -10,16 +10,15 @@ # License for the specific language governing permissions and limitations # under the License. -import sys - class BaseException(Exception): - """An error occurred.""" + '''An error occurred.''' def __init__(self, message=None): self.message = message def __str__(self): return self.message or self.__class__.__doc__ + class CommandError(BaseException): - """Invalid usage of CLI.""" + '''Invalid usage of CLI.''' diff --git a/senlinclient/v1/__init__.py b/senlinclient/v1/__init__.py new file mode 100644 index 00000000..06617394 --- /dev/null +++ b/senlinclient/v1/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# 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. + +__all__ = ['Client'] + +from heatclient.v1.client import Client # noqa diff --git a/senlinclient/v1/actions.py b/senlinclient/v1/actions.py new file mode 100644 index 00000000..8b567dd3 --- /dev/null +++ b/senlinclient/v1/actions.py @@ -0,0 +1,89 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Action(base.Resource): + def __repr__(self): + return "" % self._info + + def update(self, **fields): + self.manager.update(self, **fields) + + def delete(self): + return self.manager.delete(self) + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + +class ActionManager(base.BaseManager): + resource_class = Action + + def list(self, **kwargs): + """Get a list of actions. + :param limit: maximum number of actions to return + :param marker: begin returning actions that appear later in the + list than that represented by this action id + :param filters: dict of direct comparison filters that mimics the + structure of a action object + :rtype: list of :class:`Action` + """ + def paginate(params): + '''Paginate actions, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/actions?%s' % parse.urlencode(params, True) + actions = self._list(url, 'actions') + for action in actions: + yield action + + count = len(actions) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = action.id + for action in paginate(params): + yield action + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def create(self, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/actions', + data=kwargs, headers=headers) + return body + + def cancel(self, action_id): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/actions/%s/cancel' % action_id, + headers=headers) + return body + + def delete(self, action_id): + self._delete('/actions/%s' % action_id) diff --git a/senlinclient/v1/build_info.py b/senlinclient/v1/build_info.py new file mode 100644 index 00000000..a79aafbb --- /dev/null +++ b/senlinclient/v1/build_info.py @@ -0,0 +1,29 @@ +# 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 senlinclient.openstack.common.apiclient import base + + +class BuildInfo(base.Resource): + def __repr__(self): + return "" % self._info + + def build_info(self): + return self.manager.build_info() + + +class BuildInfoManager(base.BaseManager): + resource_class = BuildInfo + + def build_info(self): + resp, body = self.client.json_request('GET', '/build_info') + return body diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py new file mode 100644 index 00000000..ee429215 --- /dev/null +++ b/senlinclient/v1/client.py @@ -0,0 +1,48 @@ +# 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 senlinclient.common import http +from senlinclient.v1 import actions +from senlinclient.v1 import build_info +from senlinclient.v1 import clusters +from senlinclient.v1 import events +from senlinclient.v1 import nodes +from senlinclient.v1 import policies +from senlinclient.v1 import policy_types +from senlinclient.v1 import profile_types +from senlinclient.v1 import profiles + + +class Client(object): + """Client for the Senlin v1 API. + + :param string endpoint: A user-supplied endpoint URL for the Senlin + service. + :param string token: Token for authentication. + :param integer timeout: Allows customization of the timeout for client + http requests. (optional) + """ + + def __init__(self, *args, **kwargs): + """Initialize a new client for the Senlin v1 API.""" + self.http_client = http._construct_http_client(*args, **kwargs) + + self.clusters = clusters.ClusterManager(self.http_client) + self.nodes = nodes.NodeManager(self.http_client) + self.profiles = profiles.ProfileManager(self.http_client) + self.policies = policies.PolicyManager(self.http_client) + self.policy_types = policy_types.PolicyTypeManager(self.http_client) + self.profile_types = profile_types.ProfileTypeManager(self.http_client) + self.events = events.EventManager(self.http_client) + self.actions = actions.ActionManager(self.http_client) + + self.build_info = build_info.BuildInfoManager(self.http_client) diff --git a/senlinclient/v1/clusters.py b/senlinclient/v1/clusters.py new file mode 100644 index 00000000..6440d4e6 --- /dev/null +++ b/senlinclient/v1/clusters.py @@ -0,0 +1,147 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Cluster(base.Resource): + def __repr__(self): + return "" % self._info + + def create(self, **fields): + return self.manager.create(self.identifier, **fields) + + def update(self, **fields): + self.manager.update(self.identifier, **fields) + + def delete(self): + return self.manager.delete(self.identifier) + + def get(self): + # set_loaded() first ... so if we have to bail, we know we tried. + self._loaded = True + if not hasattr(self.manager, 'get'): + return + + new = self.manager.get(self.identifier) + if new: + self._add_details(new._info) + + @property + def identifier(self): + return '%s/%s' % (self.name, self.id) + + +class ClusterManager(base.BaseManager): + resource_class = Cluster + + def list(self, **kwargs): + """Get a list of clusters. + :param limit: maximum number of clusters to return + :param marker: begin returning clusters that appear later in the + cluster list than that represented by this cluster id + :param filters: dict of direct comparison filters that mimics the + structure of a cluster object + :rtype: list of :class:`Cluster` + """ + def paginate(params): + '''Paginate clusters, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/clusters?%s' % parse.urlencode(params, True) + clusters = self._list(url, 'clusters') + for cluster in clusters: + yield cluster + + count = len(clusters) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = cluster.id + for cluster in paginate(params): + yield cluster + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def create(self, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/clusters', + data=kwargs, headers=headers) + return body + + def update(self, cluster_id, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'PUT', + '/clusters/%s' % cluster_id, + data=kwargs, headers=headers) + + def delete(self, cluster_id): + """Delete a cluster.""" + self._delete("/clusters/%s" % cluster_id) + + def attach_policy(self, cluster_id, policy_id): + """Attach a policy to a cluster.""" + cluster = self.get(cluster_id) + data = {'policy_id': policy_id} + resp, body = self.client.json_request( + 'POST', + '/clusters/%s/policies' % cluster.identifier, + data=data) + return body + + def show_policy(self, cluster_id, policy_id): + cluster = self.get(cluster_id) + resp, body = self.client.json_request( + 'GET', + '/clusters/%s/policies/%s' % (cluster.identifier, policy_id)) + return body + + def detach_policy(self, cluster_id, policy_id): + cluster = self.get(cluster_id) + resp, body = self.client.json_request( + 'DELETE', + '/clusters/%s/policies/%s' % (cluster.identifier, policy_id)) + return body + + def policy_list(self, cluster_id): + cluster = self.get(cluster_id) + resp, body = self.client.json_request( + 'GET', + '/clusters/%s/policies' % cluster.identifier) + return body + + def get(self, cluster_id): + resp, body = self.client.json_request( + 'GET', + '/clusters/%s' % cluster_id) + return Cluster(self, body['cluster']) + + def profile(self, cluster_id): + '''Get the profile spec for a specific cluster as a parsed Json.''' + resp, body = self.client.json_request( + 'GET', + '/clusters/%s/profile' % cluster_id) + return body diff --git a/senlinclient/v1/events.py b/senlinclient/v1/events.py new file mode 100644 index 00000000..7af19c97 --- /dev/null +++ b/senlinclient/v1/events.py @@ -0,0 +1,80 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Event(base.Resource): + def __repr__(self): + return "" % self._info + + def update(self, **fields): + self.manager.update(self, **fields) + + def delete(self): + return self.manager.delete(self) + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + +class EventManager(base.BaseManager): + resource_class = Event + + def list(self, **kwargs): + """Get a list of events. + :param limit: maximum number of events to return + :param marker: begin returning events that appear later in the + list than that represented by this event id + :param filters: dict of direct comparison filters that mimics the + structure of a event object + :rtype: list of :class:`Event` + """ + def paginate(params): + '''Paginate events, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/events?%s' % parse.urlencode(params, True) + events = self._list(url, 'events') + for event in events: + yield event + + count = len(events) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = event.id + for event in paginate(params): + yield event + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def delete(self, event_id): + self._delete("/events/%s" % event_id) + + def get(self, event_id): + '''Get the details for a specific event.''' + resp, body = self.client.json_request( + 'GET', + '/events/%s' % event_id) + return Event(self, body['event']) diff --git a/senlinclient/v1/nodes.py b/senlinclient/v1/nodes.py new file mode 100644 index 00000000..f0c636b2 --- /dev/null +++ b/senlinclient/v1/nodes.py @@ -0,0 +1,118 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Node(base.Resource): + def __repr__(self): + return "" % self._info + + def update(self, **fields): + self.manager.update(self, **fields) + + def delete(self): + return self.manager.delete(self) + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + +class NodeManager(base.BaseManager): + resource_class = Node + + def list(self, **kwargs): + """Get a list of nodes. + :param limit: maximum number of nodes to return + :param marker: begin returning nodes that appear later in the + list than that represented by this node id + :param filters: dict of direct comparison filters that mimics the + structure of a node object + :rtype: list of :class:`Node` + """ + def paginate(params): + '''Paginate nodes, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/nodes?%s' % parse.urlencode(params, True) + nodes = self._list(url, 'nodes') + for node in nodes: + yield node + + count = len(nodes) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = node.id + for node in paginate(params): + yield node + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def create(self, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/nodes', data=kwargs, headers=headers) + return body + + def delete(self, node_id): + self._delete('/nodes/%s' % node_id) + + def update(self, node_id, kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'PATCH', + '/nodes/%s' % node_id, + data=kwargs, headers=headers) + + def get(self, node_id): + '''Get the details for a specific node.''' + resp, body = self.client.json_request( + 'GET', + '/nodes/%s' % node_id) + return Node(self, body['node']) + + def join(self, node_id, cluster_id): + '''Make node join the specified cluster.''' + headers = self.client.credentials_headers() + data = {'cluster_id': cluster_id} + resp, body = self.client.json_request( + 'POST', + '/nodes/%s/cluster' % node_id, + data=data, headers=headers) + return body + + def leave(self, node_id): + '''Make node leave its current cluster.''' + resp, body = self.client.json_request( + 'POST', + '/nodes/%s/cluster' % node_id) + return body + + def profile(self, node_id): + '''Get the profile spec for a specific node as a parsed JSON.''' + resp, body = self.client.json_request( + 'GET', + '/nodes/%s/profile' % node_id) + return body diff --git a/senlinclient/v1/policies.py b/senlinclient/v1/policies.py new file mode 100644 index 00000000..2bba0cd4 --- /dev/null +++ b/senlinclient/v1/policies.py @@ -0,0 +1,106 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Policy(base.Resource): + def __repr__(self): + return "" % self._info + + def create(self, **fields): + return self.manager.create(self.identifier, **fields) + + def update(self, **fields): + self.manager.update(self.identifier, **fields) + + def delete(self): + return self.manager.delete(self.identifier) + + def get(self): + # set _loaded() first ... so if we have to bail, we know we tried. + self._loaded = True + if not hasattr(self.manager, 'get'): + return + + new = self.manager.get(self.identifier) + if new: + self._add_details(new._info) + + @property + def identifier(self): + return '%s/%s' % (self.name, self.id) + + +class PolicyManager(base.BaseManager): + resource_class = Policy + + def list(self, **kwargs): + """Get a list of policies. + :param limit: maximum number of policies to return + :param marker: begin returning policies that appear later in the + list than that represented by this policy id + :param filters: dict of direct comparison filters that mimics the + structure of a policy object + :rtype: list of :class:`Policy` + """ + def paginate(params): + '''Paginate policies, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/policies?%s' % parse.urlencode(params, True) + policies = self._list(url, 'policies') + for policy in policies: + yield policy + + count = len(policies) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = policy.id + for policy in paginate(params): + yield policy + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def create(self, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/policies', + data=kwargs, headers=headers) + return body + + def update(self, policy_id, **kwargs): + '''We don't allow update to a policy.''' + return None + + def delete(self, policy_id): + """Delete a policy.""" + self._delete("/policies/%s" % policy_id) + + def get(self, policy_id): + resp, body = self.client.json_request( + 'GET', + '/policies/%s' % policy_id) + return Policy(self, body['policy']) diff --git a/senlinclient/v1/policy_types.py b/senlinclient/v1/policy_types.py new file mode 100644 index 00000000..ba06ed77 --- /dev/null +++ b/senlinclient/v1/policy_types.py @@ -0,0 +1,53 @@ +# 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 six.moves.urllib import parse + +from oslo.utils import encodeutils + +from senlinclient.openstack.common.apiclient import base + + +class PolicyType(base.Resource): + def __repr__(self): + return "" % self._info + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + def _add_details(self, info): + self.policy_type = info + + +class PolicyTypeManager(base.BaseManager): + resource_class = PolicyType + + def list(self): + """Get a list of policy types. + :rtype: list of :class:`PolicyType` + """ + return self._list('/policy_types', 'policy_types') + + def get(self, policy_type): + '''Get the details for a specific policy_type.''' + url_str = parse.quote(encodeutils.safe_encode(policy_type), '') + resp, body = self.client.json_request( + 'GET', + '/policy_types/%s' % url_str) + return body + + def generate_template(self, policy_type): + url_str = parse.quote(encodeutils.safe_encode(policy_type), '') + resp, body = self.client.json_request( + 'GET', + '/policy_types/%s/template' % url_str) + return body diff --git a/senlinclient/v1/profile_types.py b/senlinclient/v1/profile_types.py new file mode 100644 index 00000000..1cb280a1 --- /dev/null +++ b/senlinclient/v1/profile_types.py @@ -0,0 +1,53 @@ +# 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 six.moves.urllib import parse + +from oslo.utils import encodeutils + +from senlinclient.openstack.common.apiclient import base + + +class ProfileType(base.Resource): + def __repr__(self): + return "" % self._info + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + def _add_details(self, info): + self.profile_type = info + + +class ProfileTypeManager(base.BaseManager): + resource_class = ProfileType + + def list(self): + """Get a list of profile types. + :rtype: list of :class:`ProfileType` + """ + return self._list('/profile_types', 'profile_types') + + def get(self, profile_type): + '''Get the details for a specific profile_type.''' + url_str = parse.quote(encodeutils.safe_encode(profile_type), '') + resp, body = self.client.json_request( + 'GET', + '/profile_types/%s' % url_str) + return body + + def generate_template(self, profile_type): + url_str = parse.quote(encodeutils.safe_encode(profile_type), '') + resp, body = self.client.json_request( + 'GET', + '/profile_types/%s/template' % url_str) + return body diff --git a/senlinclient/v1/profiles.py b/senlinclient/v1/profiles.py new file mode 100644 index 00000000..5d86e22b --- /dev/null +++ b/senlinclient/v1/profiles.py @@ -0,0 +1,106 @@ +# 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 six +from six.moves.urllib import parse + +from senlinclient.openstack.common.apiclient import base + + +class Profile(base.Resource): + def __repr__(self): + return "" % self._info + + def create(self, **fields): + return self.manager.create(self.identifier, **fields) + + def update(self, **fields): + self.manager.update(self.identifier, **fields) + + def delete(self): + return self.manager.delete(self.identifier) + + def get(self): + # set _loaded() first ... so if we have to bail, we know we tried. + self._loaded = True + if not hasattr(self.manager, 'get'): + return + + new = self.manager.get(self.identifier) + if new: + self._add_details(new._info) + + @property + def identifier(self): + return '%s/%s' % (self.name, self.id) + + +class ProfileManager(base.BaseManager): + resource_class = Profile + + def list(self, **kwargs): + """Get a list of profiles. + :param limit: maximum number of profiles to return + :param marker: begin returning profiles that appear later in the + list than that represented by this profile id + :param filters: dict of direct comparison filters that mimics the + structure of a profile object + :rtype: list of :class:`Profile` + """ + def paginate(params): + '''Paginate profiles, even if more than API limit.''' + current_limit = int(params.get('limit') or 0) + url = '/profiles?%s' % parse.urlencode(params, True) + profiles = self._list(url, 'profiles') + for profile in profiles: + yield profile + + count = len(profiles) + remaining = current_limit - count + if remaining > 0 and count > 0: + params['limit'] = remaining + params['marker'] = profile.id + for profile in paginate(params): + yield profile + + params = {} + if 'filters' in kwargs: + filters = kwargs.pop('filters') + params.update(filters) + + for key, value in six.iteritems(kwargs): + if value: + params[key] = value + + return paginate(params) + + def create(self, **kwargs): + headers = self.client.credentials_headers() + resp, body = self.client.json_request( + 'POST', + '/profiles', + data=kwargs, headers=headers) + return body + + def update(self, profile_id, **kwargs): + '''We don't allow update to a profile.''' + return None + + def delete(self, profile_id): + """Delete a profile.""" + self._delete("/profiles/%s" % profile_id) + + def get(self, profile_id): + resp, body = self.client.json_request( + 'GET', + '/profiles/%s' % profile_id) + return Profile(self, body['profile']) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py new file mode 100644 index 00000000..f485ae52 --- /dev/null +++ b/senlinclient/v1/shell.py @@ -0,0 +1,61 @@ +# 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 senlinclient.common.i18n import _ +from senlinclient.common import utils + +logger = logging.getLogger(__name__) + + +@utils.arg('-s', '--show-deleted', default=False, action="store_true", + help=_('Include soft-deleted clusters if any.')) +@utils.arg('-n', '--show-nested', default=False, action="store_true", + help=_('Include nested clusters if any.')) +@utils.arg('-f', '--filters', metavar='', + help=_('Filter parameters to apply on returned clusters. ' + 'This can be specified multiple times, or once with ' + 'parameters separated by a semicolon.'), + action='append') +@utils.arg('-l', '--limit', metavar='', + help=_('Limit the number of clusters returned.')) +@utils.arg('-m', '--marker', metavar='', + help=_('Only return clusters that appear after the given cluster ' + 'ID.')) +@utils.arg('-g', '--global-tenant', action='store_true', default=False, + help=_('List clusters from all tenants. Operation only authorized ' + 'for users who match the policy in policy file.')) +@utils.arg('-o', '--show-parent', action='store_true', default=False, + help=_('Show cluster parent information. This is automatically ' + 'enabled when using %(arg)s.') % {'arg': '--global-tenant'}) +def do_cluster_list(sc, args=None): + '''List the user's clusters.''' + kwargs = {} + fields = ['id', 'cluster_name', 'status', 'created_time'] + if args: + kwargs = {'limit': args.limit, + 'marker': args.marker, + 'filters': utils.format_parameters(args.filters), + 'global_tenant': args.global_tenant, + 'show_deleted': args.show_deleted} + if args.show_nested: + fields.append('parent') + kwargs['show_nested'] = True + + if args.global_tenant or args.show_owner: + fields.insert(2, 'stack_owner') + if args.global_tenant: + fields.insert(2, 'project') + + clusters = sc.clusters.list(**kwargs) + utils.print_list(clusters, fields, sortby_index=3)