xpk 0.0.1__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.
- xpk/__init__.py +15 -0
- xpk/api/__init__.py +15 -0
- xpk/api/storage_crd.yaml +52 -0
- xpk/commands/__init__.py +15 -0
- xpk/commands/batch.py +131 -0
- xpk/commands/cluster.py +808 -0
- xpk/commands/cluster_gcluster.py +269 -0
- xpk/commands/common.py +44 -0
- xpk/commands/config.py +29 -0
- xpk/commands/info.py +243 -0
- xpk/commands/inspector.py +357 -0
- xpk/commands/job.py +199 -0
- xpk/commands/kind.py +283 -0
- xpk/commands/kjob_common.py +44 -0
- xpk/commands/run.py +128 -0
- xpk/commands/shell.py +140 -0
- xpk/commands/storage.py +267 -0
- xpk/commands/version.py +27 -0
- xpk/commands/workload.py +889 -0
- xpk/core/__init__.py +15 -0
- xpk/core/blueprint/__init__.py +15 -0
- xpk/core/blueprint/blueprint_definitions.py +62 -0
- xpk/core/blueprint/blueprint_generator.py +708 -0
- xpk/core/capacity.py +185 -0
- xpk/core/cluster.py +564 -0
- xpk/core/cluster_private.py +200 -0
- xpk/core/commands.py +356 -0
- xpk/core/config.py +179 -0
- xpk/core/docker_container.py +225 -0
- xpk/core/docker_image.py +210 -0
- xpk/core/docker_manager.py +308 -0
- xpk/core/docker_resources.py +350 -0
- xpk/core/filestore.py +251 -0
- xpk/core/gcloud_context.py +196 -0
- xpk/core/gcluster_manager.py +176 -0
- xpk/core/gcsfuse.py +50 -0
- xpk/core/kjob.py +444 -0
- xpk/core/kueue.py +358 -0
- xpk/core/monitoring.py +134 -0
- xpk/core/nap.py +361 -0
- xpk/core/network.py +377 -0
- xpk/core/nodepool.py +581 -0
- xpk/core/pathways.py +377 -0
- xpk/core/ray.py +222 -0
- xpk/core/remote_state/__init__.py +15 -0
- xpk/core/remote_state/fuse_remote_state.py +99 -0
- xpk/core/remote_state/remote_state_client.py +38 -0
- xpk/core/resources.py +238 -0
- xpk/core/scheduling.py +253 -0
- xpk/core/storage.py +581 -0
- xpk/core/system_characteristics.py +1432 -0
- xpk/core/vertex.py +105 -0
- xpk/core/workload.py +341 -0
- xpk/core/workload_decorators/__init__.py +15 -0
- xpk/core/workload_decorators/rdma_decorator.py +129 -0
- xpk/core/workload_decorators/storage_decorator.py +52 -0
- xpk/core/workload_decorators/tcpxo_decorator.py +190 -0
- xpk/main.py +75 -0
- xpk/parser/__init__.py +15 -0
- xpk/parser/batch.py +43 -0
- xpk/parser/cluster.py +662 -0
- xpk/parser/common.py +259 -0
- xpk/parser/config.py +49 -0
- xpk/parser/core.py +135 -0
- xpk/parser/info.py +64 -0
- xpk/parser/inspector.py +65 -0
- xpk/parser/job.py +147 -0
- xpk/parser/kind.py +95 -0
- xpk/parser/run.py +47 -0
- xpk/parser/shell.py +59 -0
- xpk/parser/storage.py +316 -0
- xpk/parser/validators.py +39 -0
- xpk/parser/version.py +23 -0
- xpk/parser/workload.py +726 -0
- xpk/templates/__init__.py +15 -0
- xpk/templates/storage.yaml +13 -0
- xpk/utils/__init__.py +15 -0
- xpk/utils/console.py +55 -0
- xpk/utils/file.py +82 -0
- xpk/utils/gcs_utils.py +125 -0
- xpk/utils/kubectl.py +57 -0
- xpk/utils/network.py +168 -0
- xpk/utils/objects.py +88 -0
- xpk/utils/templates.py +28 -0
- xpk/utils/validation.py +80 -0
- xpk/utils/yaml.py +30 -0
- xpk-0.0.1.dist-info/LICENSE +202 -0
- xpk-0.0.1.dist-info/METADATA +1498 -0
- xpk-0.0.1.dist-info/RECORD +92 -0
- xpk-0.0.1.dist-info/WHEEL +5 -0
- xpk-0.0.1.dist-info/entry_points.txt +2 -0
- xpk-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from ..utils.console import xpk_exit, xpk_print
|
|
18
|
+
from ..utils.network import (
|
|
19
|
+
add_current_machine_to_networks,
|
|
20
|
+
is_current_machine_in_any_network,
|
|
21
|
+
)
|
|
22
|
+
from ..utils.objects import is_text_true
|
|
23
|
+
from .commands import run_command_for_value, run_command_with_updates
|
|
24
|
+
from .gcloud_context import zone_to_region
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def authorize_private_cluster_access_if_necessary(args) -> int:
|
|
28
|
+
"""Updates a GKE cluster to add authorize networks to access a private cluster's control plane, if not added already.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
args: user provided arguments for running the command.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
0 if successful and error code otherwise.
|
|
35
|
+
"""
|
|
36
|
+
if not is_cluster_private(args):
|
|
37
|
+
if not args.private and args.authorized_networks is None:
|
|
38
|
+
xpk_print('Cluster is public and no need to authorize networks.')
|
|
39
|
+
return 0
|
|
40
|
+
else:
|
|
41
|
+
xpk_print(
|
|
42
|
+
'Cannot convert an existing public cluster to private. The arguments'
|
|
43
|
+
' --private and --authorized-networks are not acceptable for public'
|
|
44
|
+
' clusters.'
|
|
45
|
+
)
|
|
46
|
+
return 1
|
|
47
|
+
|
|
48
|
+
new_authorized_networks_needed, authorized_networks = (
|
|
49
|
+
check_if_new_authorized_networks_needed(args)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
(
|
|
53
|
+
add_current_machine_to_networks_return_code,
|
|
54
|
+
is_current_machine_in_network,
|
|
55
|
+
authorized_networks,
|
|
56
|
+
) = add_current_machine_to_networks_if_needed(authorized_networks)
|
|
57
|
+
if add_current_machine_to_networks_return_code != 0:
|
|
58
|
+
return add_current_machine_to_networks_return_code
|
|
59
|
+
|
|
60
|
+
if new_authorized_networks_needed or not is_current_machine_in_network:
|
|
61
|
+
return update_cluster_new_authorized_networks(args, authorized_networks)
|
|
62
|
+
|
|
63
|
+
xpk_print("Current machine's IP adrress is already authorized.")
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def update_cluster_new_authorized_networks(args, authorized_networks) -> int:
|
|
68
|
+
cluster_authorized_networks_update_code = update_cluster_authorized_networks(
|
|
69
|
+
args, authorized_networks
|
|
70
|
+
)
|
|
71
|
+
if cluster_authorized_networks_update_code != 0:
|
|
72
|
+
xpk_print('Updating cluster authorized networks failed!')
|
|
73
|
+
return cluster_authorized_networks_update_code
|
|
74
|
+
|
|
75
|
+
xpk_print("Cluster's master authorized networks updated successfully.")
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def add_current_machine_to_networks_if_needed(
|
|
80
|
+
authorized_networks,
|
|
81
|
+
) -> tuple[int, bool, list]:
|
|
82
|
+
is_current_machine_in_network_return_code, is_current_machine_in_network = (
|
|
83
|
+
is_current_machine_in_any_network(authorized_networks)
|
|
84
|
+
)
|
|
85
|
+
if is_current_machine_in_network_return_code != 0:
|
|
86
|
+
xpk_print("Error on checking current machine's IP adrress.")
|
|
87
|
+
return is_current_machine_in_network_return_code, False, authorized_networks
|
|
88
|
+
|
|
89
|
+
if not is_current_machine_in_network:
|
|
90
|
+
add_current_machine_to_networks_return_code, authorized_networks = (
|
|
91
|
+
add_current_machine_to_networks(authorized_networks)
|
|
92
|
+
)
|
|
93
|
+
if add_current_machine_to_networks_return_code != 0:
|
|
94
|
+
xpk_print(
|
|
95
|
+
"Adding current machine's IP address to the authorized networks"
|
|
96
|
+
' failed!'
|
|
97
|
+
)
|
|
98
|
+
return add_current_machine_to_networks_return_code, authorized_networks
|
|
99
|
+
|
|
100
|
+
return 0, is_current_machine_in_network, authorized_networks
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def check_if_new_authorized_networks_needed(args) -> tuple[bool, list]:
|
|
104
|
+
new_authorized_networks_needed = args.authorized_networks is not None
|
|
105
|
+
|
|
106
|
+
authorized_networks = (
|
|
107
|
+
args.authorized_networks
|
|
108
|
+
if new_authorized_networks_needed
|
|
109
|
+
else get_cluster_authorized_networks(args)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return new_authorized_networks_needed, authorized_networks
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_cluster_private(args) -> bool:
|
|
116
|
+
"""Checks if cluster is private.
|
|
117
|
+
Args:
|
|
118
|
+
args: user provided arguments for running the command.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if cluster is private and False otherwise.
|
|
122
|
+
"""
|
|
123
|
+
command = (
|
|
124
|
+
f'gcloud container clusters describe {args.cluster}'
|
|
125
|
+
f' --project={args.project} --region={zone_to_region(args.zone)}'
|
|
126
|
+
' --format="value(privateClusterConfig.enablePrivateNodes)"'
|
|
127
|
+
)
|
|
128
|
+
return_code, private_nodes_enabled = run_command_for_value(
|
|
129
|
+
command,
|
|
130
|
+
'Check if Private Nodes is enabled in cluster.',
|
|
131
|
+
args,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if return_code != 0:
|
|
135
|
+
xpk_print('Checking if Private Nodes is enabled failed!')
|
|
136
|
+
xpk_exit(return_code)
|
|
137
|
+
|
|
138
|
+
if is_text_true(private_nodes_enabled):
|
|
139
|
+
xpk_print('Private Nodes is enabled on the cluster.')
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
xpk_print('Private Nodes is not enabled on the cluster.')
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_cluster_authorized_networks(args) -> list[str]:
|
|
147
|
+
"""Retreives the networks list that are authorized to have access to Control Plane.
|
|
148
|
+
Args:
|
|
149
|
+
args: user provided arguments for running the command.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of networks CIDRs as strings
|
|
153
|
+
"""
|
|
154
|
+
command = (
|
|
155
|
+
f'gcloud container clusters describe {args.cluster}'
|
|
156
|
+
f' --project={args.project} --region={zone_to_region(args.zone)}'
|
|
157
|
+
' --format="value(masterAuthorizedNetworksConfig.cidrBlocks[].cidrBlock)"'
|
|
158
|
+
)
|
|
159
|
+
return_code, authorized_networks = run_command_for_value(
|
|
160
|
+
command,
|
|
161
|
+
'Fetching the list of authorized network from cluster describe.',
|
|
162
|
+
args,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if return_code != 0:
|
|
166
|
+
xpk_print('Fetching authorized networks failed!')
|
|
167
|
+
xpk_exit(return_code)
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
authorized_networks.strip().split(';')
|
|
171
|
+
if authorized_networks.strip() != ''
|
|
172
|
+
else []
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def update_cluster_authorized_networks(args, authorized_networks) -> int:
|
|
177
|
+
"""Run the GKE cluster update command for existing cluster and update master authorized networks list.
|
|
178
|
+
Args:
|
|
179
|
+
args: user provided arguments for running the command.
|
|
180
|
+
authorized_networks: list of networks CIDRs to authorize.
|
|
181
|
+
Returns:
|
|
182
|
+
0 if successful and 1 otherwise.
|
|
183
|
+
"""
|
|
184
|
+
command = (
|
|
185
|
+
'gcloud container clusters update'
|
|
186
|
+
f' {args.cluster} --project={args.project}'
|
|
187
|
+
f' --region={zone_to_region(args.zone)}'
|
|
188
|
+
' --enable-master-authorized-networks'
|
|
189
|
+
f' --master-authorized-networks={",".join(authorized_networks)}'
|
|
190
|
+
' --quiet'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return_code = run_command_with_updates(
|
|
194
|
+
command, 'GKE Cluster Update master authorized networks', args
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if return_code != 0:
|
|
198
|
+
xpk_print(f'GKE Cluster Update request returned ERROR {return_code}')
|
|
199
|
+
return 1
|
|
200
|
+
return 0
|
xpk/core/commands.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from argparse import Namespace
|
|
22
|
+
|
|
23
|
+
from ..utils.objects import chunks
|
|
24
|
+
from ..utils.file import make_tmp_files, write_tmp_file
|
|
25
|
+
from ..utils.console import xpk_print
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_commands(commands, jobname, per_command_name, batch=10, dry_run=False):
|
|
29
|
+
"""Run commands in groups of `batch`.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
commands: list of command.
|
|
33
|
+
jobname: the name of the job.
|
|
34
|
+
per_command_name: list of command names.
|
|
35
|
+
batch: number of commands to run in parallel.
|
|
36
|
+
dry_run: enables dry_run if set to true.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
0 if successful and 1 otherwise.
|
|
40
|
+
"""
|
|
41
|
+
temporary_files_batches = chunks(make_tmp_files(per_command_name), batch)
|
|
42
|
+
commands_batched = chunks(commands, batch)
|
|
43
|
+
per_command_name_batches = chunks(per_command_name, batch)
|
|
44
|
+
|
|
45
|
+
xpk_print(
|
|
46
|
+
f'Breaking up a total of {len(commands)} commands into'
|
|
47
|
+
f' {len(commands_batched)} batches'
|
|
48
|
+
)
|
|
49
|
+
if dry_run:
|
|
50
|
+
xpk_print('Pretending all the jobs succeeded')
|
|
51
|
+
return 0
|
|
52
|
+
|
|
53
|
+
max_return_code = 0
|
|
54
|
+
for i, _ in enumerate(commands_batched):
|
|
55
|
+
xpk_print(f'Dispatching batch {i}/{len(commands_batched)}')
|
|
56
|
+
batch_max_return_code, _ = run_command_batch(
|
|
57
|
+
commands_batched[i],
|
|
58
|
+
jobname,
|
|
59
|
+
per_command_name_batches[i],
|
|
60
|
+
temporary_files_batches[i],
|
|
61
|
+
)
|
|
62
|
+
max_return_code = max(max_return_code, batch_max_return_code)
|
|
63
|
+
if max_return_code > 0:
|
|
64
|
+
return max_return_code
|
|
65
|
+
return max_return_code
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_command_batch(commands, jobname, per_command_name, output_logs):
|
|
69
|
+
"""Runs commands in parallel.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
commands: list of n commands, each command is a a list of strings
|
|
73
|
+
jobname: Useful debugging name for the group of commands
|
|
74
|
+
per_command_name: specific name per task
|
|
75
|
+
output_logs: list of n log paths, each command will output to each log.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The max return code and a list of all the return codes.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
children = []
|
|
82
|
+
start_time = datetime.datetime.now()
|
|
83
|
+
for i, command in enumerate(commands):
|
|
84
|
+
children.append(
|
|
85
|
+
# subprocess managed by list pylint: disable=consider-using-with
|
|
86
|
+
subprocess.Popen(
|
|
87
|
+
command, stdout=output_logs[i], stderr=output_logs[i], shell=True
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
while True:
|
|
92
|
+
returncodes = [child.poll() for child in children]
|
|
93
|
+
max_returncode = max([0] + [r for r in returncodes if r is not None])
|
|
94
|
+
completed = len([r for r in returncodes if r is not None])
|
|
95
|
+
total = len(returncodes)
|
|
96
|
+
seconds_elapsed = (datetime.datetime.now() - start_time).total_seconds()
|
|
97
|
+
if completed < total:
|
|
98
|
+
slow_worker_index = returncodes.index(None)
|
|
99
|
+
slow_worker_text = per_command_name[slow_worker_index]
|
|
100
|
+
slow_str = (
|
|
101
|
+
f', task {slow_worker_text} still working, logfile'
|
|
102
|
+
f' {output_logs[slow_worker_index].name}'
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
slow_str = ''
|
|
106
|
+
xpk_print(
|
|
107
|
+
f'[t={seconds_elapsed:.2f}, {jobname}] Completed'
|
|
108
|
+
f' {completed}/{total}{slow_str}'
|
|
109
|
+
)
|
|
110
|
+
if max_returncode > 0:
|
|
111
|
+
failing_index = [
|
|
112
|
+
i for i, x in enumerate(returncodes) if x is not None and x > 0
|
|
113
|
+
][0]
|
|
114
|
+
xpk_print(
|
|
115
|
+
f'Terminating all {jobname} processes since at least one failed.'
|
|
116
|
+
)
|
|
117
|
+
xpk_print(
|
|
118
|
+
f'Failure is {per_command_name[failing_index]}'
|
|
119
|
+
f' and logfile {output_logs[failing_index].name}'
|
|
120
|
+
)
|
|
121
|
+
for child in children:
|
|
122
|
+
child.terminate()
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if completed == total:
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
time.sleep(1)
|
|
129
|
+
return max_returncode, returncodes
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def run_command_with_updates_retry(
|
|
133
|
+
command, task, args, verbose=True, num_retry_attempts=5, wait_seconds=10
|
|
134
|
+
) -> int:
|
|
135
|
+
"""Generic run commands function with updates and retry logic.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
command: command to execute
|
|
139
|
+
task: user-facing name of the task
|
|
140
|
+
args: user provided arguments for running the command.
|
|
141
|
+
verbose: shows stdout and stderr if set to true. Set to True by default.
|
|
142
|
+
num_retry_attempts: number of attempts to retry the command.
|
|
143
|
+
This has a default value in the function arguments.
|
|
144
|
+
wait_seconds: Seconds to wait between attempts.
|
|
145
|
+
Has a default value in the function arguments.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
0 if successful and 1 otherwise.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
i = 0
|
|
152
|
+
return_code = -1
|
|
153
|
+
while return_code != 0 and i < num_retry_attempts:
|
|
154
|
+
# Do not sleep before first try.
|
|
155
|
+
if i != 0:
|
|
156
|
+
xpk_print(f'Wait {wait_seconds} seconds before retrying.')
|
|
157
|
+
time.sleep(wait_seconds)
|
|
158
|
+
i += 1
|
|
159
|
+
xpk_print(f'Try {i}: {task}')
|
|
160
|
+
return_code = run_command_with_updates(command, task, args, verbose=verbose)
|
|
161
|
+
return return_code
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def run_command_with_updates(command, task, global_args, verbose=True) -> int:
|
|
165
|
+
"""Generic run commands function with updates.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
command: command to execute
|
|
169
|
+
task: user-facing name of the task
|
|
170
|
+
global_args: user provided arguments for running the command.
|
|
171
|
+
verbose: shows stdout and stderr if set to true. Set to True by default.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
0 if successful and 1 otherwise.
|
|
175
|
+
"""
|
|
176
|
+
if global_args.dry_run:
|
|
177
|
+
xpk_print(
|
|
178
|
+
f'Task: `{task}` is implemented by the following command'
|
|
179
|
+
' not running since it is a dry run.'
|
|
180
|
+
f' \n{command}'
|
|
181
|
+
)
|
|
182
|
+
return 0
|
|
183
|
+
if verbose:
|
|
184
|
+
xpk_print(
|
|
185
|
+
f'Task: `{task}` is implemented by `{command}`, streaming output live.'
|
|
186
|
+
)
|
|
187
|
+
with subprocess.Popen(
|
|
188
|
+
command,
|
|
189
|
+
stdout=sys.stdout,
|
|
190
|
+
stderr=sys.stderr,
|
|
191
|
+
shell=True,
|
|
192
|
+
) as child:
|
|
193
|
+
i = 0
|
|
194
|
+
while True:
|
|
195
|
+
return_code = child.poll()
|
|
196
|
+
if return_code is None:
|
|
197
|
+
xpk_print(f'Waiting for `{task}`, for {i} seconds...', end='\r')
|
|
198
|
+
time.sleep(1)
|
|
199
|
+
i += 1
|
|
200
|
+
else:
|
|
201
|
+
xpk_print(f'Task: `{task}` terminated with code `{return_code}`')
|
|
202
|
+
return return_code
|
|
203
|
+
else:
|
|
204
|
+
xpk_print(
|
|
205
|
+
f'Task: `{task}` is implemented by `{command}`, hiding output unless'
|
|
206
|
+
' there is an error.'
|
|
207
|
+
)
|
|
208
|
+
try:
|
|
209
|
+
subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
|
|
210
|
+
except subprocess.CalledProcessError as e:
|
|
211
|
+
xpk_print(
|
|
212
|
+
f'Task: `{task}` terminated with ERROR `{e.returncode}`, printing'
|
|
213
|
+
' logs'
|
|
214
|
+
)
|
|
215
|
+
xpk_print('*' * 80)
|
|
216
|
+
xpk_print(e.output)
|
|
217
|
+
xpk_print('*' * 80)
|
|
218
|
+
return e.returncode
|
|
219
|
+
xpk_print(f'Task: `{task}` succeeded.')
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def run_command_for_value(
|
|
224
|
+
command,
|
|
225
|
+
task,
|
|
226
|
+
global_args,
|
|
227
|
+
dry_run_return_val='0',
|
|
228
|
+
print_timer=False,
|
|
229
|
+
hide_error=False,
|
|
230
|
+
quiet=False,
|
|
231
|
+
) -> tuple[int, str]:
|
|
232
|
+
"""Runs the command and returns the error code and stdout.
|
|
233
|
+
|
|
234
|
+
Prints errors and associated user-facing information
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
command: user provided command to run.
|
|
238
|
+
task: user provided task name for running the command.
|
|
239
|
+
global_args: user provided arguments for running the command.
|
|
240
|
+
dry_run_return_val: return value of this command for dry run.
|
|
241
|
+
print_timer: print out the time the command is running.
|
|
242
|
+
hide_error: hide the error from the command output upon success.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
tuple[int, str]
|
|
246
|
+
int: return_code, default is 0
|
|
247
|
+
str: return_val, default is '0'
|
|
248
|
+
"""
|
|
249
|
+
if global_args is not None and global_args.dry_run:
|
|
250
|
+
xpk_print(
|
|
251
|
+
f'Task: `{task}` is implemented by the following command'
|
|
252
|
+
' not running since it is a dry run.'
|
|
253
|
+
f' \n{command}'
|
|
254
|
+
)
|
|
255
|
+
return 0, dry_run_return_val
|
|
256
|
+
|
|
257
|
+
if print_timer:
|
|
258
|
+
if not quiet:
|
|
259
|
+
xpk_print(f'Task: `{task}` is implemented by `{command}`')
|
|
260
|
+
with subprocess.Popen(
|
|
261
|
+
command,
|
|
262
|
+
stdout=subprocess.PIPE,
|
|
263
|
+
stderr=subprocess.PIPE,
|
|
264
|
+
shell=True,
|
|
265
|
+
) as child:
|
|
266
|
+
i = 0
|
|
267
|
+
while True:
|
|
268
|
+
return_code = child.poll()
|
|
269
|
+
if return_code is None:
|
|
270
|
+
if not quiet:
|
|
271
|
+
xpk_print(f'Waiting for `{task}`, for {i} seconds...', end='\r')
|
|
272
|
+
time.sleep(1)
|
|
273
|
+
i += 1
|
|
274
|
+
else:
|
|
275
|
+
if not quiet:
|
|
276
|
+
xpk_print(f'Task: `{task}` terminated with code `{return_code}`')
|
|
277
|
+
out, err = child.communicate()
|
|
278
|
+
out, err = str(out, 'UTF-8'), str(err, 'UTF-8')
|
|
279
|
+
return return_code, f'{out}\n{err}'
|
|
280
|
+
else:
|
|
281
|
+
if not quiet:
|
|
282
|
+
xpk_print(
|
|
283
|
+
f'Task: `{task}` is implemented by `{command}`, hiding output unless'
|
|
284
|
+
' there is an error.'
|
|
285
|
+
)
|
|
286
|
+
try:
|
|
287
|
+
output = subprocess.check_output(
|
|
288
|
+
command,
|
|
289
|
+
shell=True,
|
|
290
|
+
stderr=subprocess.STDOUT if not hide_error else None,
|
|
291
|
+
)
|
|
292
|
+
except subprocess.CalledProcessError as e:
|
|
293
|
+
if not quiet:
|
|
294
|
+
xpk_print(f'Task {task} failed with {e.returncode}')
|
|
295
|
+
xpk_print('*' * 80)
|
|
296
|
+
xpk_print(e.output)
|
|
297
|
+
xpk_print('*' * 80)
|
|
298
|
+
return e.returncode, str(e.output, 'UTF-8')
|
|
299
|
+
return 0, str(output, 'UTF-8')
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def run_command_with_full_controls(
|
|
303
|
+
command: str,
|
|
304
|
+
task: str,
|
|
305
|
+
global_args: Namespace,
|
|
306
|
+
instructions: str | None = None,
|
|
307
|
+
) -> int:
|
|
308
|
+
"""Run command in current shell with system out, in and error handles. Wait
|
|
309
|
+
until it exits.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
command: command to execute
|
|
313
|
+
task: user-facing name of the task
|
|
314
|
+
global_args: user provided arguments for running the command.
|
|
315
|
+
verbose: shows stdout and stderr if set to true. Set to True by default.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
0 if successful and 1 otherwise.
|
|
319
|
+
"""
|
|
320
|
+
if global_args.dry_run:
|
|
321
|
+
xpk_print(
|
|
322
|
+
f'Task: `{task}` is implemented by the following command'
|
|
323
|
+
' not running since it is a dry run.'
|
|
324
|
+
f' \n{command}'
|
|
325
|
+
)
|
|
326
|
+
return 0
|
|
327
|
+
|
|
328
|
+
xpk_print(
|
|
329
|
+
f'Task: `{task}` is implemented by `{command}`. '
|
|
330
|
+
'Streaming output and input live.'
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if instructions is not None:
|
|
334
|
+
xpk_print(instructions)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
with subprocess.Popen(
|
|
338
|
+
command,
|
|
339
|
+
stdout=sys.stdout,
|
|
340
|
+
stderr=sys.stderr,
|
|
341
|
+
stdin=sys.stdin,
|
|
342
|
+
shell=True,
|
|
343
|
+
) as child:
|
|
344
|
+
return_code = child.wait()
|
|
345
|
+
xpk_print(f'Task: `{task}` terminated with code `{return_code}`')
|
|
346
|
+
except KeyboardInterrupt:
|
|
347
|
+
return_code = 0
|
|
348
|
+
|
|
349
|
+
return return_code
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def run_kubectl_apply(yml_string: str, task: str, args: Namespace) -> int:
|
|
353
|
+
tmp = write_tmp_file(yml_string)
|
|
354
|
+
command = f'kubectl apply -f {str(tmp.file.name)}'
|
|
355
|
+
err_code = run_command_with_updates(command, task, args)
|
|
356
|
+
return err_code
|