218
207
os.unlink(temp_file.name)
221
class SimpleEnvironment:
222
"""Represents a model in a JUJU_HOME directory for juju 1."""
224
def __init__(self, environment, config=None, juju_home=None,
228
:param environment: Name of the environment.
229
:param config: Dictionary with configuration options, default is None.
230
:param juju_home: Path to JUJU_HOME directory, default is None.
231
:param controller: Controller instance-- this model's controller.
232
If not given or None a new instance is created."""
233
self.user_name = None
234
if controller is None:
235
controller = Controller(environment)
236
self.controller = controller
237
self.environment = environment
239
self.juju_home = juju_home
240
if self.config is not None:
241
self.local = bool(self.config.get('type') == 'local')
243
self.local and bool(self.config.get('container') == 'kvm'))
244
self.maas = bool(self.config.get('type') == 'maas')
245
self.joyent = bool(self.config.get('type') == 'joyent')
252
def clone(self, model_name=None):
253
config = deepcopy(self.config)
254
if model_name is None:
255
model_name = self.environment
257
config['name'] = unqualified_model_name(model_name)
258
result = self.__class__(model_name, config, self.juju_home,
260
result.local = self.local
261
result.kvm = self.kvm
262
result.maas = self.maas
263
result.joyent = self.joyent
266
def __eq__(self, other):
267
if type(self) != type(other):
269
if self.environment != other.environment:
271
if self.config != other.config:
273
if self.local != other.local:
275
if self.maas != other.maas:
279
def __ne__(self, other):
280
return not self == other
282
def set_model_name(self, model_name, set_controller=True):
284
self.controller.name = model_name
285
self.environment = model_name
286
self.config['name'] = unqualified_model_name(model_name)
289
def from_config(cls, name):
290
"""Create an environment from the configuation file.
292
:param name: Name of the environment to get the configuration from."""
293
return cls._from_config(name)
296
def _from_config(cls, name):
297
config, selected = get_selected_environment(name)
300
return cls(name, config)
302
def needs_sudo(self):
306
def make_jes_home(self, juju_home, dir_name, new_config):
307
home_path = jes_home_path(juju_home, dir_name)
308
if os.path.exists(home_path):
310
os.makedirs(home_path)
311
self.dump_yaml(home_path, new_config)
314
def get_cloud_credentials(self):
315
"""Return the credentials for this model's cloud.
317
This implementation returns config variables in addition to
322
def dump_yaml(self, path, config):
323
dump_environments_yaml(path, config)
326
class JujuData(SimpleEnvironment):
327
"""Represents a model in a JUJU_DATA directory for juju 2."""
329
def __init__(self, environment, config=None, juju_home=None,
333
This extends SimpleEnvironment's constructor.
335
:param environment: Name of the environment.
336
:param config: Dictionary with configuration options; default is None.
337
:param juju_home: Path to JUJU_DATA directory. If None (the default),
338
the home directory is autodetected.
339
:param controller: Controller instance-- this model's controller.
340
If not given or None, a new instance is created.
342
if juju_home is None:
343
juju_home = get_juju_home()
344
super(JujuData, self).__init__(environment, config, juju_home,
346
self.credentials = {}
349
def clone(self, model_name=None):
350
result = super(JujuData, self).clone(model_name)
351
result.credentials = deepcopy(self.credentials)
352
result.clouds = deepcopy(self.clouds)
356
def from_env(cls, env):
357
juju_data = cls(env.environment, env.config, env.juju_home)
358
juju_data.load_yaml()
363
with open(os.path.join(self.juju_home, 'credentials.yaml')) as f:
364
self.credentials = yaml.safe_load(f)
366
if e.errno != errno.ENOENT:
368
'Failed to read credentials file: {}'.format(str(e)))
369
self.credentials = {}
371
with open(os.path.join(self.juju_home, 'clouds.yaml')) as f:
372
self.clouds = yaml.safe_load(f)
374
if e.errno != errno.ENOENT:
376
'Failed to read clouds file: {}'.format(str(e)))
377
# Default to an empty clouds file.
378
self.clouds = {'clouds': {}}
381
def from_config(cls, name):
382
"""Create a model from the three configuration files."""
383
juju_data = cls._from_config(name)
384
juju_data.load_yaml()
387
def dump_yaml(self, path, config):
388
"""Dump the configuration files to the specified path.
390
config is unused, but is accepted for compatibility with
391
SimpleEnvironment and make_jes_home().
393
with open(os.path.join(path, 'credentials.yaml'), 'w') as f:
394
yaml.safe_dump(self.credentials, f)
395
with open(os.path.join(path, 'clouds.yaml'), 'w') as f:
396
yaml.safe_dump(self.clouds, f)
398
def find_endpoint_cloud(self, cloud_type, endpoint):
399
for cloud, cloud_config in self.clouds['clouds'].items():
400
if cloud_config['type'] != cloud_type:
402
if cloud_config['endpoint'] == endpoint:
404
raise LookupError('No such endpoint: {}'.format(endpoint))
407
provider = self.config['type']
408
# Separate cloud recommended by: Juju Cloud / Credentials / BootStrap /
409
# Model CLI specification
410
if provider == 'ec2' and self.config['region'] == 'cn-north-1':
412
if provider not in ('maas', 'openstack'):
416
}.get(provider, provider)
417
if provider == 'maas':
418
endpoint = self.config['maas-server']
419
elif provider == 'openstack':
420
endpoint = self.config['auth-url']
421
return self.find_endpoint_cloud(provider, endpoint)
423
def get_region(self):
424
provider = self.config['type']
425
if provider == 'azure':
426
if 'tenant-id' not in self.config:
427
return self.config['location'].replace(' ', '').lower()
428
return self.config['location']
429
elif provider == 'joyent':
430
matcher = re.compile('https://(.*).api.joyentcloud.com')
431
return matcher.match(self.config['sdc-url']).group(1)
432
elif provider == 'lxd':
434
elif provider == 'manual':
435
return self.config['bootstrap-host']
436
elif provider in ('maas', 'manual'):
439
return self.config['region']
441
def get_cloud_credentials(self):
442
"""Return the credentials for this model's cloud."""
443
cloud_name = self.get_cloud()
444
cloud = self.credentials['credentials'][cloud_name]
445
(credentials,) = cloud.values()
451
212
def __init__(self, status, status_text):
833
def get_client_class(version):
834
if version.startswith('1.16'):
835
raise Exception('Unsupported juju: %s' % version)
836
elif re.match('^1\.22[.-]', version):
837
client_class = EnvJujuClient22
838
elif re.match('^1\.24[.-]', version):
839
client_class = EnvJujuClient24
840
elif re.match('^1\.25[.-]', version):
841
client_class = EnvJujuClient25
842
elif re.match('^1\.26[.-]', version):
843
client_class = EnvJujuClient26
844
elif re.match('^1\.', version):
845
client_class = EnvJujuClient1X
846
# Ensure alpha/beta number matches precisely
847
elif re.match('^2\.0-alpha1([^\d]|$)', version):
848
client_class = EnvJujuClient2A1
849
elif re.match('^2\.0-alpha2([^\d]|$)', version):
850
client_class = EnvJujuClient2A2
851
elif re.match('^2\.0-(alpha3|beta[12])([^\d]|$)', version):
852
client_class = EnvJujuClient2B2
853
elif re.match('^2\.0-(beta[3-6])([^\d]|$)', version):
854
client_class = EnvJujuClient2B3
855
elif re.match('^2\.0-(beta7)([^\d]|$)', version):
856
client_class = EnvJujuClient2B7
857
elif re.match('^2\.0-beta8([^\d]|$)', version):
858
client_class = EnvJujuClient2B8
860
elif re.match('^2\.0-beta(9|1[0-4])([^\d]|$)', version):
861
client_class = EnvJujuClient2B9
863
client_class = EnvJujuClient
867
def client_from_config(config, juju_path, debug=False, soft_deadline=None):
868
"""Create a client from an environment's configuration.
870
:param config: Name of the environment to use the config from.
871
:param juju_path: Path to juju binary the client should wrap.
872
:param debug=False: The debug flag for the client, False by default.
873
:param soft_deadline: A datetime representing the deadline by which
874
normal operations should complete. If None, no deadline is
877
version = EnvJujuClient.get_version(juju_path)
878
client_class = get_client_class(version)
879
env = client_class.config_class.from_config(config)
880
if juju_path is None:
881
full_path = EnvJujuClient.get_full_path()
883
full_path = os.path.abspath(juju_path)
884
return client_class(env, version, full_path, debug=debug,
885
soft_deadline=soft_deadline)
888
563
class EnvJujuClient:
889
"""Wraps calls to a juju instance, associated with a single model.
891
Note: A model is often called an enviroment (Juju 1 legacy).
893
This class represents the latest Juju version. Subclasses are used to
894
support older versions (see get_client_class).
897
565
# The environments.yaml options that are replaced by bootstrap options.
993
643
return WIN_JUJU_CMD
994
644
return subprocess.check_output(('which', 'juju')).rstrip('\n')
996
def clone_path_cls(self, juju_path):
997
"""Clone using the supplied path to determine the class."""
998
version = self.get_version(juju_path)
999
cls = get_client_class(version)
647
def by_version(cls, env, juju_path=None, debug=False):
648
version = cls.get_version(juju_path)
1000
649
if juju_path is None:
1001
full_path = self.get_full_path()
650
full_path = cls.get_full_path()
1003
652
full_path = os.path.abspath(juju_path)
1004
return self.clone(version=version, full_path=full_path, cls=cls)
653
if version.startswith('1.16'):
654
raise Exception('Unsupported juju: %s' % version)
655
elif re.match('^1\.22[.-]', version):
656
client_class = EnvJujuClient22
657
elif re.match('^1\.24[.-]', version):
658
client_class = EnvJujuClient24
659
elif re.match('^1\.25[.-]', version):
660
client_class = EnvJujuClient25
661
elif re.match('^1\.26[.-]', version):
662
client_class = EnvJujuClient26
663
elif re.match('^1\.', version):
664
client_class = EnvJujuClient1X
665
elif re.match('^2\.0-alpha1', version):
666
client_class = EnvJujuClient2A1
667
elif re.match('^2\.0-alpha2', version):
668
client_class = EnvJujuClient2A2
669
elif re.match('^2\.0-(alpha3|beta[12])', version):
670
client_class = EnvJujuClient2B2
671
elif re.match('^2\.0-(beta[3-6])', version):
672
client_class = EnvJujuClient2B3
673
elif re.match('^2\.0-(beta7)', version):
674
client_class = EnvJujuClient2B7
675
elif re.match('^2\.0-beta8', version):
676
client_class = EnvJujuClient2B8
678
client_class = EnvJujuClient
679
return client_class(env, version, full_path, debug=debug)
1006
681
def clone(self, env=None, version=None, full_path=None, debug=None,
1237
879
raise AssertionError(
1238
880
'Controller and environment names should not vary (yet)')
1240
def update_user_name(self):
1241
self.env.user_name = 'admin@local'
1243
def bootstrap(self, upload_tools=False, bootstrap_series=None,
1244
credential=None, auto_upgrade=False, metadata_source=None,
1245
to=None, agent_version=None):
882
def bootstrap(self, upload_tools=False, bootstrap_series=None):
1246
883
"""Bootstrap a controller."""
1247
884
self._check_bootstrap()
1248
885
with self._bootstrap_config() as config_filename:
1249
886
args = self.get_bootstrap_args(
1250
upload_tools, config_filename, bootstrap_series, credential,
1251
auto_upgrade, metadata_source, to, agent_version)
1252
self.update_user_name()
887
upload_tools, config_filename, bootstrap_series)
1253
888
self.juju('bootstrap', args, include_e=False)
1256
def bootstrap_async(self, upload_tools=False, bootstrap_series=None,
1257
auto_upgrade=False, metadata_source=None, to=None):
891
def bootstrap_async(self, upload_tools=False, bootstrap_series=None):
1258
892
self._check_bootstrap()
1259
893
with self._bootstrap_config() as config_filename:
1260
894
args = self.get_bootstrap_args(
1261
upload_tools, config_filename, bootstrap_series, None,
1262
auto_upgrade, metadata_source, to)
1263
self.update_user_name()
895
upload_tools, config_filename, bootstrap_series)
1264
896
with self.juju_async('bootstrap', args, include_e=False):
1266
898
log.info('Waiting for bootstrap of {}.'.format(
1343
973
return self.juju('set-model-constraints', constraint_strings)
1345
975
def get_model_config(self):
1346
"""Return the value of the environment's configured options."""
1347
return yaml.safe_load(
1348
self.get_juju_output('model-config', '--format', 'yaml'))
976
"""Return the value of the environment's configured option."""
977
return yaml.safe_load(self.get_juju_output('get-model-config'))
1350
979
def get_env_option(self, option):
1351
980
"""Return the value of the environment's configured option."""
1352
return self.get_juju_output('model-config', option)
981
return self.get_juju_output('get-model-config', option)
1354
983
def set_env_option(self, option, value):
1355
984
"""Set the value of the option in the environment."""
1356
985
option_value = "%s=%s" % (option, value)
1357
return self.juju('model-config', (option_value,))
1359
def unset_env_option(self, option):
1360
"""Unset the value of the option in the environment."""
1361
return self.juju('model-config', ('--reset', option,))
1363
def get_agent_metadata_url(self):
1364
return self.get_env_option(self.agent_metadata_url)
1366
def set_testing_agent_metadata_url(self):
1367
url = self.get_agent_metadata_url()
986
return self.juju('set-model-config', (option_value,))
988
def set_testing_tools_metadata_url(self):
989
url = self.get_env_option('tools-metadata-url')
1368
990
if 'testing' not in url:
1369
991
testing_url = url.replace('/tools', '/testing/tools')
1370
self.set_env_option(self.agent_metadata_url, testing_url)
992
self.set_env_option('tools-metadata-url', testing_url)
1372
994
def juju(self, command, args, sudo=False, check=True, include_e=True,
1373
995
timeout=None, extra_env=None):
1374
996
"""Run a command under juju for the current environment."""
1375
model = self._cmd_model(include_e, controller=False)
997
model = self._cmd_model(include_e, admin=False)
1376
998
return self._backend.juju(
1377
999
command, args, self.used_feature_flags, self.env.juju_home,
1378
1000
model, check, timeout, extra_env)
1660
1270
env = self.env.clone(model_name=name)
1661
1271
return self.clone(env=env)
1663
def get_model_uuid(self):
1664
name = self.env.environment
1665
model = self._cmd_model(True, False)
1666
output_yaml = self.get_juju_output(
1667
'show-model', '--format', 'yaml', model, include_e=False)
1668
output = yaml.safe_load(output_yaml)
1669
return output[name]['model-uuid']
1671
def get_controller_uuid(self):
1672
name = self.env.controller.name
1673
output_yaml = self.get_juju_output(
1674
'show-controller', '--format', 'yaml', include_e=False)
1675
output = yaml.safe_load(output_yaml)
1676
return output[name]['details']['uuid']
1678
def get_controller_model_uuid(self):
1679
output_yaml = self.get_juju_output(
1680
'show-model', 'controller', '--format', 'yaml', include_e=False)
1681
output = yaml.safe_load(output_yaml)
1682
return output['controller']['model-uuid']
1684
def get_controller_client(self):
1685
"""Return a client for the controller model. May return self.
1273
def get_admin_client(self):
1274
"""Return a client for the admin model. May return self.
1687
1276
This may be inaccurate for models created using add_model
1688
1277
rather than bootstrap.
1690
return self._acquire_model_client(self.get_controller_model_name())
1279
return self._acquire_model_client(self.get_admin_model_name())
1692
1281
def list_controllers(self):
1693
1282
"""List the controllers."""
1977
1558
return command_parts[-1]
1978
1559
raise AssertionError('Juju register command not found in output')
1980
def add_user(self, username):
1981
"""Adds provided user and return register command arguments.
1983
:return: Registration token provided by the add-user command.
1985
output = self.get_juju_output(
1986
'add-user', username, '-c', self.env.controller.name,
1988
return self._get_register_command(output)
1990
def add_user_perms(self, username, models=None, permissions='login'):
1991
"""Adds provided user and return register command arguments.
1993
:return: Registration token provided by the add-user command.
1995
output = self.add_user(username)
1996
self.grant(username, permissions, models)
1999
def revoke(self, username, models=None, permissions='read'):
2001
models = self.env.environment
2003
args = (username, permissions, models)
2005
self.controller_juju('revoke', args)
2007
def add_storage(self, unit, storage_type, amount="1"):
2008
"""Add storage instances to service.
2010
Only type 'disk' is able to add instances.
2012
self.juju('add-storage', (unit, storage_type + "=" + amount))
2014
def list_storage(self):
2015
"""Return the storage list."""
2016
return self.get_juju_output('list-storage', '--format', 'json')
2018
def list_storage_pool(self):
2019
"""Return the list of storage pool."""
2020
return self.get_juju_output('list-storage-pools', '--format', 'json')
2022
def create_storage_pool(self, name, provider, size):
2023
"""Create storage pool."""
2024
self.juju('create-storage-pool',
2026
'size={}'.format(size)))
2028
def disable_user(self, user_name):
2029
"""Disable an user"""
2030
self.controller_juju('disable-user', (user_name,))
2032
def enable_user(self, user_name):
2033
"""Enable an user"""
2034
self.controller_juju('enable-user', (user_name,))
2037
"""Logout an user"""
2038
self.controller_juju('logout', ())
2039
self.env.user_name = ''
2041
def register_user(self, user, juju_home, controller_name=None):
2042
"""Register `user` for the `client` return the cloned client used."""
2043
username = user.name
2044
if controller_name is None:
2045
controller_name = '{}_controller'.format(username)
2047
model = self.env.environment
2048
token = self.add_user_perms(username, models=model,
2049
permissions=user.permissions)
2050
user_client = self.create_cloned_environment(juju_home,
2055
child = user_client.expect('register', (token), include_e=False)
2056
child.expect('(?i)name')
2057
child.sendline(controller_name)
2058
child.expect('(?i)password')
2059
child.sendline(username + '_password')
2060
child.expect('(?i)password')
2061
child.sendline(username + '_password')
2062
child.expect(pexpect.EOF)
2065
'Registering user failed: pexpect session still alive')
2066
except pexpect.TIMEOUT:
2068
'Registering user failed: pexpect session timed out')
2069
user_client.env.user_name = username
2072
def remove_user(self, username):
2073
self.juju('remove-user', (username, '-y'), include_e=False)
2075
def create_cloned_environment(
2076
self, cloned_juju_home, controller_name, user_name=None):
2077
"""Create a cloned environment.
2079
If `user_name` is passed ensures that the cloned environment is updated
2083
user_client = self.clone(env=self.env.clone())
2084
user_client.env.juju_home = cloned_juju_home
2085
if user_name is not None and user_name != self.env.user_name:
2086
user_client.env.user_name = user_name
2087
user_client.env.environment = qualified_model_name(
2088
user_client.env.environment, self.env.user_name)
2089
# New user names the controller.
2090
user_client.env.controller = Controller(controller_name)
2093
def grant(self, user_name, permission, model=None):
2094
"""Grant the user with model or controller permission."""
2095
if permission in self.controller_permissions:
2098
(user_name, permission, '-c', self.env.controller.name),
2100
elif permission in self.model_permissions:
2102
model = self.model_name
2105
(user_name, permission, model, '-c', self.env.controller.name),
2108
raise ValueError('Unknown permission {}'.format(permission))
2110
def list_clouds(self, format='json'):
2111
"""List all the available clouds."""
2112
return self.get_juju_output('list-clouds', '--format',
2113
format, include_e=False)
2115
def show_controller(self, format='json'):
2116
"""Show controller's status."""
2117
return self.get_juju_output('show-controller', '--format',
2118
format, include_e=False)
2120
def ssh_keys(self, full=False):
2121
"""Give the ssh keys registered for the current model."""
2124
args.append('--full')
2125
return self.get_juju_output('ssh-keys', *args)
2127
def add_ssh_key(self, *keys):
2128
"""Add one or more ssh keys to the current model."""
2129
return self.get_juju_output('add-ssh-key', *keys, merge_stderr=True)
2131
def remove_ssh_key(self, *keys):
2132
"""Remove one or more ssh keys from the current model."""
2133
return self.get_juju_output('remove-ssh-key', *keys, merge_stderr=True)
2135
def import_ssh_key(self, *keys):
2136
"""Import ssh keys from one or more identities to the current model."""
2137
return self.get_juju_output('import-ssh-key', *keys, merge_stderr=True)
2139
def list_disabled_commands(self):
2140
"""List all the commands disabled on the model."""
2141
raw = self.get_juju_output('list-disabled-commands',
2143
return yaml.safe_load(raw)
2145
def disable_command(self, args):
2146
"""Disable a command set."""
2147
return self.juju('disable-command', args)
2149
def enable_command(self, args):
2150
"""Enable a command set."""
2151
return self.juju('enable-command', args)
2153
def sync_tools(self, local_dir=None):
2154
"""Copy tools into a local directory or model."""
2155
if local_dir is None:
2156
return self.juju('sync-tools', ())
2158
return self.juju('sync-tools', ('--local-dir', local_dir),
2162
class EnvJujuClient2B9(EnvJujuClient):
2164
def update_user_name(self):
2167
def create_cloned_environment(
2168
self, cloned_juju_home, controller_name, user_name=None):
2169
"""Create a cloned environment.
2171
`user_name` is unused in this version of beta.
2173
user_client = self.clone(env=self.env.clone())
2174
user_client.env.juju_home = cloned_juju_home
2175
# New user names the controller.
2176
user_client.env.controller = Controller(controller_name)
2179
def get_model_uuid(self):
2180
name = self.env.environment
2181
output_yaml = self.get_juju_output('show-model', '--format', 'yaml')
2182
output = yaml.safe_load(output_yaml)
2183
return output[name]['model-uuid']
2185
def add_user_perms(self, username, models=None, permissions='read'):
1561
def add_user(self, username, models=None, permissions='read'):
2186
1562
"""Adds provided user and return register command arguments.
2188
1564
:return: Registration token provided by the add-user command.
2213
1582
self.controller_juju('revoke', args)
2215
def set_config(self, service, options):
2216
option_strings = self._dict_as_option_strings(options)
2217
self.juju('set-config', (service,) + option_strings)
2219
def get_config(self, service):
2220
return yaml_loads(self.get_juju_output('get-config', service))
2222
def get_model_config(self):
2223
"""Return the value of the environment's configured option."""
2224
return yaml.safe_load(
2225
self.get_juju_output('get-model-config', '--format', 'yaml'))
2227
def get_env_option(self, option):
2228
"""Return the value of the environment's configured option."""
2229
return self.get_juju_output('get-model-config', option)
2231
def set_env_option(self, option, value):
2232
"""Set the value of the option in the environment."""
2233
option_value = "%s=%s" % (option, value)
2234
return self.juju('set-model-config', (option_value,))
2236
def unset_env_option(self, option):
2237
"""Unset the value of the option in the environment."""
2238
return self.juju('unset-model-config', (option,))
2240
def list_disabled_commands(self):
2241
"""List all the commands disabled on the model."""
2242
raw = self.get_juju_output('block list', '--format', 'yaml')
2243
return yaml.safe_load(raw)
2245
def disable_command(self, args):
2246
"""Disable a command set."""
2247
return self.juju('block', args)
2249
def enable_command(self, args):
2250
"""Enable a command set."""
2251
return self.juju('unblock', args)
2254
class EnvJujuClient2B8(EnvJujuClient2B9):
1585
class EnvJujuClient2B8(EnvJujuClient):
2256
1587
status_class = ServiceStatus
2719
2004
def upgrade_mongo(self):
2720
2005
raise UpgradeMongoNotSupported()
2722
def add_storage(self, unit, storage_type, amount="1"):
2723
"""Add storage instances to service.
2725
Only type 'disk' is able to add instances.
2727
self.juju('storage add', (unit, storage_type + "=" + amount))
2729
def list_storage(self):
2730
"""Return the storage list."""
2731
return self.get_juju_output('storage list', '--format', 'json')
2733
def list_storage_pool(self):
2734
"""Return the list of storage pool."""
2735
return self.get_juju_output('storage pool list', '--format', 'json')
2737
def create_storage_pool(self, name, provider, size):
2738
"""Create storage pool."""
2739
self.juju('storage pool create',
2741
'size={}'.format(size)))
2743
def ssh_keys(self, full=False):
2744
"""Give the ssh keys registered for the current model."""
2747
args.append('--full')
2748
return self.get_juju_output('authorized-keys list', *args)
2750
def add_ssh_key(self, *keys):
2751
"""Add one or more ssh keys to the current model."""
2752
return self.get_juju_output('authorized-keys add', *keys,
2755
def remove_ssh_key(self, *keys):
2756
"""Remove one or more ssh keys from the current model."""
2757
return self.get_juju_output('authorized-keys delete', *keys,
2760
def import_ssh_key(self, *keys):
2761
"""Import ssh keys from one or more identities to the current model."""
2762
return self.get_juju_output('authorized-keys import', *keys,
2766
2008
class EnvJujuClient22(EnvJujuClient1X):
3046
2284
class Controller:
3047
"""Represents the controller for a model or models."""
3049
2286
def __init__(self, name):
3050
2287
self.name = name
2290
class SimpleEnvironment:
2292
def __init__(self, environment, config=None, juju_home=None,
2294
if controller is None:
2295
controller = Controller(environment)
2296
self.controller = controller
2297
self.environment = environment
2298
self.config = config
2299
self.juju_home = juju_home
2300
if self.config is not None:
2301
self.local = bool(self.config.get('type') == 'local')
2303
self.local and bool(self.config.get('container') == 'kvm'))
2304
self.maas = bool(self.config.get('type') == 'maas')
2305
self.joyent = bool(self.config.get('type') == 'joyent')
2312
def clone(self, model_name=None):
2313
config = deepcopy(self.config)
2314
if model_name is None:
2315
model_name = self.environment
2317
config['name'] = model_name
2318
result = self.__class__(model_name, config, self.juju_home,
2320
result.local = self.local
2321
result.kvm = self.kvm
2322
result.maas = self.maas
2323
result.joyent = self.joyent
2326
def __eq__(self, other):
2327
if type(self) != type(other):
2329
if self.environment != other.environment:
2331
if self.config != other.config:
2333
if self.local != other.local:
2335
if self.maas != other.maas:
2339
def __ne__(self, other):
2340
return not self == other
2342
def set_model_name(self, model_name, set_controller=True):
2344
self.controller.name = model_name
2345
self.environment = model_name
2346
self.config['name'] = model_name
2349
def from_config(cls, name):
2350
return cls._from_config(name)
2353
def _from_config(cls, name):
2354
config, selected = get_selected_environment(name)
2357
return cls(name, config)
2359
def needs_sudo(self):
2363
def make_jes_home(self, juju_home, dir_name, new_config):
2364
home_path = jes_home_path(juju_home, dir_name)
2365
if os.path.exists(home_path):
2367
os.makedirs(home_path)
2368
self.dump_yaml(home_path, new_config)
2371
def dump_yaml(self, path, config):
2372
dump_environments_yaml(path, config)
2375
class JujuData(SimpleEnvironment):
2377
def __init__(self, environment, config=None, juju_home=None,
2379
if juju_home is None:
2380
juju_home = get_juju_home()
2381
super(JujuData, self).__init__(environment, config, juju_home,
2383
self.credentials = {}
2386
def clone(self, model_name=None):
2387
result = super(JujuData, self).clone(model_name)
2388
result.credentials = deepcopy(self.credentials)
2389
result.clouds = deepcopy(self.clouds)
2393
def from_env(cls, env):
2394
juju_data = cls(env.environment, env.config, env.juju_home)
2395
juju_data.load_yaml()
2398
def load_yaml(self):
2400
with open(os.path.join(self.juju_home, 'credentials.yaml')) as f:
2401
self.credentials = yaml.safe_load(f)
2402
except IOError as e:
2403
if e.errno != errno.ENOENT:
2405
'Failed to read credentials file: {}'.format(str(e)))
2406
self.credentials = {}
2408
with open(os.path.join(self.juju_home, 'clouds.yaml')) as f:
2409
self.clouds = yaml.safe_load(f)
2410
except IOError as e:
2411
if e.errno != errno.ENOENT:
2413
'Failed to read clouds file: {}'.format(str(e)))
2414
# Default to an empty clouds file.
2415
self.clouds = {'clouds': {}}
2418
def from_config(cls, name):
2419
juju_data = cls._from_config(name)
2420
juju_data.load_yaml()
2423
def dump_yaml(self, path, config):
2424
"""Dump the configuration files to the specified path.
2426
config is unused, but is accepted for compatibility with
2427
SimpleEnvironment and make_jes_home().
2429
with open(os.path.join(path, 'credentials.yaml'), 'w') as f:
2430
yaml.safe_dump(self.credentials, f)
2431
with open(os.path.join(path, 'clouds.yaml'), 'w') as f:
2432
yaml.safe_dump(self.clouds, f)
2434
def find_endpoint_cloud(self, cloud_type, endpoint):
2435
for cloud, cloud_config in self.clouds['clouds'].items():
2436
if cloud_config['type'] != cloud_type:
2438
if cloud_config['endpoint'] == endpoint:
2440
raise LookupError('No such endpoint: {}'.format(endpoint))
2442
def get_cloud(self):
2443
provider = self.config['type']
2444
# Separate cloud recommended by: Juju Cloud / Credentials / BootStrap /
2445
# Model CLI specification
2446
if provider == 'ec2' and self.config['region'] == 'cn-north-1':
2448
if provider not in ('maas', 'openstack'):
2452
}.get(provider, provider)
2453
if provider == 'maas':
2454
endpoint = self.config['maas-server']
2455
elif provider == 'openstack':
2456
endpoint = self.config['auth-url']
2457
return self.find_endpoint_cloud(provider, endpoint)
2459
def get_region(self):
2460
provider = self.config['type']
2461
if provider == 'azure':
2462
if 'tenant-id' not in self.config:
2463
return self.config['location'].replace(' ', '').lower()
2464
return self.config['location']
2465
elif provider == 'joyent':
2466
matcher = re.compile('https://(.*).api.joyentcloud.com')
2467
return matcher.match(self.config['sdc-url']).group(1)
2468
elif provider == 'lxd':
2470
elif provider == 'manual':
2471
return self.config['bootstrap-host']
2472
elif provider in ('maas', 'manual'):
2475
return self.config['region']
3053
2478
class GroupReporter:
3055
2480
def __init__(self, stream, expected):