ops-cli 2.2.1__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ops/cli/inventory.py +1 -1
- ops/cli/ssh.py +18 -0
- ops/inventory/ec2inventory.py +90 -115
- ops/inventory/plugin/cns.py +7 -7
- ops/inventory/plugin/ec2.py +7 -5
- ops/simplevault.py +116 -46
- ops/terraform/terraform_cmd_generator.py +7 -3
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/METADATA +159 -144
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/RECORD +13 -13
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/WHEEL +1 -1
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/LICENSE +0 -0
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/entry_points.txt +0 -0
- {ops_cli-2.2.1.dist-info → ops_cli-2.3.0.dist-info}/top_level.txt +0 -0
ops/cli/inventory.py
CHANGED
|
@@ -53,7 +53,7 @@ class InventoryRunner(object):
|
|
|
53
53
|
group_names = [group.name for group in host.get_groups()]
|
|
54
54
|
group_names = sorted(group_names)
|
|
55
55
|
group_string = ", ".join(group_names)
|
|
56
|
-
host_id = host.vars.get('
|
|
56
|
+
host_id = host.vars.get('ec2_InstanceId', '')
|
|
57
57
|
if host_id != '':
|
|
58
58
|
name_and_id = "%s -- %s" % (stringc(host.name,
|
|
59
59
|
'blue'), stringc(host_id, 'blue'))
|
ops/cli/ssh.py
CHANGED
|
@@ -92,6 +92,12 @@ class SshParserConfig(SubParserConfig):
|
|
|
92
92
|
help='When using Shell Control Box (SCB) and creating a proxy,'
|
|
93
93
|
'a random port is generated, which will be used in the ssh config '
|
|
94
94
|
'for all playbook, run and sync operations')
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
'--ssh-dest-user',
|
|
97
|
+
type=str,
|
|
98
|
+
dest='ssh_dest_user',
|
|
99
|
+
help='SSH User for the destination host, different from the bastion or SCB user. '
|
|
100
|
+
'Useful when LDAP is not working on the destination host.')
|
|
95
101
|
|
|
96
102
|
def get_help(self):
|
|
97
103
|
return 'SSH or create an SSH tunnel to a server in the cluster'
|
|
@@ -260,6 +266,12 @@ class SshRunner(object):
|
|
|
260
266
|
ssh_config = args.ssh_config or self.ops_config.get(
|
|
261
267
|
'ssh.config') or self.ansible_inventory.get_ssh_config()
|
|
262
268
|
|
|
269
|
+
ssh_host_bastion, ssh_host_dest = None, None
|
|
270
|
+
if args.ssh_dest_user:
|
|
271
|
+
ssh_host_parts = ssh_host.split('--')
|
|
272
|
+
ssh_host_bastion = ssh_host_parts[0]
|
|
273
|
+
ssh_host_dest = ssh_host_parts[1] if len(ssh_host_parts) > 1 else None
|
|
274
|
+
|
|
263
275
|
scb_ssh_host = None
|
|
264
276
|
if scb_enabled:
|
|
265
277
|
# scb->bastion->host vs scb->bastion
|
|
@@ -280,8 +292,14 @@ class SshRunner(object):
|
|
|
280
292
|
else:
|
|
281
293
|
if scb_enabled:
|
|
282
294
|
command = f"ssh -F {ssh_config} {ssh_user}@{scb_ssh_host}"
|
|
295
|
+
if args.ssh_dest_user and ssh_host_dest:
|
|
296
|
+
command = (f"ssh -F {ssh_config} -t {ssh_user}@{ssh_host_bastion}@{scb_host} "
|
|
297
|
+
f"ssh {args.ssh_dest_user}@{ssh_host_dest}")
|
|
283
298
|
else:
|
|
284
299
|
command = f"ssh -F {ssh_config} {ssh_host}"
|
|
300
|
+
if args.ssh_dest_user and ssh_host_dest:
|
|
301
|
+
command = (f"ssh -F {ssh_config} -t {ssh_user}@{ssh_host_bastion} "
|
|
302
|
+
f"ssh {args.ssh_dest_user}@{ssh_host_dest}")
|
|
285
303
|
|
|
286
304
|
if args.proxy:
|
|
287
305
|
if scb_enabled:
|
ops/inventory/ec2inventory.py
CHANGED
|
@@ -11,26 +11,24 @@
|
|
|
11
11
|
import json
|
|
12
12
|
import re
|
|
13
13
|
import sys
|
|
14
|
-
import os
|
|
15
14
|
|
|
16
|
-
import
|
|
17
|
-
from
|
|
18
|
-
from boto.pyami.config import Config
|
|
19
|
-
|
|
20
|
-
from six import iteritems, string_types, integer_types
|
|
15
|
+
import boto3
|
|
16
|
+
from botocore.exceptions import NoRegionError, NoCredentialsError, PartialCredentialsError
|
|
21
17
|
|
|
22
18
|
|
|
23
19
|
class Ec2Inventory(object):
|
|
24
|
-
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _empty_inventory():
|
|
25
22
|
return {"_meta": {"hostvars": {}}}
|
|
26
23
|
|
|
27
|
-
def __init__(self, boto_profile, regions, filters=
|
|
24
|
+
def __init__(self, boto_profile, regions, filters=None, bastion_filters=None):
|
|
28
25
|
|
|
29
|
-
self.filters = filters
|
|
26
|
+
self.filters = filters or []
|
|
30
27
|
self.regions = regions.split(',')
|
|
31
28
|
self.boto_profile = boto_profile
|
|
32
|
-
self.bastion_filters = bastion_filters
|
|
29
|
+
self.bastion_filters = bastion_filters or []
|
|
33
30
|
self.group_callbacks = []
|
|
31
|
+
self.boto3_session = self.create_boto3_session(boto_profile)
|
|
34
32
|
|
|
35
33
|
# Inventory grouped by instance IDs, tags, security groups, regions,
|
|
36
34
|
# and availability zones
|
|
@@ -39,6 +37,25 @@ class Ec2Inventory(object):
|
|
|
39
37
|
# Index of hostname (address) to instance ID
|
|
40
38
|
self.index = {}
|
|
41
39
|
|
|
40
|
+
def create_boto3_session(self, profile_name):
|
|
41
|
+
try:
|
|
42
|
+
# Use the profile to create a session
|
|
43
|
+
session = boto3.Session(profile_name=profile_name)
|
|
44
|
+
|
|
45
|
+
# Verify region
|
|
46
|
+
if not self.regions:
|
|
47
|
+
if not session.region_name:
|
|
48
|
+
raise NoRegionError
|
|
49
|
+
self.regions = [session.region_name]
|
|
50
|
+
|
|
51
|
+
except NoRegionError:
|
|
52
|
+
sys.exit(f"Region not specified and could not be determined for profile: {profile_name}")
|
|
53
|
+
except (NoCredentialsError, PartialCredentialsError):
|
|
54
|
+
sys.exit(f"Credentials not found or incomplete for profile: {profile_name}")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
sys.exit(f"An error occurred: {str(e)}")
|
|
57
|
+
return session
|
|
58
|
+
|
|
42
59
|
def get_as_json(self):
|
|
43
60
|
self.do_api_calls_update_cache()
|
|
44
61
|
return self.json_format_dict(self.inventory, True)
|
|
@@ -55,20 +72,20 @@ class Ec2Inventory(object):
|
|
|
55
72
|
def group(self, *args):
|
|
56
73
|
self.group_callbacks.extend(args)
|
|
57
74
|
|
|
58
|
-
def find_bastion_box(self,
|
|
75
|
+
def find_bastion_box(self, ec2_client):
|
|
59
76
|
"""
|
|
60
77
|
Find ips for the bastion box
|
|
61
78
|
"""
|
|
62
79
|
|
|
63
|
-
if not self.bastion_filters
|
|
80
|
+
if not self.bastion_filters:
|
|
64
81
|
return
|
|
65
82
|
|
|
66
|
-
self.bastion_filters
|
|
83
|
+
self.bastion_filters.append({'Name': 'instance-state-name', 'Values': ['running']})
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
for instance in reservation
|
|
71
|
-
return instance
|
|
85
|
+
reservations = ec2_client.describe_instances(Filters=self.bastion_filters)['Reservations']
|
|
86
|
+
for reservation in reservations:
|
|
87
|
+
for instance in reservation['Instances']:
|
|
88
|
+
return instance['PublicIpAddress']
|
|
72
89
|
|
|
73
90
|
def do_api_calls_update_cache(self):
|
|
74
91
|
""" Do API calls to each region, and save data in cache files """
|
|
@@ -80,92 +97,67 @@ class Ec2Inventory(object):
|
|
|
80
97
|
"""Makes an AWS EC2 API call to the list of instances in a particular
|
|
81
98
|
region
|
|
82
99
|
"""
|
|
100
|
+
ec2_client = self.boto3_session.client('ec2', region_name=region)
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
cfg = Config()
|
|
86
|
-
cfg.load_credential_file(os.path.expanduser("~/.aws/credentials"))
|
|
87
|
-
cfg.load_credential_file(os.path.expanduser("~/.aws/config"))
|
|
88
|
-
session_token = cfg.get(self.boto_profile, "aws_session_token")
|
|
89
|
-
|
|
90
|
-
conn = ec2.connect_to_region(
|
|
91
|
-
region,
|
|
92
|
-
security_token=session_token,
|
|
93
|
-
profile_name=self.boto_profile)
|
|
94
|
-
|
|
95
|
-
# connect_to_region will fail "silently" by returning None if the
|
|
96
|
-
# region name is wrong or not supported
|
|
97
|
-
if conn is None:
|
|
98
|
-
sys.exit(
|
|
99
|
-
"region name: {} likely not supported, or AWS is down. "
|
|
100
|
-
"connection to region failed.".format(region))
|
|
102
|
+
reservations = ec2_client.describe_instances(Filters=self.filters)['Reservations']
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
instances = []
|
|
107
|
-
for reservation in reservations:
|
|
108
|
-
instances.extend(reservation.instances)
|
|
109
|
-
|
|
110
|
-
# sort the instance based on name and index, in this order
|
|
111
|
-
def sort_key(instance):
|
|
112
|
-
name = instance.tags.get('Name', '')
|
|
113
|
-
return "{}-{}".format(name, instance.id)
|
|
114
|
-
|
|
115
|
-
for instance in sorted(instances, key=sort_key):
|
|
116
|
-
self.add_instance(bastion_ip, instance, region)
|
|
104
|
+
bastion_ip = self.find_bastion_box(ec2_client)
|
|
105
|
+
instances = []
|
|
106
|
+
for reservation in reservations:
|
|
107
|
+
instances.extend(reservation['Instances'])
|
|
117
108
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
# sort the instance based on name and index, in this order
|
|
110
|
+
def sort_key(instance):
|
|
111
|
+
name = next((tag['Value'] for tag in instance.get('Tags', [])
|
|
112
|
+
if tag['Key'] == 'Name'), '')
|
|
113
|
+
return "{}-{}".format(name, instance['InstanceId'])
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
for instance in sorted(instances, key=sort_key):
|
|
116
|
+
self.add_instance(bastion_ip, instance, region)
|
|
124
117
|
|
|
125
118
|
def get_instance(self, region, instance_id):
|
|
126
119
|
""" Gets details about a specific instance """
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
ec2_client = self.boto3_session.client('ec2', region_name=region)
|
|
129
121
|
# connect_to_region will fail "silently" by returning None if the
|
|
130
122
|
# region name is wrong or not supported
|
|
131
|
-
if
|
|
123
|
+
if ec2_client is None:
|
|
132
124
|
sys.exit(
|
|
133
125
|
"region name: %s likely not supported, or AWS is down. "
|
|
134
126
|
"connection to region failed." % region
|
|
135
127
|
)
|
|
136
128
|
|
|
137
|
-
reservations =
|
|
129
|
+
reservations = ec2_client.describe_instances(InstanceIds=[instance_id])['Reservations']
|
|
138
130
|
for reservation in reservations:
|
|
139
|
-
for instance in reservation
|
|
131
|
+
for instance in reservation['Instances']:
|
|
140
132
|
return instance
|
|
141
133
|
|
|
142
134
|
def add_instance(self, bastion_ip, instance, region):
|
|
143
135
|
"""
|
|
144
|
-
:type instance:
|
|
136
|
+
:type instance: dict
|
|
145
137
|
"""
|
|
146
138
|
|
|
147
139
|
# Only want running instances unless all_instances is True
|
|
148
|
-
if instance
|
|
140
|
+
if instance['State']['Name'] != 'running':
|
|
149
141
|
return
|
|
150
142
|
|
|
151
143
|
# Use the instance name instead of the public ip
|
|
152
|
-
dest = instance.
|
|
144
|
+
dest = next((tag['Value'] for tag in instance.get('Tags', []) if tag['Key'] == 'Name'), instance.get('PublicIpAddress'))
|
|
153
145
|
if not dest:
|
|
154
146
|
return
|
|
155
147
|
|
|
156
|
-
if bastion_ip and bastion_ip != instance.
|
|
157
|
-
ansible_ssh_host = bastion_ip + "--" + instance.
|
|
158
|
-
elif instance.
|
|
159
|
-
ansible_ssh_host = instance.
|
|
148
|
+
if bastion_ip and bastion_ip != instance.get('PublicIpAddress'):
|
|
149
|
+
ansible_ssh_host = bastion_ip + "--" + instance.get('PrivateIpAddress')
|
|
150
|
+
elif instance.get('PublicIpAddress'):
|
|
151
|
+
ansible_ssh_host = instance.get('PublicIpAddress')
|
|
160
152
|
else:
|
|
161
|
-
ansible_ssh_host = instance.
|
|
153
|
+
ansible_ssh_host = instance.get('PrivateIpAddress')
|
|
162
154
|
|
|
163
155
|
# Add to index and append the instance id afterwards if it's already
|
|
164
156
|
# there
|
|
165
157
|
if dest in self.index:
|
|
166
|
-
dest = dest + "-" + instance.
|
|
158
|
+
dest = dest + "-" + instance['InstanceId'].replace("i-", "")
|
|
167
159
|
|
|
168
|
-
self.index[dest] = [region, instance
|
|
160
|
+
self.index[dest] = [region, instance['InstanceId']]
|
|
169
161
|
|
|
170
162
|
# group with dynamic groups
|
|
171
163
|
for grouping in set(self.group_callbacks):
|
|
@@ -175,9 +167,9 @@ class Ec2Inventory(object):
|
|
|
175
167
|
self.push(self.inventory, group, dest)
|
|
176
168
|
|
|
177
169
|
# Group by all tags
|
|
178
|
-
for tag in instance.
|
|
179
|
-
if tag:
|
|
180
|
-
self.push(self.inventory, tag, dest)
|
|
170
|
+
for tag in instance.get('Tags', []):
|
|
171
|
+
if tag['Value']:
|
|
172
|
+
self.push(self.inventory, tag['Value'], dest)
|
|
181
173
|
|
|
182
174
|
# Inventory: Group by region
|
|
183
175
|
self.push(self.inventory, region, dest)
|
|
@@ -186,56 +178,39 @@ class Ec2Inventory(object):
|
|
|
186
178
|
self.push(self.inventory, ansible_ssh_host, dest)
|
|
187
179
|
|
|
188
180
|
# Inventory: Group by availability zone
|
|
189
|
-
self.push(self.inventory, instance
|
|
181
|
+
self.push(self.inventory, instance['Placement']['AvailabilityZone'], dest)
|
|
190
182
|
|
|
191
|
-
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(
|
|
192
|
-
instance)
|
|
183
|
+
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
|
|
193
184
|
self.inventory["_meta"]["hostvars"][dest]['ansible_ssh_host'] = ansible_ssh_host
|
|
194
185
|
|
|
195
186
|
def get_host_info_dict_from_instance(self, instance):
|
|
196
187
|
instance_vars = {}
|
|
197
|
-
for key in
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
instance_vars[
|
|
207
|
-
elif key == 'ec2__previous_state':
|
|
208
|
-
instance_vars['ec2_previous_state'] = instance.previous_state or ''
|
|
209
|
-
instance_vars['ec2_previous_state_code'] = instance.previous_state_code
|
|
210
|
-
elif type(value) in integer_types or isinstance(value, bool):
|
|
211
|
-
instance_vars[key] = value
|
|
212
|
-
elif type(value) in string_types:
|
|
213
|
-
instance_vars[key] = value.strip()
|
|
188
|
+
for key, value in instance.items():
|
|
189
|
+
safe_key = self.to_safe('ec2_' + key)
|
|
190
|
+
|
|
191
|
+
if key == 'State':
|
|
192
|
+
instance_vars['ec2_state'] = value['Name']
|
|
193
|
+
instance_vars['ec2_state_code'] = value['Code']
|
|
194
|
+
elif isinstance(value, (int, bool)):
|
|
195
|
+
instance_vars[safe_key] = value
|
|
196
|
+
elif isinstance(value, str):
|
|
197
|
+
instance_vars[safe_key] = value.strip()
|
|
214
198
|
elif value is None:
|
|
215
|
-
instance_vars[
|
|
216
|
-
elif key == '
|
|
217
|
-
instance_vars[
|
|
218
|
-
elif key == '
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
group_ids = []
|
|
226
|
-
group_names = []
|
|
227
|
-
for group in value:
|
|
228
|
-
group_ids.append(group.id)
|
|
229
|
-
group_names.append(group.name)
|
|
199
|
+
instance_vars[safe_key] = ''
|
|
200
|
+
elif key == 'Placement':
|
|
201
|
+
instance_vars['ec2_placement'] = value['AvailabilityZone']
|
|
202
|
+
elif key == 'Tags':
|
|
203
|
+
for tag in value:
|
|
204
|
+
tag_key = self.to_safe('ec2_tag_' + tag['Key'])
|
|
205
|
+
instance_vars[tag_key] = tag['Value']
|
|
206
|
+
elif key == 'SecurityGroups':
|
|
207
|
+
group_ids = [group['GroupId'] for group in value]
|
|
208
|
+
group_names = [group['GroupName'] for group in value]
|
|
230
209
|
instance_vars["ec2_security_group_ids"] = ','.join(group_ids)
|
|
231
|
-
instance_vars["ec2_security_group_names"] = ','.join(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
instance_vars['private_ip'] = instance_vars.get(
|
|
236
|
-
'ec2_private_ip_address', '')
|
|
237
|
-
instance_vars['private_ip_address'] = instance_vars.get(
|
|
238
|
-
'ec2_private_ip_address', '')
|
|
210
|
+
instance_vars["ec2_security_group_names"] = ','.join(group_names)
|
|
211
|
+
|
|
212
|
+
instance_vars['private_ip'] = instance.get('PrivateIpAddress', '')
|
|
213
|
+
instance_vars['private_ip_address'] = instance.get('PrivateIpAddress', '')
|
|
239
214
|
return instance_vars
|
|
240
215
|
|
|
241
216
|
def get_host_info(self):
|
ops/inventory/plugin/cns.py
CHANGED
|
@@ -27,13 +27,13 @@ def cns(args):
|
|
|
27
27
|
region=region,
|
|
28
28
|
boto_profile=profile,
|
|
29
29
|
cache=args.get('cache', 3600 * 24),
|
|
30
|
-
filters=
|
|
31
|
-
'tag:cluster': cns_cluster
|
|
32
|
-
|
|
33
|
-
bastion=
|
|
34
|
-
'tag:cluster': cns_cluster,
|
|
35
|
-
'tag:role': 'bastion'
|
|
36
|
-
|
|
30
|
+
filters=[
|
|
31
|
+
{'Name': 'tag:cluster', 'Values': [cns_cluster]}
|
|
32
|
+
],
|
|
33
|
+
bastion=[
|
|
34
|
+
{'Name': 'tag:cluster', 'Values': [cns_cluster]},
|
|
35
|
+
{'Name': 'tag:role', 'Values': ['bastion']}
|
|
36
|
+
]
|
|
37
37
|
))
|
|
38
38
|
|
|
39
39
|
merge_inventories(result, json.loads(jsn))
|
ops/inventory/plugin/ec2.py
CHANGED
|
@@ -12,15 +12,17 @@ from ops.inventory.ec2inventory import Ec2Inventory
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def ec2(args):
|
|
15
|
-
filters = args.get('filters',
|
|
16
|
-
bastion_filters = args.get('bastion',
|
|
15
|
+
filters = args.get('filters', [])
|
|
16
|
+
bastion_filters = args.get('bastion', [])
|
|
17
17
|
|
|
18
18
|
if args.get('cluster') and not args.get('filters'):
|
|
19
|
-
filters['tag:cluster'
|
|
19
|
+
filters = [{'Name': 'tag:cluster', 'Values': [args.get('cluster')]}]
|
|
20
20
|
|
|
21
21
|
if args.get('cluster') and not args.get('bastion'):
|
|
22
|
-
bastion_filters
|
|
23
|
-
|
|
22
|
+
bastion_filters = [
|
|
23
|
+
{'Name': 'tag:cluster', 'Values': [args.get('cluster')]},
|
|
24
|
+
{'Name': 'tag:role', 'Values': ['bastion']}
|
|
25
|
+
]
|
|
24
26
|
|
|
25
27
|
return Ec2Inventory(boto_profile=args['boto_profile'],
|
|
26
28
|
regions=args['region'],
|
ops/simplevault.py
CHANGED
|
@@ -19,11 +19,12 @@ Very simple secrets management that can be used to
|
|
|
19
19
|
(ex: generate it only if it's not already there)
|
|
20
20
|
- it will also attempt login, if it will be required
|
|
21
21
|
'''
|
|
22
|
+
|
|
22
23
|
import os
|
|
23
24
|
import hvac
|
|
24
25
|
import getpass
|
|
25
26
|
from .cli import display
|
|
26
|
-
from six import iteritems
|
|
27
|
+
from six import iteritems, string_types
|
|
27
28
|
|
|
28
29
|
MAX_LDAP_ATTEMPTS = 3
|
|
29
30
|
|
|
@@ -35,30 +36,6 @@ class SimpleVault(object):
|
|
|
35
36
|
def __init__(
|
|
36
37
|
self, vault_user=None, vault_addr=None, vault_token=None, namespace=None,
|
|
37
38
|
mount_point=None, persistent_session=True, auto_prompt=True):
|
|
38
|
-
def try_reading_token_file():
|
|
39
|
-
ret = None
|
|
40
|
-
try:
|
|
41
|
-
ret = open(
|
|
42
|
-
os.path.expanduser('~/.vault-token'),
|
|
43
|
-
"r").read().strip()
|
|
44
|
-
except Exception:
|
|
45
|
-
ret = None
|
|
46
|
-
pass
|
|
47
|
-
return ret
|
|
48
|
-
|
|
49
|
-
def write_token(token=None):
|
|
50
|
-
try:
|
|
51
|
-
if token:
|
|
52
|
-
open(
|
|
53
|
-
os.path.expanduser('~/.vault-token'),
|
|
54
|
-
"w").write(
|
|
55
|
-
token.strip())
|
|
56
|
-
except Exception:
|
|
57
|
-
display(
|
|
58
|
-
"Warning: could not persist token to ~/.vault-token",
|
|
59
|
-
stderr=False,
|
|
60
|
-
color='yellow')
|
|
61
|
-
pass
|
|
62
39
|
|
|
63
40
|
self.vault_addr = vault_addr or os.getenv(
|
|
64
41
|
'VAULT_ADDR', None) or "http://localhost:8200"
|
|
@@ -66,7 +43,7 @@ class SimpleVault(object):
|
|
|
66
43
|
# How often we will create infrastructures
|
|
67
44
|
# with vault running on the provisioner's machine ?
|
|
68
45
|
self.vault_token = vault_token or os.getenv(
|
|
69
|
-
'VAULT_TOKEN', None) or try_reading_token_file()
|
|
46
|
+
'VAULT_TOKEN', None) or self.try_reading_token_file()
|
|
70
47
|
self.vault_user = vault_user or os.getenv(
|
|
71
48
|
'VAULT_USER', None) or getpass.getuser()
|
|
72
49
|
self.mount_point = mount_point
|
|
@@ -82,34 +59,127 @@ class SimpleVault(object):
|
|
|
82
59
|
namespace=self.namespace,
|
|
83
60
|
token=self.vault_token)
|
|
84
61
|
|
|
62
|
+
auth_methods = {
|
|
63
|
+
'ldap': self.auth_with_ldap,
|
|
64
|
+
'okta': self.auth_with_okta
|
|
65
|
+
}
|
|
66
|
+
auth_method = os.getenv('OPS_VAULT_AUTH_METHOD', 'okta')
|
|
67
|
+
|
|
85
68
|
while not self.vault_conn.is_authenticated() and auto_prompt:
|
|
86
69
|
display("VAULT-LIB: Not authenticated to vault '%s'" %
|
|
87
70
|
self.vault_addr, stderr=True, color='red')
|
|
88
|
-
display("Note: the default
|
|
71
|
+
display("Note: the default Vault username (%s) can be overwritten with VAULT_USER"
|
|
89
72
|
% self.vault_user, stderr=True,
|
|
90
73
|
color='yellow')
|
|
91
74
|
display(
|
|
92
75
|
" or to pass a token directly use VAULT_TOKEN",
|
|
93
76
|
stderr=True,
|
|
94
77
|
color='yellow')
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
78
|
+
|
|
79
|
+
auth_method_func = auth_methods.get(auth_method)
|
|
80
|
+
if not auth_method_func:
|
|
81
|
+
raise ValueError(f"Unsupported authentication method: {auth_method}")
|
|
82
|
+
auth_method_func()
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def try_reading_token_file():
|
|
86
|
+
ret = None
|
|
87
|
+
try:
|
|
88
|
+
ret = open(
|
|
89
|
+
os.path.expanduser('~/.vault-token'),
|
|
90
|
+
"r").read().strip()
|
|
91
|
+
except Exception:
|
|
92
|
+
ret = None
|
|
93
|
+
pass
|
|
94
|
+
return ret
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def write_token(token=None):
|
|
98
|
+
try:
|
|
99
|
+
if token:
|
|
100
|
+
open(
|
|
101
|
+
os.path.expanduser('~/.vault-token'),
|
|
102
|
+
"w").write(
|
|
103
|
+
token.strip())
|
|
104
|
+
except Exception:
|
|
105
|
+
display(
|
|
106
|
+
"Warning: could not persist token to ~/.vault-token",
|
|
107
|
+
stderr=False,
|
|
108
|
+
color='yellow')
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
def auth_with_ldap(self):
|
|
112
|
+
# LDAP authentication logic
|
|
113
|
+
try:
|
|
114
|
+
self.ldap_attempts += 1
|
|
115
|
+
ldap_password = getpass.getpass(
|
|
116
|
+
prompt='LDAP password for %s for server %s: ' %
|
|
117
|
+
(self.vault_user, self.vault_addr))
|
|
118
|
+
auth_response = self.vault_conn.auth.ldap.login(
|
|
119
|
+
username=self.vault_user, password=ldap_password)
|
|
120
|
+
self.vault_conn.is_authenticated()
|
|
121
|
+
self.vault_token = auth_response['auth']['client_token']
|
|
122
|
+
self.write_token(self.vault_token)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
if self.ldap_attempts >= MAX_LDAP_ATTEMPTS:
|
|
125
|
+
display(
|
|
126
|
+
"FAILED authentication {} times".format(
|
|
127
|
+
self.ldap_attempts), color='red')
|
|
128
|
+
raise e
|
|
129
|
+
else:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def auth_with_okta(self):
|
|
133
|
+
try:
|
|
134
|
+
okta_password = getpass.getpass(
|
|
135
|
+
prompt=f"Okta password for {self.vault_user}: ")
|
|
136
|
+
|
|
137
|
+
display("Authenticating with Okta...", color='yellow')
|
|
138
|
+
auth_response = self.vault_conn.auth.okta.login(
|
|
139
|
+
username=self.vault_user, password=okta_password)
|
|
140
|
+
|
|
141
|
+
# Check the MFA requirement
|
|
142
|
+
auth_data = auth_response.get("auth", {})
|
|
143
|
+
mfa_requirement = auth_data.get("mfa_requirement")
|
|
144
|
+
|
|
145
|
+
mfa_request_id = mfa_requirement.get("mfa_request_id")
|
|
146
|
+
mfa_constraints = mfa_requirement.get("mfa_constraints", {})
|
|
147
|
+
|
|
148
|
+
okta_factors = mfa_constraints.get("okta", {}).get("any", [])
|
|
149
|
+
mfa_factor_id = okta_factors[0].get("id")
|
|
150
|
+
|
|
151
|
+
# Create the MFA validation payload
|
|
152
|
+
mfa_payload = {
|
|
153
|
+
"mfa_request_id": mfa_request_id,
|
|
154
|
+
"mfa_payload": {
|
|
155
|
+
mfa_factor_id: [] # No MFA factor specific data required
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Make the MFA validation request
|
|
160
|
+
display("Performing Okta MFA validation. Check notification...", color='yellow')
|
|
161
|
+
mfa_response = self.vault_conn.adapter.post(
|
|
162
|
+
"/v1/sys/mfa/validate",
|
|
163
|
+
json=mfa_payload,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Update token if provided after MFA
|
|
167
|
+
self.vault_token = self.vault_conn.adapter.get_login_token(mfa_response)
|
|
168
|
+
self.vault_conn.token = self.vault_token
|
|
169
|
+
self.write_token(self.vault_token)
|
|
170
|
+
if self.vault_conn.is_authenticated():
|
|
171
|
+
display("Okta MFA validation successful, token obtained.", color='green')
|
|
172
|
+
else:
|
|
173
|
+
display("Okta MFA validation failed."
|
|
174
|
+
"Please obtain a token manually (vault cli) and run ops again.",
|
|
175
|
+
stderr=True, color='red')
|
|
176
|
+
exit(1)
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
display(f"An error occurred during Okta authentication: {e}\n"
|
|
180
|
+
"Please obtain a token manually (vault cli) and run ops again.",
|
|
181
|
+
stderr=True, color='red')
|
|
182
|
+
exit(1)
|
|
113
183
|
|
|
114
184
|
def get(self, path, key='value', wrap_ttl=None,
|
|
115
185
|
default=None, fetch_all=False, raw=False):
|
|
@@ -149,7 +219,7 @@ class SimpleVault(object):
|
|
|
149
219
|
|
|
150
220
|
def put(self, path, value, lease=None, wrap_ttl=None):
|
|
151
221
|
payload = {}
|
|
152
|
-
if isinstance(value, (
|
|
222
|
+
if isinstance(value, (string_types, int, float, bool)):
|
|
153
223
|
payload['value'] = str(value)
|
|
154
224
|
elif isinstance(value, dict):
|
|
155
225
|
for k, v in iteritems(value):
|
|
@@ -34,8 +34,10 @@ class TerraformCommandGenerator(object):
|
|
|
34
34
|
current_terraform_version = self.check_terraform_version()
|
|
35
35
|
config = self.cluster_config
|
|
36
36
|
|
|
37
|
-
current_terraform_version_major = int(
|
|
38
|
-
|
|
37
|
+
current_terraform_version_major = int(current_terraform_version.removeprefix('v')
|
|
38
|
+
.split('.')[0])
|
|
39
|
+
current_terraform_version_minor = int(current_terraform_version
|
|
40
|
+
.split('.')[1])
|
|
39
41
|
if 'enable_consul_remote_state' in config['terraform']:
|
|
40
42
|
terraform_remote_state = config['terraform']['enable_consul_remote_state']
|
|
41
43
|
elif config['terraform'].get('state', {'type': None}).get('type') == 's3':
|
|
@@ -69,7 +71,9 @@ class TerraformCommandGenerator(object):
|
|
|
69
71
|
cluster=config['cluster'])
|
|
70
72
|
landscape = ''
|
|
71
73
|
|
|
72
|
-
if current_terraform_version_major
|
|
74
|
+
if (current_terraform_version_major == 0 and
|
|
75
|
+
current_terraform_version_minor >= 9 or
|
|
76
|
+
current_terraform_version_major > 0):
|
|
73
77
|
if args.force_copy:
|
|
74
78
|
terraform_init_command = 'terraform init -force-copy && '
|
|
75
79
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: ops-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Ops - wrapper for Terraform, Ansible, and SSH for cloud automation
|
|
5
5
|
Home-page: https://github.com/adobe/ops-cli
|
|
6
6
|
Author: Adobe
|
|
@@ -23,146 +23,155 @@ Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
|
23
23
|
Requires-Python: >=3.5
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
|
-
Requires-Dist: adal
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: azure
|
|
31
|
-
Requires-Dist: azure-
|
|
32
|
-
Requires-Dist: azure-
|
|
33
|
-
Requires-Dist: azure-
|
|
34
|
-
Requires-Dist: azure-
|
|
35
|
-
Requires-Dist: azure-
|
|
36
|
-
Requires-Dist: azure-
|
|
37
|
-
Requires-Dist: azure-
|
|
38
|
-
Requires-Dist: azure-
|
|
39
|
-
Requires-Dist: azure-
|
|
40
|
-
Requires-Dist: azure-
|
|
41
|
-
Requires-Dist: azure-
|
|
42
|
-
Requires-Dist: azure-mgmt
|
|
43
|
-
Requires-Dist: azure-mgmt-
|
|
44
|
-
Requires-Dist: azure-mgmt-
|
|
45
|
-
Requires-Dist: azure-mgmt-
|
|
46
|
-
Requires-Dist: azure-mgmt-
|
|
47
|
-
Requires-Dist: azure-mgmt-
|
|
48
|
-
Requires-Dist: azure-mgmt-
|
|
49
|
-
Requires-Dist: azure-mgmt-
|
|
50
|
-
Requires-Dist: azure-mgmt-
|
|
51
|
-
Requires-Dist: azure-mgmt-
|
|
52
|
-
Requires-Dist: azure-mgmt-
|
|
53
|
-
Requires-Dist: azure-mgmt-
|
|
54
|
-
Requires-Dist: azure-mgmt-
|
|
55
|
-
Requires-Dist: azure-mgmt-
|
|
56
|
-
Requires-Dist: azure-mgmt-
|
|
57
|
-
Requires-Dist: azure-mgmt-
|
|
58
|
-
Requires-Dist: azure-mgmt-
|
|
59
|
-
Requires-Dist: azure-mgmt-
|
|
60
|
-
Requires-Dist: azure-mgmt-
|
|
61
|
-
Requires-Dist: azure-mgmt-
|
|
62
|
-
Requires-Dist: azure-mgmt-
|
|
63
|
-
Requires-Dist: azure-mgmt-
|
|
64
|
-
Requires-Dist: azure-mgmt-
|
|
65
|
-
Requires-Dist: azure-mgmt-
|
|
66
|
-
Requires-Dist: azure-mgmt-
|
|
67
|
-
Requires-Dist: azure-mgmt-
|
|
68
|
-
Requires-Dist: azure-mgmt-
|
|
69
|
-
Requires-Dist: azure-mgmt-
|
|
70
|
-
Requires-Dist: azure-mgmt-
|
|
71
|
-
Requires-Dist: azure-mgmt-
|
|
72
|
-
Requires-Dist: azure-mgmt-
|
|
73
|
-
Requires-Dist: azure-mgmt-
|
|
74
|
-
Requires-Dist: azure-mgmt-
|
|
75
|
-
Requires-Dist: azure-mgmt-
|
|
76
|
-
Requires-Dist: azure-mgmt-
|
|
77
|
-
Requires-Dist: azure-mgmt-
|
|
78
|
-
Requires-Dist: azure-mgmt-
|
|
79
|
-
Requires-Dist: azure-mgmt-
|
|
80
|
-
Requires-Dist: azure-mgmt-
|
|
81
|
-
Requires-Dist: azure-mgmt-
|
|
82
|
-
Requires-Dist: azure-mgmt-
|
|
83
|
-
Requires-Dist: azure-mgmt-
|
|
84
|
-
Requires-Dist: azure-mgmt-
|
|
85
|
-
Requires-Dist: azure-mgmt-
|
|
86
|
-
Requires-Dist: azure-mgmt-
|
|
87
|
-
Requires-Dist: azure-mgmt-
|
|
88
|
-
Requires-Dist: azure-mgmt-
|
|
89
|
-
Requires-Dist: azure-mgmt-
|
|
90
|
-
Requires-Dist: azure-mgmt-
|
|
91
|
-
Requires-Dist: azure-mgmt-
|
|
92
|
-
Requires-Dist: azure-mgmt-
|
|
93
|
-
Requires-Dist: azure-mgmt-
|
|
94
|
-
Requires-Dist: azure-mgmt-
|
|
95
|
-
Requires-Dist: azure-mgmt-
|
|
96
|
-
Requires-Dist: azure-mgmt-
|
|
97
|
-
Requires-Dist: azure-mgmt-
|
|
98
|
-
Requires-Dist: azure-mgmt-
|
|
99
|
-
Requires-Dist: azure-mgmt-
|
|
100
|
-
Requires-Dist: azure-mgmt-
|
|
101
|
-
Requires-Dist: azure-
|
|
102
|
-
Requires-Dist: azure-
|
|
103
|
-
Requires-Dist: azure-
|
|
104
|
-
Requires-Dist: azure-
|
|
105
|
-
Requires-Dist: azure-
|
|
106
|
-
Requires-Dist: azure-
|
|
107
|
-
Requires-Dist: azure-
|
|
108
|
-
Requires-Dist: azure-
|
|
109
|
-
Requires-Dist:
|
|
110
|
-
Requires-Dist:
|
|
111
|
-
Requires-Dist:
|
|
112
|
-
Requires-Dist:
|
|
113
|
-
Requires-Dist: lru-cache
|
|
114
|
-
Requires-Dist:
|
|
115
|
-
Requires-Dist:
|
|
116
|
-
Requires-Dist:
|
|
117
|
-
Requires-Dist:
|
|
118
|
-
Requires-Dist:
|
|
119
|
-
Requires-Dist:
|
|
120
|
-
Requires-Dist:
|
|
121
|
-
Requires-Dist:
|
|
122
|
-
Requires-Dist:
|
|
123
|
-
Requires-Dist:
|
|
124
|
-
Requires-Dist:
|
|
125
|
-
Requires-Dist:
|
|
126
|
-
Requires-Dist:
|
|
127
|
-
Requires-Dist:
|
|
128
|
-
Requires-Dist: himl
|
|
129
|
-
Requires-Dist:
|
|
130
|
-
Requires-Dist:
|
|
131
|
-
Requires-Dist:
|
|
132
|
-
Requires-Dist:
|
|
133
|
-
Requires-Dist:
|
|
134
|
-
Requires-Dist:
|
|
135
|
-
Requires-Dist:
|
|
136
|
-
Requires-Dist:
|
|
137
|
-
Requires-Dist:
|
|
138
|
-
Requires-Dist:
|
|
139
|
-
Requires-Dist:
|
|
140
|
-
Requires-Dist:
|
|
141
|
-
Requires-Dist: oauthlib
|
|
142
|
-
Requires-Dist:
|
|
143
|
-
Requires-Dist:
|
|
144
|
-
Requires-Dist:
|
|
145
|
-
Requires-Dist:
|
|
146
|
-
Requires-Dist:
|
|
147
|
-
Requires-Dist:
|
|
148
|
-
Requires-Dist:
|
|
149
|
-
Requires-Dist:
|
|
150
|
-
Requires-Dist:
|
|
151
|
-
Requires-Dist:
|
|
152
|
-
Requires-Dist:
|
|
153
|
-
Requires-Dist:
|
|
154
|
-
Requires-Dist:
|
|
155
|
-
Requires-Dist:
|
|
156
|
-
Requires-Dist:
|
|
157
|
-
Requires-Dist:
|
|
158
|
-
Requires-Dist:
|
|
159
|
-
Requires-Dist:
|
|
160
|
-
Requires-Dist:
|
|
161
|
-
Requires-Dist:
|
|
162
|
-
Requires-Dist: typing-extensions
|
|
163
|
-
Requires-Dist:
|
|
164
|
-
Requires-Dist:
|
|
165
|
-
|
|
26
|
+
Requires-Dist: adal==1.2.7
|
|
27
|
+
Requires-Dist: ansible==8.7.0; python_version >= "3.9"
|
|
28
|
+
Requires-Dist: ansible-core==2.15.13; python_version >= "3.9"
|
|
29
|
+
Requires-Dist: awscli==1.32.6; python_version >= "3.8"
|
|
30
|
+
Requires-Dist: azure==4.0.0
|
|
31
|
+
Requires-Dist: azure-applicationinsights==0.1.1
|
|
32
|
+
Requires-Dist: azure-batch==4.1.3
|
|
33
|
+
Requires-Dist: azure-common==1.1.28
|
|
34
|
+
Requires-Dist: azure-core==1.32.0; python_version >= "3.8"
|
|
35
|
+
Requires-Dist: azure-cosmosdb-nspkg==2.0.2
|
|
36
|
+
Requires-Dist: azure-cosmosdb-table==1.0.6
|
|
37
|
+
Requires-Dist: azure-datalake-store==0.0.53
|
|
38
|
+
Requires-Dist: azure-eventgrid==1.3.0
|
|
39
|
+
Requires-Dist: azure-graphrbac==0.40.0
|
|
40
|
+
Requires-Dist: azure-keyvault==1.1.0
|
|
41
|
+
Requires-Dist: azure-loganalytics==0.1.1
|
|
42
|
+
Requires-Dist: azure-mgmt==4.0.0
|
|
43
|
+
Requires-Dist: azure-mgmt-advisor==1.0.1
|
|
44
|
+
Requires-Dist: azure-mgmt-applicationinsights==0.1.1
|
|
45
|
+
Requires-Dist: azure-mgmt-authorization==0.50.0
|
|
46
|
+
Requires-Dist: azure-mgmt-batch==5.0.1
|
|
47
|
+
Requires-Dist: azure-mgmt-batchai==2.0.0
|
|
48
|
+
Requires-Dist: azure-mgmt-billing==0.2.0
|
|
49
|
+
Requires-Dist: azure-mgmt-cdn==3.1.0
|
|
50
|
+
Requires-Dist: azure-mgmt-cognitiveservices==3.0.0
|
|
51
|
+
Requires-Dist: azure-mgmt-commerce==1.0.1
|
|
52
|
+
Requires-Dist: azure-mgmt-compute==4.6.2
|
|
53
|
+
Requires-Dist: azure-mgmt-consumption==2.0.0
|
|
54
|
+
Requires-Dist: azure-mgmt-containerinstance==1.5.0
|
|
55
|
+
Requires-Dist: azure-mgmt-containerregistry==2.8.0
|
|
56
|
+
Requires-Dist: azure-mgmt-containerservice==4.4.0
|
|
57
|
+
Requires-Dist: azure-mgmt-cosmosdb==0.4.1
|
|
58
|
+
Requires-Dist: azure-mgmt-datafactory==0.6.0
|
|
59
|
+
Requires-Dist: azure-mgmt-datalake-analytics==0.6.0
|
|
60
|
+
Requires-Dist: azure-mgmt-datalake-nspkg==3.0.1
|
|
61
|
+
Requires-Dist: azure-mgmt-datalake-store==0.5.0
|
|
62
|
+
Requires-Dist: azure-mgmt-datamigration==1.0.0
|
|
63
|
+
Requires-Dist: azure-mgmt-devspaces==0.1.0
|
|
64
|
+
Requires-Dist: azure-mgmt-devtestlabs==2.2.0
|
|
65
|
+
Requires-Dist: azure-mgmt-dns==2.1.0
|
|
66
|
+
Requires-Dist: azure-mgmt-eventgrid==1.0.0
|
|
67
|
+
Requires-Dist: azure-mgmt-eventhub==2.6.0
|
|
68
|
+
Requires-Dist: azure-mgmt-hanaonazure==0.1.1
|
|
69
|
+
Requires-Dist: azure-mgmt-iotcentral==0.1.0
|
|
70
|
+
Requires-Dist: azure-mgmt-iothub==0.5.0
|
|
71
|
+
Requires-Dist: azure-mgmt-iothubprovisioningservices==0.2.0
|
|
72
|
+
Requires-Dist: azure-mgmt-keyvault==1.1.0
|
|
73
|
+
Requires-Dist: azure-mgmt-loganalytics==0.2.0
|
|
74
|
+
Requires-Dist: azure-mgmt-logic==3.0.0
|
|
75
|
+
Requires-Dist: azure-mgmt-machinelearningcompute==0.4.1
|
|
76
|
+
Requires-Dist: azure-mgmt-managementgroups==0.1.0
|
|
77
|
+
Requires-Dist: azure-mgmt-managementpartner==0.1.1
|
|
78
|
+
Requires-Dist: azure-mgmt-maps==0.1.0
|
|
79
|
+
Requires-Dist: azure-mgmt-marketplaceordering==0.1.0
|
|
80
|
+
Requires-Dist: azure-mgmt-media==1.0.1
|
|
81
|
+
Requires-Dist: azure-mgmt-monitor==0.5.2
|
|
82
|
+
Requires-Dist: azure-mgmt-msi==0.2.0
|
|
83
|
+
Requires-Dist: azure-mgmt-network==2.7.0
|
|
84
|
+
Requires-Dist: azure-mgmt-notificationhubs==2.1.0
|
|
85
|
+
Requires-Dist: azure-mgmt-nspkg==3.0.2
|
|
86
|
+
Requires-Dist: azure-mgmt-policyinsights==0.1.0
|
|
87
|
+
Requires-Dist: azure-mgmt-powerbiembedded==2.0.0
|
|
88
|
+
Requires-Dist: azure-mgmt-rdbms==1.9.0
|
|
89
|
+
Requires-Dist: azure-mgmt-recoveryservices==0.3.0
|
|
90
|
+
Requires-Dist: azure-mgmt-recoveryservicesbackup==0.3.0
|
|
91
|
+
Requires-Dist: azure-mgmt-redis==5.0.0
|
|
92
|
+
Requires-Dist: azure-mgmt-relay==0.1.0
|
|
93
|
+
Requires-Dist: azure-mgmt-reservations==0.2.1
|
|
94
|
+
Requires-Dist: azure-mgmt-resource==2.2.0
|
|
95
|
+
Requires-Dist: azure-mgmt-scheduler==2.0.0
|
|
96
|
+
Requires-Dist: azure-mgmt-search==2.1.0
|
|
97
|
+
Requires-Dist: azure-mgmt-servicebus==0.5.3
|
|
98
|
+
Requires-Dist: azure-mgmt-servicefabric==0.2.0
|
|
99
|
+
Requires-Dist: azure-mgmt-signalr==0.1.1
|
|
100
|
+
Requires-Dist: azure-mgmt-sql==0.9.1
|
|
101
|
+
Requires-Dist: azure-mgmt-storage==2.0.0
|
|
102
|
+
Requires-Dist: azure-mgmt-subscription==0.2.0
|
|
103
|
+
Requires-Dist: azure-mgmt-trafficmanager==0.50.0
|
|
104
|
+
Requires-Dist: azure-mgmt-web==0.35.0
|
|
105
|
+
Requires-Dist: azure-nspkg==3.0.2
|
|
106
|
+
Requires-Dist: azure-servicebus==0.21.1
|
|
107
|
+
Requires-Dist: azure-servicefabric==6.3.0.0
|
|
108
|
+
Requires-Dist: azure-servicemanagement-legacy==0.20.8
|
|
109
|
+
Requires-Dist: azure-storage-blob==1.5.0
|
|
110
|
+
Requires-Dist: azure-storage-common==1.4.2
|
|
111
|
+
Requires-Dist: azure-storage-file==1.4.0
|
|
112
|
+
Requires-Dist: azure-storage-queue==1.4.0
|
|
113
|
+
Requires-Dist: backports.functools-lru-cache==1.6.6; python_version >= "2.6"
|
|
114
|
+
Requires-Dist: boto3==1.34.6; python_version >= "3.8"
|
|
115
|
+
Requires-Dist: botocore==1.34.6; python_version >= "3.8"
|
|
116
|
+
Requires-Dist: cachetools==5.5.0; python_version >= "3.7"
|
|
117
|
+
Requires-Dist: certifi==2024.12.14; python_version >= "3.6"
|
|
118
|
+
Requires-Dist: cffi==1.17.1; python_version >= "3.8"
|
|
119
|
+
Requires-Dist: charset-normalizer==3.4.1; python_version >= "3.7"
|
|
120
|
+
Requires-Dist: colorama==0.4.4; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4"
|
|
121
|
+
Requires-Dist: cryptography==44.0.0; python_version >= "3.7" and python_full_version not in "3.9.0, 3.9.1"
|
|
122
|
+
Requires-Dist: deepmerge==1.1.1
|
|
123
|
+
Requires-Dist: docutils==0.16; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4"
|
|
124
|
+
Requires-Dist: gitdb==4.0.12; python_version >= "3.7"
|
|
125
|
+
Requires-Dist: gitpython==3.1.44; python_version >= "3.7"
|
|
126
|
+
Requires-Dist: google-auth==2.37.0; python_version >= "3.7"
|
|
127
|
+
Requires-Dist: hashmerge==0.2
|
|
128
|
+
Requires-Dist: himl==0.15.2; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
129
|
+
Requires-Dist: hvac==1.2.1; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
|
|
130
|
+
Requires-Dist: idna==3.10; python_version >= "3.6"
|
|
131
|
+
Requires-Dist: inflection==0.5.1; python_version >= "3.5"
|
|
132
|
+
Requires-Dist: isodate==0.7.2; python_version >= "3.7"
|
|
133
|
+
Requires-Dist: jinja2==3.1.4; python_version >= "3.7"
|
|
134
|
+
Requires-Dist: jmespath==1.0.1; python_version >= "3.7"
|
|
135
|
+
Requires-Dist: kubernetes==26.1.0; python_version >= "3.6"
|
|
136
|
+
Requires-Dist: lru-cache==0.2.3
|
|
137
|
+
Requires-Dist: markupsafe==3.0.2; python_version >= "3.9"
|
|
138
|
+
Requires-Dist: msal==1.31.1; python_version >= "3.7"
|
|
139
|
+
Requires-Dist: msrest==0.7.1; python_version >= "3.6"
|
|
140
|
+
Requires-Dist: msrestazure==0.6.4
|
|
141
|
+
Requires-Dist: oauthlib==3.2.2; python_version >= "3.6"
|
|
142
|
+
Requires-Dist: packaging==24.2; python_version >= "3.8"
|
|
143
|
+
Requires-Dist: passgen==1.1.1
|
|
144
|
+
Requires-Dist: pathlib2==2.3.7.post1
|
|
145
|
+
Requires-Dist: pyasn1==0.6.1; python_version >= "3.8"
|
|
146
|
+
Requires-Dist: pyasn1-modules==0.4.1; python_version >= "3.8"
|
|
147
|
+
Requires-Dist: pycparser==2.22; python_version >= "3.8"
|
|
148
|
+
Requires-Dist: pyhcl==0.4.5
|
|
149
|
+
Requires-Dist: pyjwt[crypto]==2.10.1; python_version >= "3.9"
|
|
150
|
+
Requires-Dist: python-consul==1.1.0
|
|
151
|
+
Requires-Dist: python-dateutil==2.9.0.post0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
|
|
152
|
+
Requires-Dist: pyyaml==6.0.1; python_version >= "3.6"
|
|
153
|
+
Requires-Dist: requests==2.32.3; python_version >= "3.8"
|
|
154
|
+
Requires-Dist: requests-oauthlib==2.0.0; python_version >= "3.4"
|
|
155
|
+
Requires-Dist: resolvelib==1.0.1
|
|
156
|
+
Requires-Dist: rsa==4.7.2; python_version >= "3.5" and python_version < "4"
|
|
157
|
+
Requires-Dist: s3transfer==0.10.4; python_version >= "3.8"
|
|
158
|
+
Requires-Dist: setuptools==75.8.0; python_version >= "3.9"
|
|
159
|
+
Requires-Dist: simpledi==0.4.1
|
|
160
|
+
Requires-Dist: six==1.17.0; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
|
|
161
|
+
Requires-Dist: smmap==5.0.2; python_version >= "3.7"
|
|
162
|
+
Requires-Dist: typing-extensions==4.12.2; python_version >= "3.8"
|
|
163
|
+
Requires-Dist: urllib3==2.0.7; python_version >= "3.7"
|
|
164
|
+
Requires-Dist: websocket-client==1.8.0; python_version >= "3.8"
|
|
165
|
+
Dynamic: author
|
|
166
|
+
Dynamic: author-email
|
|
167
|
+
Dynamic: classifier
|
|
168
|
+
Dynamic: description
|
|
169
|
+
Dynamic: description-content-type
|
|
170
|
+
Dynamic: home-page
|
|
171
|
+
Dynamic: license
|
|
172
|
+
Dynamic: requires-dist
|
|
173
|
+
Dynamic: requires-python
|
|
174
|
+
Dynamic: summary
|
|
166
175
|
|
|
167
176
|
# Ops CLI
|
|
168
177
|
[](https://github.com/adobe/ops-cli/actions/workflows/release.yml) [](https://github.com/adobe/ops-cli/pkgs/container/ops-cli) [](https://github.com/adobe/ops-cli/blob/master/LICENSE)
|
|
@@ -317,7 +326,7 @@ workon ops
|
|
|
317
326
|
# uninstall previous `ops` version (if you have it)
|
|
318
327
|
pip uninstall ops --yes
|
|
319
328
|
|
|
320
|
-
# install ops-cli v2.
|
|
329
|
+
# install ops-cli v2.3.0 stable release
|
|
321
330
|
pip install --upgrade ops-cli
|
|
322
331
|
```
|
|
323
332
|
|
|
@@ -333,7 +342,7 @@ You can try out `ops-cli`, by using docker. The docker image has all required pr
|
|
|
333
342
|
|
|
334
343
|
To start out a container, running the latest `ops-cli` docker image run:
|
|
335
344
|
```sh
|
|
336
|
-
docker run -it ghcr.io/adobe/ops-cli:2.
|
|
345
|
+
docker run -it ghcr.io/adobe/ops-cli:2.3.0 bash
|
|
337
346
|
```
|
|
338
347
|
|
|
339
348
|
After the container has started, you can start using `ops-cli`:
|
|
@@ -859,6 +868,12 @@ Which will translate behind the scenes in :
|
|
|
859
868
|
This allows us to just refer in cluster files a secret that actually exists in vault and make sure we only generate it once - if it was already created by os or any other system, we will just use what is already there.
|
|
860
869
|
The reference is by means of fixed form jinja call added to the cluster file, which ends up interpreted later during the templating phase.
|
|
861
870
|
|
|
871
|
+
#### Vault auth
|
|
872
|
+
Ops checks if a valid vault token is present in `~/.vault-token` or in the environment variable `VAULT_TOKEN`. If not, it will try to authenticate using the method defined in `OPS_VAULT_AUTH_METHOD` environment variable.
|
|
873
|
+
The following methods are supported:
|
|
874
|
+
- `okta` - use the Vault Okta auth method (default)
|
|
875
|
+
- `ldap` - use the Vault ldap auth method
|
|
876
|
+
|
|
862
877
|
### Amazon Secrets Manager (SSM)
|
|
863
878
|
|
|
864
879
|
Amazon offers the possibility to use their [Secrets Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html) in order to manage configuration data such as credentials, passwords and license keys.
|
|
@@ -4,7 +4,7 @@ ops/main.py,sha256=FvrpZHVawYsxBgJub9PuZP148HzWrYlnXuELNx5mBwI,6988
|
|
|
4
4
|
ops/opsconfig.py,sha256=kIiV2mU5yts644x2VTYUnUSJRVLofGIJMD4xwweaw0o,5847
|
|
5
5
|
ops/simpleconsul.py,sha256=4JWgUmw7RKn_Ti6gguf_bIyJnF7fpVBWkCXwcngbQVI,4089
|
|
6
6
|
ops/simplessm.py,sha256=BwD8P9EKagJmR4BR2aKAXaGLIren9Yljb1GxB8nzYbk,1703
|
|
7
|
-
ops/simplevault.py,sha256=
|
|
7
|
+
ops/simplevault.py,sha256=qVEA0L5UcZEFAVpY3CNQsMiyxHNyx8Fu7kEPiKbs8yE,12697
|
|
8
8
|
ops/ansible/__init__.py,sha256=n5YIjodX0NasK2cpc8kU88jZfLQF8j94F05V5k2vAhI,603
|
|
9
9
|
ops/ansible/callback_plugins/__init__.py,sha256=v9uW7YO2C7EC4MQWr_TMH_K-1ALd0uLl9vZ_h3xCQkg,596
|
|
10
10
|
ops/ansible/filter_plugins/__init__.py,sha256=v9uW7YO2C7EC4MQWr_TMH_K-1ALd0uLl9vZ_h3xCQkg,596
|
|
@@ -17,12 +17,12 @@ ops/cli/aws.py,sha256=OsmpOVv1924_lvNESeElLgcEi1caYTnI88C7wzXie78,911
|
|
|
17
17
|
ops/cli/config.py,sha256=T0ZfW1bWdSr1dF4oqsvTjUtUiCr7RVm4_rFdezgGCqM,7629
|
|
18
18
|
ops/cli/config_generator.py,sha256=ARGp5kAgdMLqtrHyQb_dE9sEuM1bAnVddfWDxd_5OdA,1718
|
|
19
19
|
ops/cli/helmfile.py,sha256=2ipKyFMY5M87U1mXDQle_UsOXICdQ-0VX2f97TAg0I8,6354
|
|
20
|
-
ops/cli/inventory.py,sha256=
|
|
20
|
+
ops/cli/inventory.py,sha256=kSqNw978_P7OrFzLng5ipzuJBh6WqtvjvLDtiNwFQ8A,3109
|
|
21
21
|
ops/cli/packer.py,sha256=GAgi6uPWO0wv3uIEwl0O2sb4fQtpCbL1561nP8Y_Lpc,2626
|
|
22
22
|
ops/cli/parser.py,sha256=tzXvpu4GycyHSjERFrjQVEFZYVzOszLhDEjpkFYJ-oc,4377
|
|
23
23
|
ops/cli/playbook.py,sha256=rPaIfUUMSTgITxzrvkVyTpPJ2UfqYnk3WfjY3jDrHO0,5287
|
|
24
24
|
ops/cli/run.py,sha256=cwT2rQEBRip_OndkSQLFR7Q8D5BD6L5BbAq6nehKhco,3373
|
|
25
|
-
ops/cli/ssh.py,sha256=
|
|
25
|
+
ops/cli/ssh.py,sha256=AGDtNqLnbOjIbVxAnxfEJDpi5m1D0tOO3ml3uQ3bhgQ,14196
|
|
26
26
|
ops/cli/sync.py,sha256=8K3Dsh6cwv9gTat0KXvRemHmqpAWd46pHfS8wMS9Olk,5663
|
|
27
27
|
ops/cli/terraform.py,sha256=XeLXr_nFpdnsHdEfAFj_oiurvIhDQv-xE3L-NpMTcB0,12446
|
|
28
28
|
ops/data/ansible/ansible.cfg,sha256=LE0Ve37qwQnbM1AJfHX442yh6HMMzM1FPCTCUKZElV8,186
|
|
@@ -39,21 +39,21 @@ ops/inventory/SKMS.py,sha256=jv4nHTKbDmJBTNf3HgGyKSfAJ10akKdDxNoGOcPehfo,17616
|
|
|
39
39
|
ops/inventory/__init__.py,sha256=yvYO8z4aCdcOEYIjy27YL7fcbPqZAwQk9y_3YcNVAGU,643
|
|
40
40
|
ops/inventory/azurerm.py,sha256=pWZqbZP9-7ChHNK1DwfSlt9hU6lcJjvNXmvduR2G5tQ,33507
|
|
41
41
|
ops/inventory/caching.py,sha256=G2sJJ7nPHCqNBz3Vh0vxrFWQZt6B_11yS50GeZ8_1Bs,2000
|
|
42
|
-
ops/inventory/ec2inventory.py,sha256=
|
|
42
|
+
ops/inventory/ec2inventory.py,sha256=Zz3pB6hGLkzZa_ZIwpF2DfBXM_IQDIUmlwUIyy5D2HU,10655
|
|
43
43
|
ops/inventory/generator.py,sha256=BcjUxMoWTnmx-P_S-1fLjqG3PUhyMyAO2S-037KG5q0,10885
|
|
44
44
|
ops/inventory/sshconfig.py,sha256=lxqKyH0uCRCk4XXZISGrtX_d0c7OREg2Hh77ndtz05k,4438
|
|
45
45
|
ops/inventory/plugin/__init__.py,sha256=kSOYPdE9T0Ixe6tQUYoOl_udNbFZdVatvRf6bkf92aE,725
|
|
46
46
|
ops/inventory/plugin/azr.py,sha256=8-G3mEc0WQV7ZhJ4z6X-Wo90nQ7zAzHwgLpV4l_gbjo,5910
|
|
47
|
-
ops/inventory/plugin/cns.py,sha256=
|
|
48
|
-
ops/inventory/plugin/ec2.py,sha256=
|
|
47
|
+
ops/inventory/plugin/cns.py,sha256=X7CUMA8ltGxDfCkvaeEHsd60g8Y_C_gJk7287UM-qHw,1763
|
|
48
|
+
ops/inventory/plugin/ec2.py,sha256=M9Z_MJukDUyA1XtvHeiRGuTUh8A3ibHfDINJDUwLo-U,1332
|
|
49
49
|
ops/inventory/plugin/legacy_pcs.py,sha256=ii1kOkwD9PIaOlleJa3_yE95ojCoPlBPKSBu5naZhxw,1184
|
|
50
50
|
ops/inventory/plugin/skms.py,sha256=pFMpvfbXCf0KPgEOj5oAD7UzpUZ5zL1lcN4wESOjPuI,8517
|
|
51
51
|
ops/jinja/__init__.py,sha256=Nvmvb1edSAehNKYyroo26Jrxjzbu_TOCBS8Y9mMEOyA,1743
|
|
52
52
|
ops/terraform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
ops/terraform/terraform_cmd_generator.py,sha256=
|
|
54
|
-
ops_cli-2.
|
|
55
|
-
ops_cli-2.
|
|
56
|
-
ops_cli-2.
|
|
57
|
-
ops_cli-2.
|
|
58
|
-
ops_cli-2.
|
|
59
|
-
ops_cli-2.
|
|
53
|
+
ops/terraform/terraform_cmd_generator.py,sha256=5yfzS7aOOjVDUYHwBadFvPjWKEFJWk9SEOhTZHywlG0,21728
|
|
54
|
+
ops_cli-2.3.0.dist-info/LICENSE,sha256=ff5lJoiLrFF1nJn5pRJiuicRqMEqBn8hgWCd2aQGa4Q,11335
|
|
55
|
+
ops_cli-2.3.0.dist-info/METADATA,sha256=fiCNonTcwfXYBj8vytZW3M66vBrCovowBpgYhv8AAXE,39904
|
|
56
|
+
ops_cli-2.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
57
|
+
ops_cli-2.3.0.dist-info/entry_points.txt,sha256=maaS2Tf8WvxMXckssedK13LXegD9jgHB2AT8xiEfVpQ,37
|
|
58
|
+
ops_cli-2.3.0.dist-info/top_level.txt,sha256=enC05wWafSg8iDKIvj3gvtAtEP2kYCyN5Gmd689q-_I,4
|
|
59
|
+
ops_cli-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|