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 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('ec2_id', '')
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:
@@ -11,26 +11,24 @@
11
11
  import json
12
12
  import re
13
13
  import sys
14
- import os
15
14
 
16
- import boto
17
- from boto import ec2
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
- def _empty_inventory(self):
20
+ @staticmethod
21
+ def _empty_inventory():
25
22
  return {"_meta": {"hostvars": {}}}
26
23
 
27
- def __init__(self, boto_profile, regions, filters={}, bastion_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, conn):
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.values():
80
+ if not self.bastion_filters:
64
81
  return
65
82
 
66
- self.bastion_filters['instance-state-name'] = 'running'
83
+ self.bastion_filters.append({'Name': 'instance-state-name', 'Values': ['running']})
67
84
 
68
- for reservation in conn.get_all_instances(
69
- filters=self.bastion_filters):
70
- for instance in reservation.instances:
71
- return instance.ip_address
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
- try:
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
- reservations = conn.get_all_instances(filters=self.filters)
103
-
104
- bastion_ip = self.find_bastion_box(conn)
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
- except boto.provider.ProfileNotFoundError as e:
119
- raise Exception(
120
- "{}, configure it with 'aws configure --profile {}'".format(e.message, self.boto_profile))
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
- except boto.exception.BotoServerError as e:
123
- sys.exit(e)
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
- conn = ec2.connect_to_region(region)
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 conn is None:
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 = conn.get_all_instances([instance_id])
129
+ reservations = ec2_client.describe_instances(InstanceIds=[instance_id])['Reservations']
138
130
  for reservation in reservations:
139
- for instance in reservation.instances:
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: boto.ec2.instance.Instance
136
+ :type instance: dict
145
137
  """
146
138
 
147
139
  # Only want running instances unless all_instances is True
148
- if instance.state != 'running':
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.tags.get('Name', instance.ip_address)
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.ip_address:
157
- ansible_ssh_host = bastion_ip + "--" + instance.private_ip_address
158
- elif instance.ip_address:
159
- ansible_ssh_host = instance.ip_address
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.private_ip_address
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.id.replace("i-", "")
158
+ dest = dest + "-" + instance['InstanceId'].replace("i-", "")
167
159
 
168
- self.index[dest] = [region, instance.id]
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.tags.values():
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.placement, dest)
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 vars(instance):
198
- value = getattr(instance, key)
199
- key = self.to_safe('ec2_' + key)
200
-
201
- # Handle complex types
202
- # state/previous_state changed to properties in boto in
203
- # https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518
204
- if key == 'ec2__state':
205
- instance_vars['ec2_state'] = instance.state or ''
206
- instance_vars['ec2_state_code'] = instance.state_code
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[key] = ''
216
- elif key == 'ec2_region':
217
- instance_vars[key] = value.name
218
- elif key == 'ec2__placement':
219
- instance_vars['ec2_placement'] = value.zone
220
- elif key == 'ec2_tags':
221
- for k, v in iteritems(value):
222
- key = self.to_safe('ec2_tag_' + k)
223
- instance_vars[key] = v
224
- elif key == 'ec2_groups':
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
- group_names)
233
- # add non ec2 prefix private ip address that are being used in cross provider command
234
- # e.g ssh, sync
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):
@@ -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))
@@ -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'] = args.get('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['tag:cluster'] = args.get('cluster')
23
- bastion_filters['tag:role'] = 'bastion'
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 LDAP username (%s) can be overwritten with VAULT_USER"
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
- try:
96
- self.ldap_attempts += 1
97
- ldap_password = getpass.getpass(
98
- prompt='LDAP password for %s for server %s: ' %
99
- (self.vault_user, self.vault_addr))
100
- auth_response = self.vault_conn.auth.ldap.login(
101
- username=self.vault_user, password=ldap_password)
102
- self.vault_conn.is_authenticated()
103
- self.vault_token = auth_response['auth']['client_token']
104
- write_token(self.vault_token)
105
- except Exception as e:
106
- if self.ldap_attempts >= MAX_LDAP_ATTEMPTS:
107
- display(
108
- "FAILED authentication {} times".format(
109
- self.ldap_attempts), color='red')
110
- raise e
111
- else:
112
- pass
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, (basestring, int, float, bool)):
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
- current_terraform_version.split('.')[1])
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 >= 9:
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
1
+ Metadata-Version: 2.2
2
2
  Name: ops-cli
3
- Version: 2.2.1
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 ==1.2.7
27
- Requires-Dist: azure ==4.0.0
28
- Requires-Dist: azure-applicationinsights ==0.1.1
29
- Requires-Dist: azure-batch ==4.1.3
30
- Requires-Dist: azure-common ==1.1.28
31
- Requires-Dist: azure-cosmosdb-nspkg ==2.0.2
32
- Requires-Dist: azure-cosmosdb-table ==1.0.6
33
- Requires-Dist: azure-datalake-store ==0.0.53
34
- Requires-Dist: azure-eventgrid ==1.3.0
35
- Requires-Dist: azure-graphrbac ==0.40.0
36
- Requires-Dist: azure-keyvault ==1.1.0
37
- Requires-Dist: azure-loganalytics ==0.1.1
38
- Requires-Dist: azure-mgmt ==4.0.0
39
- Requires-Dist: azure-mgmt-advisor ==1.0.1
40
- Requires-Dist: azure-mgmt-applicationinsights ==0.1.1
41
- Requires-Dist: azure-mgmt-authorization ==0.50.0
42
- Requires-Dist: azure-mgmt-batch ==5.0.1
43
- Requires-Dist: azure-mgmt-batchai ==2.0.0
44
- Requires-Dist: azure-mgmt-billing ==0.2.0
45
- Requires-Dist: azure-mgmt-cdn ==3.1.0
46
- Requires-Dist: azure-mgmt-cognitiveservices ==3.0.0
47
- Requires-Dist: azure-mgmt-commerce ==1.0.1
48
- Requires-Dist: azure-mgmt-compute ==4.6.2
49
- Requires-Dist: azure-mgmt-consumption ==2.0.0
50
- Requires-Dist: azure-mgmt-containerinstance ==1.5.0
51
- Requires-Dist: azure-mgmt-containerregistry ==2.8.0
52
- Requires-Dist: azure-mgmt-containerservice ==4.4.0
53
- Requires-Dist: azure-mgmt-cosmosdb ==0.4.1
54
- Requires-Dist: azure-mgmt-datafactory ==0.6.0
55
- Requires-Dist: azure-mgmt-datalake-analytics ==0.6.0
56
- Requires-Dist: azure-mgmt-datalake-nspkg ==3.0.1
57
- Requires-Dist: azure-mgmt-datalake-store ==0.5.0
58
- Requires-Dist: azure-mgmt-datamigration ==1.0.0
59
- Requires-Dist: azure-mgmt-devspaces ==0.1.0
60
- Requires-Dist: azure-mgmt-devtestlabs ==2.2.0
61
- Requires-Dist: azure-mgmt-dns ==2.1.0
62
- Requires-Dist: azure-mgmt-eventgrid ==1.0.0
63
- Requires-Dist: azure-mgmt-eventhub ==2.6.0
64
- Requires-Dist: azure-mgmt-hanaonazure ==0.1.1
65
- Requires-Dist: azure-mgmt-iotcentral ==0.1.0
66
- Requires-Dist: azure-mgmt-iothub ==0.5.0
67
- Requires-Dist: azure-mgmt-iothubprovisioningservices ==0.2.0
68
- Requires-Dist: azure-mgmt-keyvault ==1.1.0
69
- Requires-Dist: azure-mgmt-loganalytics ==0.2.0
70
- Requires-Dist: azure-mgmt-logic ==3.0.0
71
- Requires-Dist: azure-mgmt-machinelearningcompute ==0.4.1
72
- Requires-Dist: azure-mgmt-managementgroups ==0.1.0
73
- Requires-Dist: azure-mgmt-managementpartner ==0.1.1
74
- Requires-Dist: azure-mgmt-maps ==0.1.0
75
- Requires-Dist: azure-mgmt-marketplaceordering ==0.1.0
76
- Requires-Dist: azure-mgmt-media ==1.0.1
77
- Requires-Dist: azure-mgmt-monitor ==0.5.2
78
- Requires-Dist: azure-mgmt-msi ==0.2.0
79
- Requires-Dist: azure-mgmt-network ==2.7.0
80
- Requires-Dist: azure-mgmt-notificationhubs ==2.1.0
81
- Requires-Dist: azure-mgmt-nspkg ==3.0.2
82
- Requires-Dist: azure-mgmt-policyinsights ==0.1.0
83
- Requires-Dist: azure-mgmt-powerbiembedded ==2.0.0
84
- Requires-Dist: azure-mgmt-rdbms ==1.9.0
85
- Requires-Dist: azure-mgmt-recoveryservices ==0.3.0
86
- Requires-Dist: azure-mgmt-recoveryservicesbackup ==0.3.0
87
- Requires-Dist: azure-mgmt-redis ==5.0.0
88
- Requires-Dist: azure-mgmt-relay ==0.1.0
89
- Requires-Dist: azure-mgmt-reservations ==0.2.1
90
- Requires-Dist: azure-mgmt-resource ==2.2.0
91
- Requires-Dist: azure-mgmt-scheduler ==2.0.0
92
- Requires-Dist: azure-mgmt-search ==2.1.0
93
- Requires-Dist: azure-mgmt-servicebus ==0.5.3
94
- Requires-Dist: azure-mgmt-servicefabric ==0.2.0
95
- Requires-Dist: azure-mgmt-signalr ==0.1.1
96
- Requires-Dist: azure-mgmt-sql ==0.9.1
97
- Requires-Dist: azure-mgmt-storage ==2.0.0
98
- Requires-Dist: azure-mgmt-subscription ==0.2.0
99
- Requires-Dist: azure-mgmt-trafficmanager ==0.50.0
100
- Requires-Dist: azure-mgmt-web ==0.35.0
101
- Requires-Dist: azure-nspkg ==3.0.2
102
- Requires-Dist: azure-servicebus ==0.21.1
103
- Requires-Dist: azure-servicefabric ==6.3.0.0
104
- Requires-Dist: azure-servicemanagement-legacy ==0.20.7
105
- Requires-Dist: azure-storage-blob ==1.5.0
106
- Requires-Dist: azure-storage-common ==1.4.2
107
- Requires-Dist: azure-storage-file ==1.4.0
108
- Requires-Dist: azure-storage-queue ==1.4.0
109
- Requires-Dist: boto ==2.49.0
110
- Requires-Dist: deepmerge ==1.1.0
111
- Requires-Dist: hashmerge ==0.2
112
- Requires-Dist: isodate ==0.6.1
113
- Requires-Dist: lru-cache ==0.2.3
114
- Requires-Dist: msrestazure ==0.6.4
115
- Requires-Dist: passgen ==1.1.1
116
- Requires-Dist: pathlib2 ==2.3.7.post1
117
- Requires-Dist: pycparser ==2.21
118
- Requires-Dist: pyhcl ==0.4.5
119
- Requires-Dist: python-consul ==1.1.0
120
- Requires-Dist: resolvelib ==1.0.1
121
- Requires-Dist: simpledi ==0.4.1
122
- Requires-Dist: hvac ==1.1.1 ; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
123
- Requires-Dist: charset-normalizer ==3.3.0 ; python_full_version >= "3.7.0"
124
- Requires-Dist: backports.functools-lru-cache ==1.6.6 ; python_version >= "2.6"
125
- Requires-Dist: msal ==1.24.1 ; python_version >= "2.7"
126
- Requires-Dist: python-dateutil ==2.8.2 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
127
- Requires-Dist: six ==1.16.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
128
- Requires-Dist: himl ==0.15.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
129
- Requires-Dist: requests-oauthlib ==1.3.1 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
130
- 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"
131
- Requires-Dist: docutils ==0.16 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4"
132
- Requires-Dist: pyasn1 ==0.5.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4, 3.5"
133
- Requires-Dist: pyasn1-modules ==0.3.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4, 3.5"
134
- Requires-Dist: urllib3 ==1.26.17 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4, 3.5"
135
- Requires-Dist: idna ==3.4 ; python_version >= "3.5"
136
- Requires-Dist: inflection ==0.5.1 ; python_version >= "3.5"
137
- Requires-Dist: rsa ==4.7.2 ; python_version >= "3.5" and python_version < "4"
138
- Requires-Dist: certifi ==2023.7.22 ; python_version >= "3.6"
139
- Requires-Dist: kubernetes ==26.1.0 ; python_version >= "3.6"
140
- Requires-Dist: msrest ==0.7.1 ; python_version >= "3.6"
141
- Requires-Dist: oauthlib ==3.2.2 ; python_version >= "3.6"
142
- Requires-Dist: pyyaml ==6.0.1 ; python_version >= "3.6"
143
- Requires-Dist: awscli ==1.29.12 ; python_version >= "3.7"
144
- Requires-Dist: azure-core ==1.29.4 ; python_version >= "3.7"
145
- Requires-Dist: boto3 ==1.28.12 ; python_version >= "3.7"
146
- Requires-Dist: botocore ==1.31.12 ; python_version >= "3.7"
147
- Requires-Dist: cachetools ==5.3.1 ; python_version >= "3.7"
148
- Requires-Dist: cryptography ==41.0.4 ; python_version >= "3.7"
149
- Requires-Dist: gitdb ==4.0.10 ; python_version >= "3.7"
150
- Requires-Dist: gitpython ==3.1.37 ; python_version >= "3.7"
151
- Requires-Dist: google-auth ==2.23.3 ; python_version >= "3.7"
152
- Requires-Dist: jinja2 ==3.1.2 ; python_version >= "3.7"
153
- Requires-Dist: jmespath ==1.0.1 ; python_version >= "3.7"
154
- Requires-Dist: markupsafe ==2.1.3 ; python_version >= "3.7"
155
- Requires-Dist: packaging ==23.2 ; python_version >= "3.7"
156
- Requires-Dist: pyjwt ==2.8.0 ; python_version >= "3.7"
157
- Requires-Dist: requests ==2.31.0 ; python_version >= "3.7"
158
- Requires-Dist: s3transfer ==0.6.2 ; python_version >= "3.7"
159
- Requires-Dist: smmap ==5.0.1 ; python_version >= "3.7"
160
- Requires-Dist: cffi ==1.16.0 ; python_version >= "3.8"
161
- Requires-Dist: setuptools ==68.2.2 ; python_version >= "3.8"
162
- Requires-Dist: typing-extensions ==4.8.0 ; python_version >= "3.8"
163
- Requires-Dist: websocket-client ==1.6.4 ; python_version >= "3.8"
164
- Requires-Dist: ansible ==8.2.0 ; python_version >= "3.9"
165
- Requires-Dist: ansible-core ==2.15.5 ; python_version >= "3.9"
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
  [![Build status](https://github.com/adobe/ops-cli/actions/workflows/release.yml/badge.svg)](https://github.com/adobe/ops-cli/actions/workflows/release.yml) [![Docker image](https://img.shields.io/badge/Docker-ghcr.io/adobe/opscli-brightgreen.svg?style=flat-square)](https://github.com/adobe/ops-cli/pkgs/container/ops-cli) [![License](https://img.shields.io/github/license/adobe/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.2.1 stable release
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.2.1 bash
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=ojd_5rbAYNQS-sIva3zrFFabJPzw8RCHso1BTnihtik,10123
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=VsGXuWg9_yVbuWVnZadNyvzCvaciKu68qND59ct8kqc,3101
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=rlVmwyVyv6RLkJ_cM27UMcnDkFS9tplwwjCTvS9Wuek,13188
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=Kt1LxenzjuaDQvN5Q4r-2sg6Mdj5jn7lIf32Cy6BGtE,11179
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=KjqG2SUiglDYj-XvguVIvo1fzqjgg_XPOdLdTaRfm24,1697
48
- ops/inventory/plugin/ec2.py,sha256=rU2DdJ0dduh711dF67Rz5721sQZFnuz98-HvucfCP0M,1253
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=PCeAbsj7KQ6XY-7Q1g0ngyFjIdbEcgk6ZU9XJU3ZRjc,21428
54
- ops_cli-2.2.1.dist-info/LICENSE,sha256=ff5lJoiLrFF1nJn5pRJiuicRqMEqBn8hgWCd2aQGa4Q,11335
55
- ops_cli-2.2.1.dist-info/METADATA,sha256=2vblK8lTKd7aTU_rVDfyo5Ri1xLkiDjvGGTFmfuJhoM,39647
56
- ops_cli-2.2.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
57
- ops_cli-2.2.1.dist-info/entry_points.txt,sha256=maaS2Tf8WvxMXckssedK13LXegD9jgHB2AT8xiEfVpQ,37
58
- ops_cli-2.2.1.dist-info/top_level.txt,sha256=enC05wWafSg8iDKIvj3gvtAtEP2kYCyN5Gmd689q-_I,4
59
- ops_cli-2.2.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5