Add dcae-cli and component-json-schemas projects
[dcaegen2/platform/cli.git] / dcae-cli / dcae_cli / util / profiles.py
1 # ============LICENSE_START=======================================================
2 # org.onap.dcae
3 # ================================================================================
4 # Copyright (c) 2017 AT&T Intellectual Property. All rights reserved.
5 # ================================================================================
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #      http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 # ============LICENSE_END=========================================================
18 #
19 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
20
21 #!/usr/bin/env python3
22 # -*- coding: utf-8 -*-
23 """
24 Provides dcae cli profile variables
25 """
26 import os
27 from collections import namedtuple
28
29 import six
30
31 from dcae_cli import util
32 from dcae_cli.util import get_app_dir, get_pref, write_pref
33 from dcae_cli.util import config
34 from dcae_cli.util.config import get_config, update_config
35 from dcae_cli.util.exc import DcaeException
36 from dcae_cli.util.logger import get_logger
37
38
39 logger = get_logger('Profile')
40
41
42 # reserved profile names
43 ACTIVE = 'active'
44 _reserved_names = {ACTIVE}
45
46
47 # create enums for profile keys so that they can be imported for testing, instead of using literals
48 CONSUL_HOST = 'consul_host'
49 CONFIG_BINDING_SERVICE = 'config_binding_service'
50 CDAP_BROKER = 'cdap_broker'
51 DOCKER_HOST = 'docker_host'
52
53 # TODO: Should probably lift this strict list of allowed keys and repurpose to be
54 # keys that are required.
55 _allowed_keys = set([CONSUL_HOST, CONFIG_BINDING_SERVICE, CDAP_BROKER, DOCKER_HOST])
56 Profile = namedtuple('Profile', _allowed_keys)
57
58
59 def _create_stub_profile():
60     """Create a new stub of a profile"""
61     return { k: "" for k in _allowed_keys }
62
63
64 def _fmt_seq(seq):
65     '''Returns a sorted string formatted list'''
66     return list(sorted(map(str, seq)))
67
68
69 def get_profiles_path():
70     '''Returns the absolute path to the profiles file'''
71     return os.path.join(get_app_dir(), 'profiles.json')
72
73
74 def get_active_name():
75     '''Returns the active profile name in the config'''
76     return config.get_active_profile()
77
78
79 def _set_active_name(name):
80     '''Sets the active profile name in the config'''
81     update_config(active_profile=name)
82
83
84 class ProfilesInitError(RuntimeError):
85     pass
86
87 def reinit_profiles():
88     """Reinitialize profiles
89
90     Grab the remote profiles and merge with the local profiles if there is one.
91
92     Returns:
93     --------
94     Dict of complete new profiles
95     """
96     # Grab the remote profiles and merge it in
97     try:
98         new_profiles = util.fetch_file_from_nexus("/dcae-cli/profiles.json")
99     except:
100         # REVIEW: Should we allow users to manually setup their config if not
101         # able to pull from remote server?
102         raise ProfilesInitError("Could not download profiles from remote server")
103
104     profiles_path = get_profiles_path()
105
106     if  util.pref_exists(profiles_path):
107         existing_profiles = get_profiles(include_active=False)
108         # Make sure to clobber existing values and not other way
109         existing_profiles.update(new_profiles)
110         new_profiles = existing_profiles
111
112     write_pref(new_profiles, profiles_path)
113     return new_profiles
114
115
116 def get_profiles(user_only=False, include_active=True):
117     '''Returns a dict containing all available profiles
118
119     Example of the returned dict:
120         {
121             "profile-foo": {
122                 "some_variable_A": "some_value_A",
123                 "some_variable_B": "some_value_B",
124                 "some_variable_C": "some_value_C"
125             }
126         }
127     '''
128     try:
129         profiles = get_pref(get_profiles_path(), reinit_profiles)
130     except ProfilesInitError as e:
131         raise DcaeException("Failed to initialize profiles: {0}".format(e))
132
133     if user_only:
134         return profiles
135
136     if include_active:
137         active_name = get_active_name()
138         if active_name not in profiles:
139             raise DcaeException("Active profile '{}' does not exist. How did this happen?".format(active_name))
140         profiles[ACTIVE] = profiles[active_name]
141
142     return profiles
143
144
145 def get_profile(name=ACTIVE):
146     '''Returns a `Profile` object'''
147     profiles = get_profiles()
148
149     if name not in profiles:
150         raise DcaeException("Specified profile '{}' does not exist.".format(name))
151
152     try:
153         profile = Profile(**profiles[name])
154     except TypeError as e:
155         raise DcaeException("Specified profile '{}' is malformed.".format(name))
156
157     return profile
158
159
160 def create_profile(name, **kwargs):
161     '''Creates a new profile'''
162     _assert_not_reserved(name)
163
164     profiles = get_profiles(user_only=True)
165     if name in profiles:
166         raise DcaeException("Profile '{}' already exists.".format(name))
167
168     profile = _create_stub_profile()
169     profile.update(kwargs)
170     _assert_valid_profile(profile)
171
172     profiles[name] = profile
173     _write_profiles(profiles)
174
175
176 def delete_profile(name):
177     '''Deletes a profile'''
178     _assert_not_reserved(name)
179     profiles = get_profiles(user_only=True)
180     if name not in profiles:
181         raise DcaeException("Profile '{}' does not exist.".format(name))
182     if name == get_active_name():
183         logger.warning("Profile '{}' is currently active. Activate another profile first."
184                 .format(name))
185         return False
186     del profiles[name]
187     _write_profiles(profiles)
188     return True
189
190
191 def update_profile(name, **kwargs):
192     '''Creates or updates a profile'''
193     _assert_not_reserved(name)
194     _assert_valid_profile(kwargs)
195
196     profiles = get_profiles(user_only=True)
197     if name not in profiles:
198         raise DcaeException("Profile '{}' does not exist.".format(name))
199
200     profiles[name].update(kwargs)
201     _write_profiles(profiles)
202
203
204 def _assert_valid_profile(params):
205     '''Raises DcaeException if the profile parameter dict is invalid'''
206     if not params:
207         raise DcaeException('No update key-value pairs were provided.')
208     keys = set(params.keys())
209     if not _allowed_keys.issuperset(keys):
210         invalid_keys = keys - _allowed_keys
211         raise DcaeException("Invalid keys {} detected. Only keys {} are supported.".format(_fmt_seq(invalid_keys), _fmt_seq(_allowed_keys)))
212
213
214 def _assert_not_reserved(name):
215     '''Raises DcaeException if the profile is reserved'''
216     if name in _reserved_names:
217         raise DcaeException("Profile '{}' is reserved and cannot be modified.".format(name))
218
219
220 def _write_profiles(profiles):
221     '''Writes the profiles dictionary to disk'''
222     return write_pref(profiles, path=get_profiles_path())
223
224
225 def activate_profile(name):
226     '''Modifies the config and sets a new active profile'''
227     avail_profiles = set(get_profiles().keys()) - {ACTIVE, }
228     if name not in avail_profiles:
229         raise DcaeException("Profile name '{}' does not exist. Please select from {} or create a new profile.".format(name, _fmt_seq(avail_profiles)))
230     _set_active_name(name)