metaflow 2.14.3__py2.py3-none-any.whl → 2.15.0__py2.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.
@@ -0,0 +1,16 @@
1
+ import sys
2
+ import subprocess
3
+ from pathlib import Path
4
+ import sysconfig
5
+
6
+
7
+ def main():
8
+ share_dir = Path(sysconfig.get_paths()["data"]) / "share" / "metaflow" / "devtools"
9
+ makefile_path = share_dir / "Makefile"
10
+ cmd = ["make", "-f", str(makefile_path)] + sys.argv[1:]
11
+ # subprocess.run(cmd, check=True)
12
+ try:
13
+ completed = subprocess.run(cmd, check=True)
14
+ sys.exit(completed.returncode)
15
+ except subprocess.CalledProcessError as ex:
16
+ sys.exit(ex.returncode)
@@ -211,7 +211,7 @@ def step(
211
211
  log_driver=None,
212
212
  log_options=None,
213
213
  num_parallel=None,
214
- **kwargs
214
+ **kwargs,
215
215
  ):
216
216
  def echo(msg, stream="stderr", batch_id=None, **kwargs):
217
217
  msg = util.to_unicode(msg)
@@ -273,11 +273,11 @@ def step(
273
273
  "metaflow_version"
274
274
  ]
275
275
 
276
+ env = {"METAFLOW_FLOW_FILENAME": os.path.basename(sys.argv[0])}
277
+
276
278
  env_deco = [deco for deco in node.decorators if deco.name == "environment"]
277
279
  if env_deco:
278
- env = env_deco[0].attributes["vars"]
279
- else:
280
- env = {}
280
+ env.update(env_deco[0].attributes["vars"])
281
281
 
282
282
  # Add the environment variables related to the input-paths argument
283
283
  if split_vars:
@@ -298,6 +298,13 @@ class BatchDecorator(StepDecorator):
298
298
  self._save_logs_sidecar = Sidecar("save_logs_periodically")
299
299
  self._save_logs_sidecar.start()
300
300
 
301
+ # Start spot termination monitor sidecar.
302
+ current._update_env(
303
+ {"spot_termination_notice": "/tmp/spot_termination_notice"}
304
+ )
305
+ self._spot_monitor_sidecar = Sidecar("spot_termination_monitor")
306
+ self._spot_monitor_sidecar.start()
307
+
301
308
  num_parallel = int(os.environ.get("AWS_BATCH_JOB_NUM_NODES", 0))
302
309
  if num_parallel >= 1 and ubf_context == UBF_CONTROL:
303
310
  # UBF handling for multinode case
@@ -350,6 +357,7 @@ class BatchDecorator(StepDecorator):
350
357
 
351
358
  try:
352
359
  self._save_logs_sidecar.terminate()
360
+ self._spot_monitor_sidecar.terminate()
353
361
  except:
354
362
  # Best effort kill
355
363
  pass
@@ -19,7 +19,7 @@ import warnings
19
19
 
20
20
  from . import MAGIC_FILE, _datastore_packageroot
21
21
 
22
- FAST_INIT_BIN_URL = "https://fast-flow-init.outerbounds.sh/{platform}/fast-env-0.1.1.gz"
22
+ FAST_INIT_BIN_URL = "https://fast-flow-init.outerbounds.sh/{platform}/latest"
23
23
 
24
24
  # Bootstraps a valid conda virtual environment composed of conda and pypi packages
25
25
 
@@ -78,13 +78,19 @@ class SubprocessManager(object):
78
78
  self.commands: Dict[int, CommandManager] = {}
79
79
 
80
80
  try:
81
- loop = asyncio.get_running_loop()
82
- loop.add_signal_handler(
83
- signal.SIGINT,
84
- lambda: asyncio.create_task(self._async_handle_sigint()),
81
+ try:
82
+ loop = asyncio.get_running_loop()
83
+ loop.add_signal_handler(
84
+ signal.SIGINT,
85
+ lambda: asyncio.create_task(self._async_handle_sigint()),
86
+ )
87
+ except RuntimeError:
88
+ signal.signal(signal.SIGINT, self._handle_sigint)
89
+ except ValueError:
90
+ sys.stderr.write(
91
+ "Warning: Unable to set signal handlers in non-main thread. "
92
+ "Interrupt handling will be limited.\n"
85
93
  )
86
- except RuntimeError:
87
- signal.signal(signal.SIGINT, self._handle_sigint)
88
94
 
89
95
  async def _async_handle_sigint(self):
90
96
  pids = [
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.14.3"
1
+ metaflow_version = "2.15.0"
@@ -0,0 +1,323 @@
1
+ SHELL := /bin/bash
2
+ .SHELLFLAGS := -eu -o pipefail -c
3
+
4
+ help:
5
+ @echo "Available targets:"
6
+ @echo " up - Start the development environment"
7
+ @echo " shell - Switch to development environment's shell"
8
+ @echo " ui - Open Metaflow UI"
9
+ @echo " dashboard - Open Minikube dashboard"
10
+ @echo " down - Stop and clean up the environment"
11
+ @echo " help - Show this help message"
12
+
13
+ HELM_VERSION := v3.14.0
14
+ MINIKUBE_VERSION := v1.32.0
15
+ TILT_VERSION := v0.33.11
16
+ GUM_VERSION := v0.15.2
17
+
18
+ MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
19
+ MKFILE_DIR := $(dir $(MKFILE_PATH))
20
+ DEVTOOLS_DIR := $(MKFILE_DIR).devtools
21
+ PICK_SERVICES := $(MKFILE_DIR)pick_services.sh
22
+ MINIKUBE_DIR := $(DEVTOOLS_DIR)/minikube
23
+ MINIKUBE := $(MINIKUBE_DIR)/minikube
24
+ TILT_DIR := $(DEVTOOLS_DIR)/tilt
25
+ TILT := $(TILT_DIR)/tilt
26
+ TILTFILE := $(MKFILE_DIR)/Tiltfile
27
+ MAKE_CMD := $(MAKE) -C "$(MKFILE_DIR)"
28
+
29
+ MINIKUBE_CPUS ?= 4
30
+ MINIKUBE_MEMORY ?= 6000
31
+ MINIKUBE_DISK_SIZE ?= 20g
32
+
33
+ ifeq ($(shell uname), Darwin)
34
+ minikube_os = darwin
35
+ tilt_os = mac
36
+ else
37
+ minikube_os = linux
38
+ tilt_os = linux
39
+ endif
40
+
41
+ ifeq ($(shell uname -m), x86_64)
42
+ arch = amd64
43
+ tilt_arch = x86_64
44
+ else
45
+ arch = arm64
46
+ tilt_arch = arm64
47
+ endif
48
+
49
+ # TODO: Move scripts to a folder
50
+
51
+ install-helm:
52
+ @if ! command -v helm >/dev/null 2>&1; then \
53
+ echo "📥 Installing Helm $(HELM_VERSION) (may require sudo access)..."; \
54
+ curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | HELM_INSTALL_VERSION=$(HELM_VERSION) bash; \
55
+ echo "✅ Helm installation complete"; \
56
+ fi
57
+
58
+ check-docker:
59
+ @if ! command -v docker >/dev/null 2>&1; then \
60
+ echo "❌ Docker is not installed. Please install Docker first: https://docs.docker.com/get-docker/"; \
61
+ exit 1; \
62
+ fi
63
+ @echo "🔍 Checking Docker daemon..."
64
+ @if [ "$(shell uname)" = "Darwin" ]; then \
65
+ open -a Docker || (echo "❌ Please start Docker Desktop" && exit 1); \
66
+ else \
67
+ systemctl is-active --quiet docker || (echo "❌ Docker daemon is not running. Start with 'sudo systemctl start docker'" && exit 1); \
68
+ fi
69
+ @echo "✅ Docker is running"
70
+
71
+ install-brew:
72
+ @if [ "$(shell uname)" = "Darwin" ] && ! command -v brew >/dev/null 2>&1; then \
73
+ echo "📥 Installing Homebrew..."; \
74
+ /bin/bash -c "$$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; \
75
+ echo "✅ Homebrew installation complete"; \
76
+ fi
77
+
78
+ install-curl:
79
+ @if ! command -v curl >/dev/null 2>&1; then \
80
+ echo "📥 Installing curl..."; \
81
+ if [ "$(shell uname)" = "Darwin" ]; then \
82
+ HOMEBREW_NO_AUTO_UPDATE=1 brew install curl; \
83
+ elif command -v apt-get >/dev/null 2>&1; then \
84
+ sudo apt-get update && sudo apt-get install -y curl; \
85
+ elif command -v yum >/dev/null 2>&1; then \
86
+ sudo yum install -y curl; \
87
+ elif command -v dnf >/dev/null 2>&1; then \
88
+ sudo dnf install -y curl; \
89
+ else \
90
+ echo "❌ Could not install curl. Please install manually."; \
91
+ exit 1; \
92
+ fi; \
93
+ echo "✅ curl installation complete"; \
94
+ fi
95
+
96
+ install-gum:
97
+ @echo "🔍 Checking if gum is installed..."
98
+ @if ! command -v gum >/dev/null 2>&1; then \
99
+ echo "📥 Installing gum..."; \
100
+ if [ "$(shell uname)" = "Darwin" ]; then \
101
+ HOMEBREW_NO_AUTO_UPDATE=1 brew install gum; \
102
+ elif command -v apt-get >/dev/null 2>&1; then \
103
+ curl -fsSL -o /tmp/gum.deb \
104
+ "https://github.com/charmbracelet/gum/releases/download/$(GUM_VERSION)/gum_$(GUM_VERSION:v%=%)_$(arch).deb"; \
105
+ sudo apt-get update -qq; \
106
+ sudo apt-get install -y /tmp/gum.deb || sudo dpkg -i /tmp/gum.deb; \
107
+ rm -f /tmp/gum.deb; \
108
+ else \
109
+ echo "❌ Could not determine how to install gum for your platform. Please install manually."; \
110
+ exit 1; \
111
+ fi; \
112
+ echo "✅ gum installation complete"; \
113
+ else \
114
+ echo "✅ gum is already installed."; \
115
+ fi
116
+
117
+ setup-minikube:
118
+ @if [ ! -f "$(MINIKUBE)" ]; then \
119
+ echo "📥 Installing Minikube $(MINIKUBE_VERSION)"; \
120
+ mkdir -p $(MINIKUBE_DIR); \
121
+ curl -L --fail https://github.com/kubernetes/minikube/releases/download/$(MINIKUBE_VERSION)/minikube-$(minikube_os)-$(arch) -o $(MINIKUBE) || (echo "❌ Failed to download minikube" && exit 1); \
122
+ chmod +x $(MINIKUBE); \
123
+ echo "✅ Minikube $(MINIKUBE_VERSION) installed successfully"; \
124
+ fi
125
+ @echo "🔧 Setting up Minikube $(MINIKUBE_VERSION) cluster..."
126
+ @if ! $(MINIKUBE) status >/dev/null 2>&1; then \
127
+ echo "🚀 Starting new Minikube $(MINIKUBE_VERSION) cluster..."; \
128
+ $(MINIKUBE) start \
129
+ --cpus $(MINIKUBE_CPUS) \
130
+ --memory $(MINIKUBE_MEMORY) \
131
+ --disk-size $(MINIKUBE_DISK_SIZE) \
132
+ --driver docker; \
133
+ echo "🔌 Enabling metrics-server and dashboard (quietly)..."; \
134
+ $(MINIKUBE) addons enable metrics-server >/dev/null 2>&1; \
135
+ $(MINIKUBE) addons enable dashboard >/dev/null 2>&1; \
136
+ else \
137
+ echo "✅ Minikube $(MINIKUBE_VERSION) cluster is already running"; \
138
+ fi
139
+ @echo "🎉 Minikube $(MINIKUBE_VERSION) cluster is ready!"
140
+
141
+ setup-tilt:
142
+ @if [ ! -f "$(TILT)" ]; then \
143
+ echo "📥 Installing Tilt $(TILT_VERSION)"; \
144
+ mkdir -p $(TILT_DIR); \
145
+ (curl -L https://github.com/tilt-dev/tilt/releases/download/$(TILT_VERSION)/tilt.$(TILT_VERSION:v%=%).$(tilt_os).$(tilt_arch).tar.gz | tar -xz -C $(TILT_DIR)) && echo "✅ Tilt $(TILT_VERSION) installed successfully" || (echo "❌ Failed to install Tilt" && exit 1); \
146
+ fi
147
+
148
+ tunnel:
149
+ $(MINIKUBE) tunnel
150
+
151
+ teardown-minikube:
152
+ @echo "🛑 Stopping Minikube $(MINIKUBE_VERSION) cluster..."
153
+ -$(MINIKUBE) stop
154
+ @echo "🗑️ Deleting Minikube $(MINIKUBE_VERSION) cluster..."
155
+ -$(MINIKUBE) delete --all
156
+ @echo "🧹 Removing Minikube binary..."
157
+ -rm -rf $(MINIKUBE_DIR)
158
+ @echo "✅ Minikube $(MINIKUBE_VERSION) teardown complete"
159
+
160
+ dashboard:
161
+ @echo "🔗 Opening Minikube Dashboard..."
162
+ @$(MINIKUBE) dashboard
163
+
164
+ # make shell is symlinked to metaflow-dev shell by metaflow
165
+ up: install-brew check-docker install-curl install-gum setup-minikube install-helm setup-tilt
166
+ @echo "🚀 Starting up (may require sudo access)..."
167
+ @mkdir -p $(DEVTOOLS_DIR)
168
+ @echo '#!/bin/bash' > $(DEVTOOLS_DIR)/start.sh
169
+ @echo 'set -e' >> $(DEVTOOLS_DIR)/start.sh
170
+ @echo 'trap "exit" INT TERM' >> $(DEVTOOLS_DIR)/start.sh
171
+ @echo 'trap "kill 0" EXIT' >> $(DEVTOOLS_DIR)/start.sh
172
+ @echo 'eval $$($(MINIKUBE) docker-env)' >> $(DEVTOOLS_DIR)/start.sh
173
+
174
+ @echo 'if [ -n "$$SERVICES_OVERRIDE" ]; then' >> "$(DEVTOOLS_DIR)/start.sh"
175
+ @echo ' echo "🌐 Using user-provided list of services: $$SERVICES_OVERRIDE"' >> "$(DEVTOOLS_DIR)/start.sh"
176
+ @echo ' SERVICES="$$SERVICES_OVERRIDE"' >> "$(DEVTOOLS_DIR)/start.sh"
177
+ @echo 'else' >> "$(DEVTOOLS_DIR)/start.sh"
178
+ @echo ' echo "📝 Selecting services..."' >> "$(DEVTOOLS_DIR)/start.sh"
179
+ @echo ' SERVICES=$$($(PICK_SERVICES))' >> "$(DEVTOOLS_DIR)/start.sh"
180
+ @echo 'fi' >> "$(DEVTOOLS_DIR)/start.sh"
181
+ @echo 'PATH="$(MINIKUBE_DIR):$(TILT_DIR):$$PATH" $(MINIKUBE) tunnel &' >> $(DEVTOOLS_DIR)/start.sh
182
+ @echo 'echo -e "🚀 Starting Tilt with selected services..."' >> $(DEVTOOLS_DIR)/start.sh
183
+ @echo 'echo -e "\033[1;38;5;46m\n🔥 \033[1;38;5;196mNext Steps:\033[0;38;5;46m Use \033[3mmetaflow-dev shell\033[23m to switch to the development\n environment'\''s shell and start executing your Metaflow flows.\n\033[0m"' >> "$(DEVTOOLS_DIR)/start.sh"
184
+ @echo 'PATH="$(MINIKUBE_DIR):$(TILT_DIR):$$PATH" SERVICES="$$SERVICES" tilt up -f $(TILTFILE)' >> $(DEVTOOLS_DIR)/start.sh
185
+ @echo 'wait' >> $(DEVTOOLS_DIR)/start.sh
186
+ @chmod +x $(DEVTOOLS_DIR)/start.sh
187
+ @$(DEVTOOLS_DIR)/start.sh
188
+
189
+ down:
190
+ @echo "🛑 Stopping all services..."
191
+ @-pkill -f "$(MINIKUBE) tunnel" 2>/dev/null || true
192
+ @echo "⏹️ Stopping Tilt..."
193
+ -PATH="$(MINIKUBE_DIR):$(TILT_DIR):$$PATH" tilt down -f $(TILTFILE)
194
+ @echo "🧹 Cleaning up Minikube..."
195
+ $(MAKE_CMD) teardown-minikube
196
+ @echo "🗑️ Removing Tilt binary and directory..."
197
+ -rm -rf $(TILT_DIR)
198
+ @echo "🧹 Removing temporary scripts..."
199
+ -rm -rf $(DEVTOOLS_DIR)
200
+ @echo "✨ All done!"
201
+
202
+ shell: setup-tilt
203
+ @echo "⏳ Checking if development environment is up..."
204
+ @set -e; \
205
+ for i in $$(seq 1 90); do \
206
+ if "$(TILT)" get session >/dev/null 2>&1; then \
207
+ found_session=1; \
208
+ break; \
209
+ else \
210
+ sleep 2; \
211
+ fi; \
212
+ done; \
213
+ if [ -z "$${found_session}" ]; then \
214
+ echo "❌ Development environment is not up."; \
215
+ echo " Please run 'metaflow-dev up' in another terminal, then re-run 'metaflow-dev shell'."; \
216
+ exit 1; \
217
+ fi
218
+ @echo "⏳ Waiting for development environment to be ready..."
219
+ @while true; do \
220
+ "$(TILT)" get uiresource generate-configs >/dev/null 2>&1; \
221
+ status=$$?; \
222
+ if [ $$status -eq 0 ]; then \
223
+ "$(TILT)" wait --for=condition=Ready uiresource/generate-configs; \
224
+ break; \
225
+ elif [ $$status -eq 127 ]; then \
226
+ echo "❌ Development environment is not up."; \
227
+ echo " Please run 'metaflow-dev up' in another terminal, then re-run 'metaflow-dev shell'."; \
228
+ exit 1; \
229
+ else \
230
+ sleep 1; \
231
+ fi; \
232
+ done
233
+ @echo "🔧 Starting a new shell for development environment..."
234
+ @bash -c '\
235
+ if [ -n "$$SHELL" ]; then \
236
+ user_shell="$$SHELL"; \
237
+ else \
238
+ user_shell="$(SHELL)"; \
239
+ fi; \
240
+ echo "🔎 Using $$user_shell for interactive session."; \
241
+ echo "🐍 If you installed Metaflow in a virtual environment, activate it now."; \
242
+ if [ -f "$(DEVTOOLS_DIR)/aws_config" ]; then \
243
+ env METAFLOW_HOME="$(DEVTOOLS_DIR)" \
244
+ METAFLOW_PROFILE=local \
245
+ AWS_CONFIG_FILE="$(DEVTOOLS_DIR)/aws_config" \
246
+ "$$user_shell" -i; \
247
+ else \
248
+ env METAFLOW_HOME="$(DEVTOOLS_DIR)" \
249
+ METAFLOW_PROFILE=local \
250
+ "$$user_shell" -i; \
251
+ fi'
252
+
253
+ # TODO: This can be done away with in a while since we now have metaflow-dev command.
254
+ #
255
+ # @echo '$(MAKE_CMD) create-dev-shell' >> $(DEVTOOLS_DIR)/start.sh
256
+ # @echo 'rm -f /tmp/metaflow-devshell-*' >> $(DEVTOOLS_DIR)/start.sh
257
+ create-dev-shell: setup-tilt
258
+ @bash -c '\
259
+ SHELL_PATH=/tmp/metaflow-dev-shell-$$$$ && \
260
+ echo "#!/bin/bash" > $$SHELL_PATH && \
261
+ echo "set -e" >> $$SHELL_PATH && \
262
+ echo "" >> $$SHELL_PATH && \
263
+ echo "echo \"⏳ Checking if development environment is up...\"" >> $$SHELL_PATH && \
264
+ echo "if ! $(TILT) get session >/dev/null 2>&1; then" >> $$SHELL_PATH && \
265
+ echo " echo \"❌ Development environment is not up.\"" >> $$SHELL_PATH && \
266
+ echo " echo \" Please run '\''make up'\'' in another terminal, then re-run this script.\"" >> $$SHELL_PATH && \
267
+ echo " exit 1" >> $$SHELL_PATH && \
268
+ echo "fi" >> $$SHELL_PATH && \
269
+ echo "" >> $$SHELL_PATH && \
270
+ echo "echo \"⏳ Waiting for development environment to be ready...\"" >> $$SHELL_PATH && \
271
+ echo "$(TILT) wait --for=condition=Ready uiresource/generate-configs" >> $$SHELL_PATH && \
272
+ echo "" >> $$SHELL_PATH && \
273
+ echo "echo \"🔧 Starting a new shell for development environment...\"" >> $$SHELL_PATH && \
274
+ echo "if [ -n \"\$$SHELL\" ]; then" >> $$SHELL_PATH && \
275
+ echo " user_shell=\"\$$SHELL\"" >> $$SHELL_PATH && \
276
+ echo "else" >> $$SHELL_PATH && \
277
+ echo " user_shell=\"$(SHELL)\"" >> $$SHELL_PATH && \
278
+ echo "fi" >> $$SHELL_PATH && \
279
+ echo "echo \"🔎 Using \$$user_shell for interactive session.\"" >> $$SHELL_PATH && \
280
+ echo "echo \"🐍 If you installed Metaflow in a virtual environment, activate it now.\"" >> $$SHELL_PATH && \
281
+ echo "if [ -f \"$(DEVTOOLS_DIR)/aws_config\" ]; then" >> $$SHELL_PATH && \
282
+ echo " env METAFLOW_HOME=\"$(DEVTOOLS_DIR)\" \\" >> $$SHELL_PATH && \
283
+ echo " METAFLOW_PROFILE=local \\" >> $$SHELL_PATH && \
284
+ echo " AWS_CONFIG_FILE=\"$(DEVTOOLS_DIR)/aws_config\" \\" >> $$SHELL_PATH && \
285
+ echo " \"\$$user_shell\" -i" >> $$SHELL_PATH && \
286
+ echo "else" >> $$SHELL_PATH && \
287
+ echo " env METAFLOW_HOME=\"$(DEVTOOLS_DIR)\" \\" >> $$SHELL_PATH && \
288
+ echo " METAFLOW_PROFILE=local \\" >> $$SHELL_PATH && \
289
+ echo " \"\$$user_shell\" -i" >> $$SHELL_PATH && \
290
+ echo "fi" >> $$SHELL_PATH && \
291
+ chmod +x $$SHELL_PATH && \
292
+ echo "✨ Created $$SHELL_PATH" && \
293
+ echo "🔑 Execute it from ANY directory to switch to development environment shell!" \
294
+ '
295
+
296
+ ui: setup-tilt
297
+ @echo "⏳ Checking if the development environment is up..."
298
+ @if ! $(TILT) get session >/dev/null 2>&1; then \
299
+ echo "❌ Development environment is not up."; \
300
+ echo " Please run 'metaflow-dev up' in another terminal, then re-run 'metaflow-dev ui'."; \
301
+ exit 1; \
302
+ fi
303
+ @echo "⏳ Waiting for Metaflow UI to be ready..."
304
+ @while true; do \
305
+ "$(TILT)" get uiresource metaflow-ui >/dev/null 2>&1; \
306
+ status=$$?; \
307
+ if [ $$status -eq 0 ]; then \
308
+ "$(TILT)" wait --for=condition=Ready uiresource/metaflow-ui; \
309
+ break; \
310
+ elif [ $$status -eq 127 ]; then \
311
+ echo "❌ Development environment is not up."; \
312
+ echo " Please run 'metaflow-dev up' in another terminal, then re-run 'metaflow-dev shell'."; \
313
+ exit 1; \
314
+ else \
315
+ sleep 1; \
316
+ fi; \
317
+ done
318
+ @echo "🔗 Opening Metaflow UI at http://localhost:3000"
319
+ @open http://localhost:3000
320
+
321
+ .PHONY: install-helm setup-minikube setup-tilt teardown-minikube tunnel up down check-docker install-curl install-gum install-brew up down dashboard shell ui help
322
+
323
+ .DEFAULT_GOAL := up
@@ -0,0 +1,607 @@
1
+ # Tilt configuration for running Metaflow on a local Kubernetes stack
2
+ #
3
+ # Usage:
4
+ # Start the development environment:
5
+ # $ tilt up
6
+ # Stop and clean up:
7
+ # $ tilt down
8
+
9
+ # TODO:
10
+ # 1. move away from temporary images
11
+ # 2. introduce kueue and jobsets
12
+ # 3. lock versions
13
+
14
+ version_settings(constraint='>=0.22.2')
15
+ allow_k8s_contexts('minikube')
16
+
17
+ components = {
18
+ "metadata-service": ["postgresql"],
19
+ "ui": ["postgresql", "minio"],
20
+ "minio": [],
21
+ "postgresql": [],
22
+ "argo-workflows": [],
23
+ "argo-events": ["argo-workflows"],
24
+ }
25
+
26
+ if os.getenv("SERVICES", "").strip():
27
+ requested_components = os.getenv("SERVICES", "").split(",")
28
+ else:
29
+ requested_components = list(components.keys())
30
+
31
+ metaflow_config = {}
32
+ metaflow_config["METAFLOW_KUBERNETES_NAMESPACE"] = "default"
33
+
34
+ aws_config = []
35
+
36
+ def write_config_files():
37
+ metaflow_json = encode_json(metaflow_config)
38
+ cmd = '''cat > .devtools/config_local.json <<EOF
39
+ %s
40
+ EOF
41
+ ''' % (metaflow_json)
42
+ if aws_config and aws_config.strip():
43
+ cmd += '''cat > .devtools/aws_config <<EOF
44
+ %s
45
+ EOF
46
+ ''' % (aws_config.strip())
47
+ return cmd
48
+
49
+ load('ext://helm_resource', 'helm_resource', 'helm_repo')
50
+ load('ext://helm_remote', 'helm_remote')
51
+
52
+
53
+ def resolve(component, resolved=None):
54
+ if resolved == None:
55
+ resolved = []
56
+ if component in resolved:
57
+ return resolved
58
+ if component in components:
59
+ for dep in components[component]:
60
+ resolve(dep, resolved)
61
+ resolved.append(component)
62
+ return resolved
63
+
64
+ valid_components = []
65
+ for component in components.keys():
66
+ if component not in valid_components:
67
+ valid_components.append(component)
68
+ for deps in components.values():
69
+ for dep in deps:
70
+ if dep not in valid_components:
71
+ valid_components.append(dep)
72
+
73
+ enabled_components = []
74
+ for component in requested_components:
75
+ if component not in valid_components:
76
+ fail("Unknown component: " + component)
77
+ for result in resolve(component):
78
+ if result not in enabled_components:
79
+ enabled_components.append(result)
80
+
81
+ # Print a friendly summary when running `tilt up`.
82
+ if config.tilt_subcommand == 'up':
83
+ print("\n📦 Components to install:")
84
+ for component in enabled_components:
85
+ print("• " + component)
86
+ if component in components and components[component]:
87
+ print(" ↳ requires: " + ", ".join(components[component]))
88
+
89
+ config_resources = []
90
+
91
+ #################################################
92
+ # MINIO
93
+ #################################################
94
+ if "minio" in enabled_components:
95
+ helm_remote(
96
+ 'minio',
97
+ repo_name='minio-s3',
98
+ repo_url='https://charts.min.io/',
99
+ set=[
100
+ 'rootUser=rootuser',
101
+ 'rootPassword=rootpass123',
102
+ 'buckets[0].name=metaflow-test',
103
+ 'buckets[0].policy=none',
104
+ 'buckets[0].purge=false',
105
+ 'mode=standalone',
106
+ 'replicas=1',
107
+ 'persistence.enabled=false',
108
+ 'resources.requests.memory=128Mi',
109
+ 'resources.requests.cpu=50m',
110
+ 'resources.limits.memory=256Mi',
111
+ 'resources.limits.cpu=100m',
112
+ ]
113
+ )
114
+
115
+ k8s_resource(
116
+ 'minio',
117
+ port_forwards=[
118
+ '9000:9000',
119
+ '9001:9001'
120
+ ],
121
+ links=[
122
+ link('http://localhost:9000', 'MinIO API'),
123
+ link('http://localhost:9001/login', 'MinIO Console (rootuser/rootpass123)')
124
+ ],
125
+ labels=['minio'],
126
+ )
127
+
128
+ k8s_resource(
129
+ "minio-post-job",
130
+ labels=['minio'],
131
+ )
132
+
133
+ k8s_yaml(encode_yaml({
134
+ 'apiVersion': 'v1',
135
+ 'kind': 'Secret',
136
+ 'metadata': {'name': 'minio-secret'},
137
+ 'type': 'Opaque',
138
+ 'stringData': {
139
+ 'AWS_ACCESS_KEY_ID': 'rootuser',
140
+ 'AWS_SECRET_ACCESS_KEY': 'rootpass123',
141
+ 'AWS_ENDPOINT_URL_S3': 'http://minio.default.svc.cluster.local:9000',
142
+ }
143
+ }))
144
+
145
+ metaflow_config["METAFLOW_DEFAULT_DATASTORE"] = "s3"
146
+ metaflow_config["METAFLOW_DATASTORE_SYSROOT_S3"] = "s3://metaflow-test/metaflow"
147
+ metaflow_config["METAFLOW_KUBERNETES_SECRETS"] = "minio-secret"
148
+
149
+ aws_config = """[default]
150
+ aws_access_key_id = rootuser
151
+ aws_secret_access_key = rootpass123
152
+ endpoint_url = http://localhost:9000
153
+ """
154
+ config_resources.append('minio')
155
+
156
+ #################################################
157
+ # POSTGRESQL
158
+ #################################################
159
+ if "postgresql" in enabled_components:
160
+ helm_remote(
161
+ 'postgresql',
162
+ version='12.5.6',
163
+ repo_name='postgresql',
164
+ repo_url='https://charts.bitnami.com/bitnami',
165
+ set=[
166
+ 'auth.username=metaflow',
167
+ 'auth.password=metaflow123',
168
+ 'auth.database=metaflow',
169
+ 'primary.persistence.enabled=false',
170
+ 'primary.resources.requests.memory=128Mi',
171
+ 'primary.resources.requests.cpu=50m',
172
+ 'primary.resources.limits.memory=256Mi',
173
+ 'primary.resources.limits.cpu=100m',
174
+ 'primary.terminationGracePeriodSeconds=1',
175
+ 'primary.podSecurityContext.enabled=false',
176
+ 'primary.containerSecurityContext.enabled=false',
177
+ 'volumePermissions.enabled=false',
178
+ 'shmVolume.enabled=false',
179
+ 'primary.extraVolumes=null',
180
+ 'primary.extraVolumeMounts=null'
181
+ ]
182
+ )
183
+
184
+ k8s_resource(
185
+ 'postgresql',
186
+ port_forwards=['5432:5432'],
187
+ links=[
188
+ link('postgresql://metaflow:metaflow@localhost:5432/metaflow', 'PostgreSQL Connection')
189
+ ],
190
+ labels=['postgresql'],
191
+ resource_deps=components['postgresql'],
192
+ )
193
+
194
+ config_resources.append('postgresql')
195
+
196
+ #################################################
197
+ # ARGO WORKFLOWS
198
+ #################################################
199
+ if "argo-workflows" in enabled_components:
200
+ helm_remote(
201
+ 'argo-workflows',
202
+ repo_name='argo',
203
+ repo_url='https://argoproj.github.io/argo-helm',
204
+ set=[
205
+ 'server.extraArgs[0]=--auth-mode=server',
206
+ 'workflow.serviceAccount.create=true',
207
+ 'workflow.rbac.create=true',
208
+ 'server.livenessProbe.initialDelaySeconds=1',
209
+ 'server.readinessProbe.initialDelaySeconds=1',
210
+ 'server.resources.requests.memory=128Mi',
211
+ 'server.resources.requests.cpu=50m',
212
+ 'server.resources.limits.memory=256Mi',
213
+ 'server.resources.limits.cpu=100m',
214
+ 'controller.resources.requests.memory=128Mi',
215
+ 'controller.resources.requests.cpu=50m',
216
+ 'controller.resources.limits.memory=256Mi',
217
+ 'controller.resources.limits.cpu=100m'
218
+ ]
219
+ )
220
+
221
+ k8s_yaml(encode_yaml({
222
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
223
+ 'kind': 'Role',
224
+ 'metadata': {
225
+ 'name': 'argo-workflowtaskresults-role',
226
+ 'namespace': 'default'
227
+ },
228
+ 'rules': [{
229
+ 'apiGroups': ['argoproj.io'],
230
+ 'resources': ['workflowtaskresults'],
231
+ 'verbs': ['create', 'patch', 'get', 'list']
232
+ }]
233
+ }))
234
+
235
+ k8s_yaml(encode_yaml({
236
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
237
+ 'kind': 'RoleBinding',
238
+ 'metadata': {
239
+ 'name': 'default-argo-workflowtaskresults-binding',
240
+ 'namespace': 'default'
241
+ },
242
+ 'subjects': [{
243
+ 'kind': 'ServiceAccount',
244
+ 'name': 'default',
245
+ 'namespace': 'default'
246
+ }],
247
+ 'roleRef': {
248
+ 'kind': 'Role',
249
+ 'name': 'argo-workflowtaskresults-role',
250
+ 'apiGroup': 'rbac.authorization.k8s.io'
251
+ }
252
+ }))
253
+
254
+ k8s_resource(
255
+ workload='argo-workflows-server',
256
+ port_forwards=['2746:2746'],
257
+ links=[
258
+ link('http://localhost:2746', 'Argo Workflows UI')
259
+ ],
260
+ labels=['argo-workflows'],
261
+ resource_deps=components['argo-workflows']
262
+ )
263
+
264
+ k8s_resource(
265
+ workload='argo-workflows-workflow-controller',
266
+ labels=['argo-workflows'],
267
+ resource_deps=components['argo-workflows']
268
+ )
269
+
270
+ config_resources.append('argo-workflows-workflow-controller')
271
+ config_resources.append('argo-workflows-server')
272
+
273
+ #################################################
274
+ # ARGO EVENTS
275
+ #################################################
276
+ if "argo-events" in enabled_components:
277
+ helm_remote(
278
+ 'argo-events',
279
+ repo_name='argo',
280
+ repo_url='https://argoproj.github.io/argo-helm',
281
+ set=[
282
+ 'crds.install=true',
283
+ 'controller.metrics.enabled=true',
284
+ 'controller.livenessProbe.initialDelaySeconds=1',
285
+ 'controller.readinessProbe.initialDelaySeconds=1',
286
+ 'controller.resources.requests.memory=64Mi',
287
+ 'controller.resources.requests.cpu=25m',
288
+ 'controller.resources.limits.memory=128Mi',
289
+ 'controller.resources.limits.cpu=50m',
290
+ 'configs.jetstream.streamConfig.maxAge=72h',
291
+ 'configs.jetstream.streamConfig.replicas=1',
292
+ 'controller.rbac.enabled=true',
293
+ 'controller.rbac.namespaced=false',
294
+ 'controller.serviceAccount.create=true',
295
+ 'controller.serviceAccount.name=argo-events-events-controller-sa',
296
+ 'configs.jetstream.versions[0].configReloaderImage=natsio/nats-server-config-reloader:latest',
297
+ 'configs.jetstream.versions[0].metricsExporterImage=natsio/prometheus-nats-exporter:latest',
298
+ 'configs.jetstream.versions[0].natsImage=nats:latest',
299
+ 'configs.jetstream.versions[0].startCommand=/nats-server',
300
+ 'configs.jetstream.versions[0].version=latest',
301
+ 'configs.jetstream.versions[1].configReloaderImage=natsio/nats-server-config-reloader:latest',
302
+ 'configs.jetstream.versions[1].metricsExporterImage=natsio/prometheus-nats-exporter:latest',
303
+ 'configs.jetstream.versions[1].natsImage=nats:2.9.15',
304
+ 'configs.jetstream.versions[1].startCommand=/nats-server',
305
+ 'configs.jetstream.versions[1].version=2.9.15',
306
+ ]
307
+ )
308
+
309
+ k8s_yaml(encode_yaml({
310
+ 'apiVersion': 'v1',
311
+ 'kind': 'ServiceAccount',
312
+ 'metadata': {
313
+ 'name': 'operate-workflow-sa',
314
+ 'namespace': 'default'
315
+ }
316
+ }))
317
+
318
+ k8s_yaml(encode_yaml({
319
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
320
+ 'kind': 'Role',
321
+ 'metadata': {
322
+ 'name': 'operate-workflow-role',
323
+ 'namespace': 'default'
324
+ },
325
+ 'rules': [{
326
+ 'apiGroups': ['argoproj.io'],
327
+ 'resources': [
328
+ 'workflows',
329
+ 'workflowtemplates',
330
+ 'cronworkflows',
331
+ 'clusterworkflowtemplates'
332
+ ],
333
+ 'verbs': ['*']
334
+ }]
335
+ }))
336
+
337
+ k8s_yaml(encode_yaml({
338
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
339
+ 'kind': 'RoleBinding',
340
+ 'metadata': {
341
+ 'name': 'operate-workflow-role-binding',
342
+ 'namespace': 'default'
343
+ },
344
+ 'roleRef': {
345
+ 'apiGroup': 'rbac.authorization.k8s.io',
346
+ 'kind': 'Role',
347
+ 'name': 'operate-workflow-role'
348
+ },
349
+ 'subjects': [{
350
+ 'kind': 'ServiceAccount',
351
+ 'name': 'operate-workflow-sa'
352
+ }]
353
+ }))
354
+
355
+ k8s_yaml(encode_yaml({
356
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
357
+ 'kind': 'Role',
358
+ 'metadata': {
359
+ 'name': 'view-events-role',
360
+ 'namespace': 'default'
361
+ },
362
+ 'rules': [{
363
+ 'apiGroups': ['argoproj.io'],
364
+ 'resources': [
365
+ 'eventsources',
366
+ 'eventbuses',
367
+ 'sensors'
368
+ ],
369
+ 'verbs': [
370
+ 'get',
371
+ 'list',
372
+ 'watch'
373
+ ]
374
+ }]
375
+ }))
376
+
377
+ k8s_yaml(encode_yaml({
378
+ 'apiVersion': 'rbac.authorization.k8s.io/v1',
379
+ 'kind': 'RoleBinding',
380
+ 'metadata': {
381
+ 'name': 'view-events-role-binding',
382
+ 'namespace': 'default'
383
+ },
384
+ 'roleRef': {
385
+ 'apiGroup': 'rbac.authorization.k8s.io',
386
+ 'kind': 'Role',
387
+ 'name': 'view-events-role'
388
+ },
389
+ 'subjects': [{
390
+ 'kind': 'ServiceAccount',
391
+ 'name': 'argo-workflows',
392
+ 'namespace': 'default'
393
+ }]
394
+ }))
395
+
396
+ k8s_yaml(encode_yaml({
397
+ 'apiVersion': 'argoproj.io/v1alpha1',
398
+ 'kind': 'EventBus',
399
+ 'metadata': {
400
+ 'name': 'default',
401
+ 'namespace': 'default'
402
+ },
403
+ 'spec': {
404
+ 'jetstream': {
405
+ 'version': '2.9.15',
406
+ 'replicas': 3,
407
+ 'containerTemplate': {
408
+ 'resources': {
409
+ 'limits': {
410
+ 'cpu': '100m',
411
+ 'memory': '128Mi'
412
+ },
413
+ 'requests': {
414
+ 'cpu': '100m',
415
+ 'memory': '128Mi'
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }))
422
+
423
+ k8s_yaml(encode_yaml({
424
+ 'apiVersion': 'argoproj.io/v1alpha1',
425
+ 'kind': 'EventSource',
426
+ 'metadata': {
427
+ 'name': 'argo-events-webhook',
428
+ 'namespace': 'default'
429
+ },
430
+ 'spec': {
431
+ 'template': {
432
+ 'container': {
433
+ 'resources': {
434
+ 'requests': {
435
+ 'cpu': '25m',
436
+ 'memory': '50Mi'
437
+ },
438
+ 'limits': {
439
+ 'cpu': '25m',
440
+ 'memory': '50Mi'
441
+ }
442
+ }
443
+ }
444
+ },
445
+ 'service': {
446
+ 'ports': [
447
+ {
448
+ 'port': 12000,
449
+ 'targetPort': 12000
450
+ }
451
+ ]
452
+ },
453
+ 'webhook': {
454
+ 'metaflow-event': {
455
+ 'port': '12000',
456
+ 'endpoint': '/metaflow-event',
457
+ 'method': 'POST'
458
+ }
459
+ }
460
+ }
461
+ }))
462
+
463
+ # Create a custom service and port-forward it because tilt :/
464
+ k8s_yaml(encode_yaml(
465
+ {
466
+ 'apiVersion': 'v1',
467
+ 'kind': 'Service',
468
+ 'metadata': {
469
+ 'name': 'argo-events-webhook-eventsource-svc-tilt',
470
+ 'namespace': 'default',
471
+ },
472
+ 'spec': {
473
+ 'ports': [{
474
+ 'port': 12000,
475
+ 'protocol': 'TCP',
476
+ 'targetPort': 12000
477
+ }],
478
+ 'selector': {
479
+ 'controller': 'eventsource-controller',
480
+ 'eventsource-name': 'argo-events-webhook',
481
+ 'owner-name': 'argo-events-webhook'
482
+ },
483
+ 'type': 'ClusterIP'
484
+ }
485
+ }
486
+ ))
487
+
488
+ local_resource(
489
+ name='argo-events-webhook-eventsource-svc',
490
+ serve_cmd='while ! kubectl get service/argo-events-webhook-eventsource-svc-tilt >/dev/null 2>&1 || ! kubectl get pods -l eventsource-name=argo-events-webhook -o jsonpath="{.items[*].status.phase}" | grep -q "Running"; do sleep 5; done && kubectl port-forward service/argo-events-webhook-eventsource-svc-tilt 12000:12000',
491
+ links=[
492
+ link('http://localhost:12000/metaflow-event', 'Argo Events Webhook'),
493
+ ],
494
+ labels=['argo-events']
495
+ )
496
+
497
+ k8s_resource(
498
+ 'argo-events-controller-manager',
499
+ labels=['argo-events'],
500
+ )
501
+
502
+ metaflow_config["METAFLOW_ARGO_EVENTS_EVENT"] = "metaflow-event"
503
+ metaflow_config["METAFLOW_ARGO_EVENTS_EVENT_BUS"] = "default"
504
+ metaflow_config["METAFLOW_ARGO_EVENTS_EVENT_SOURCE"] = "argo-events-webhook"
505
+ metaflow_config["METAFLOW_ARGO_EVENTS_SERVICE_ACCOUNT"] = "operate-workflow-sa"
506
+ metaflow_config["METAFLOW_ARGO_EVENTS_WEBHOOK_AUTH"] = "service"
507
+ metaflow_config["METAFLOW_ARGO_EVENTS_WEBHOOK_URL"] = "http://argo-events-webhook-eventsource-svc:12000/metaflow-event"
508
+
509
+ config_resources.append('argo-events-controller-manager')
510
+ config_resources.append('argo-events-webhook-eventsource-svc')
511
+
512
+ #################################################
513
+ # METADATA SERVICE
514
+ #################################################
515
+ if "metadata-service" in enabled_components:
516
+ helm_remote(
517
+ 'metaflow-service',
518
+ repo_name='metaflow-tools',
519
+ repo_url='https://outerbounds.github.io/metaflow-tools',
520
+ set=[
521
+ 'metadatadb.user=metaflow',
522
+ 'metadatadb.password=metaflow123',
523
+ 'metadatadb.database=metaflow',
524
+ 'metadatadb.host=postgresql',
525
+ 'image.repository=public.ecr.aws/p7g1e3j4/metaflow-service',
526
+ 'image.tag=2.4.13-fbcc7d04',
527
+ 'resources.requests.cpu=25m',
528
+ 'resources.requests.memory=64Mi',
529
+ 'resources.limits.cpu=50m',
530
+ 'resources.limits.memory=128Mi'
531
+ ]
532
+ )
533
+
534
+ k8s_resource(
535
+ 'metaflow-service',
536
+ port_forwards=['8080:8080'],
537
+ links=[link('http://localhost:8080/ping', 'Ping Metaflow Service')],
538
+ labels=['metadata-service'],
539
+ resource_deps=components['metadata-service']
540
+ )
541
+
542
+ metaflow_config["METAFLOW_DEFAULT_METADATA"] = "service"
543
+ metaflow_config["METAFLOW_SERVICE_URL"] = "http://localhost:8080"
544
+ metaflow_config["METAFLOW_SERVICE_INTERNAL_URL"] = "http://metaflow-service.default.svc.cluster.local:8080"
545
+
546
+ config_resources.append('metaflow-service')
547
+
548
+ #################################################
549
+ # METAFLOW UI
550
+ #################################################
551
+ if "ui" in enabled_components:
552
+ helm_remote(
553
+ 'metaflow-ui',
554
+ repo_name='metaflow-tools',
555
+ repo_url='https://outerbounds.github.io/metaflow-tools',
556
+ set=[
557
+ 'uiBackend.metadatadb.user=metaflow',
558
+ 'uiBackend.metadatadb.password=metaflow123',
559
+ 'uiBackend.metadatadb.name=metaflow',
560
+ 'uiBackend.metadatadb.host=postgresql',
561
+ 'uiBackend.metaflowDatastoreSysRootS3=s3://metaflow-test',
562
+ 'uiBackend.metaflowS3EndpointURL=http://minio.default.svc.cluster.local:9000',
563
+ 'uiBackend.image.name=public.ecr.aws/p7g1e3j4/metaflow-service',
564
+ 'uiBackend.image.tag=2.4.13-fbcc7d04',
565
+ 'uiBackend.env[0].name=AWS_ACCESS_KEY_ID',
566
+ 'uiBackend.env[0].value=rootuser',
567
+ 'uiBackend.env[1].name=AWS_SECRET_ACCESS_KEY',
568
+ 'uiBackend.env[1].value=rootpass123',
569
+ # TODO: configure lower cache limits
570
+ 'uiBackend.resources.requests.cpu=100m',
571
+ 'uiBackend.resources.requests.memory=256Mi',
572
+ 'uiStatic.metaflowUIBackendURL=http://localhost:8083/api',
573
+ 'uiStatic.image.name=public.ecr.aws/outerbounds/metaflow_ui',
574
+ 'uiStatic.image.tag=v1.3.13-5-g5dd049e',
575
+ 'uiStatic.resources.requests.cpu=25m',
576
+ 'uiStatic.resources.requests.memory=64Mi',
577
+ 'uiStatic.resources.limits.cpu=50m',
578
+ 'uiStatic.resources.limits.memory=128Mi',
579
+ ]
580
+ )
581
+
582
+ k8s_resource(
583
+ 'metaflow-ui-static',
584
+ port_forwards=['3000:3000'],
585
+ links=[link('http://localhost:3000', 'Metaflow UI')],
586
+ labels=['metaflow-ui'],
587
+ resource_deps=components['ui']
588
+ )
589
+
590
+ k8s_resource(
591
+ 'metaflow-ui',
592
+ port_forwards=['8083:8083'],
593
+ links=[link('http://localhost:3000', 'Metaflow UI')],
594
+ labels=['metaflow-ui'],
595
+ resource_deps=components['ui']
596
+ )
597
+
598
+ metaflow_config["METAFLOW_UI_URL"] = "http://localhost:3000"
599
+
600
+ config_resources.append('metaflow-ui')
601
+ config_resources.append('metaflow-ui-static')
602
+
603
+ local_resource(
604
+ name="generate-configs",
605
+ cmd=write_config_files(),
606
+ resource_deps=config_resources,
607
+ )
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ COLOR="214"
6
+
7
+ LOGO="
8
+ ______ ________________________________________ __________ __
9
+ ___ |/ /__ ____/__ __/__ |__ ____/__ / __ __ \_ | / /
10
+ __ /|_/ /__ __/ __ / __ /| |_ /_ __ / _ / / /_ | /| / /
11
+ _ / / / _ /___ _ / _ ___ | __/ _ /___/ /_/ /__ |/ |/ /
12
+ /_/ /_/ /_____/ /_/ /_/ |_/_/ /_____/\____/ ____/|__/
13
+ "
14
+
15
+ SERVICE_OPTIONS=(
16
+ "minio"
17
+ "metadata-service"
18
+ "ui"
19
+ "argo-workflows"
20
+ "argo-events"
21
+ )
22
+
23
+ gum style "$LOGO" \
24
+ --foreground "$COLOR" \
25
+ --padding "0 1" \
26
+ --margin "0 1" \
27
+ --align center >&2
28
+
29
+ gum style "Select services to deploy (press enter to select all):" \
30
+ --foreground "$COLOR" \
31
+ --bold >&2
32
+
33
+ pretty_print() {
34
+ local items=("$@")
35
+
36
+ if [ "${#items[@]}" -eq 1 ]; then
37
+ echo "${items[0]}"
38
+ return
39
+ fi
40
+
41
+ if [ "${#items[@]}" -eq 2 ]; then
42
+ echo "${items[0]} and ${items[1]}"
43
+ return
44
+ fi
45
+
46
+ local last_item="${items[-1]}"
47
+ unset 'items[-1]'
48
+ echo "$(IFS=,; echo "${items[*]}"), and $last_item"
49
+ }
50
+
51
+ pretty_print() {
52
+ local items=("$@")
53
+ local length=${#items[@]}
54
+
55
+ if [ "$length" -eq 0 ]; then
56
+ echo "(none)"
57
+ return
58
+ fi
59
+
60
+ if [ "$length" -eq 1 ]; then
61
+ echo "${items[0]}"
62
+ return
63
+ fi
64
+
65
+ if [ "$length" -eq 2 ]; then
66
+ echo "${items[0]} and ${items[1]}"
67
+ return
68
+ fi
69
+
70
+ local last_index=$((length - 1))
71
+ local last_item="${items[$last_index]}"
72
+ unset 'items[last_index]'
73
+
74
+ local joined
75
+ IFS=","
76
+ joined="${items[*]}"
77
+ unset IFS
78
+ joined="${joined//,/, }"
79
+
80
+ echo "$joined, and $last_item"
81
+ }
82
+
83
+ SELECTED="$(
84
+ gum choose "${SERVICE_OPTIONS[@]}" \
85
+ --no-limit \
86
+ --cursor.foreground="$COLOR" \
87
+ --selected.foreground="$COLOR"
88
+ )"
89
+
90
+ SELECTED_SERVICES=()
91
+ while IFS= read -r line; do
92
+ [ -n "$line" ] && SELECTED_SERVICES+=("$line")
93
+ done <<< "$SELECTED"
94
+
95
+ # If nothing was chosen, default to all
96
+ if [ -z "$SELECTED_SERVICES" ]; then
97
+ gum style "🙅 No services selected. Deploying all..." --foreground "$COLOR" >&2
98
+ SELECTED_SERVICES=("${SERVICE_OPTIONS[@]}")
99
+ fi
100
+
101
+ PRINTABLE="$(pretty_print "${SELECTED_SERVICES[@]}")"
102
+ gum style "✅ Deploying $PRINTABLE" --foreground "$COLOR" >&2
103
+
104
+ echo "$(IFS=,; echo "${SELECTED_SERVICES[*]}")"
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: metaflow
3
- Version: 2.14.3
4
- Summary: Metaflow: More Data Science, Less Engineering
3
+ Version: 2.15.0
4
+ Summary: Metaflow: More AI and ML, Less Engineering
5
5
  Author: Metaflow Developers
6
6
  Author-email: help@metaflow.org
7
7
  License: Apache Software License
@@ -26,7 +26,7 @@ License-File: LICENSE
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: boto3
28
28
  Provides-Extra: stubs
29
- Requires-Dist: metaflow-stubs==2.14.3; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.15.0; extra == "stubs"
30
30
  Dynamic: author
31
31
  Dynamic: author-email
32
32
  Dynamic: classifier
@@ -36,7 +36,7 @@ metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
36
36
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
37
37
  metaflow/util.py,sha256=mJBkV5tShIyCsLDeM1zygQGeciQVMrVPm_qI8Oi33G0,14656
38
38
  metaflow/vendor.py,sha256=FchtA9tH22JM-eEtJ2c9FpUdMn8sSb1VHuQS56EcdZk,5139
39
- metaflow/version.py,sha256=WyNmkJuZnJ4inDdNJ_Tpr0MjMGRGQA51T55iBVMg3tc,28
39
+ metaflow/version.py,sha256=IWpJOWnkdfrf92MEtc6EPVbrvSldtZ2YIiIqxV_gwJI,28
40
40
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
41
41
  metaflow/_vendor/typing_extensions.py,sha256=0nUs5p1A_UrZigrAVBoOEM6TxU37zzPDUtiij1ZwpNc,110417
42
42
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -122,6 +122,7 @@ metaflow/client/filecache.py,sha256=Wy0yhhCqC1JZgebqi7z52GCwXYnkAqMZHTtxThvwBgM,
122
122
  metaflow/cmd/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
123
123
  metaflow/cmd/configure_cmd.py,sha256=o-DKnUf2FBo_HiMVyoyzQaGBSMtpbEPEdFTQZ0hkU-k,33396
124
124
  metaflow/cmd/main_cli.py,sha256=H0UC-jiZjThHZgQeMyNZh3raSDDyUTvMspYUqKFnNSU,2955
125
+ metaflow/cmd/make_wrapper.py,sha256=NFpSdESs4Ks9xeurmYB5VUyYplhNcONDZJcUP2cf8-8,494
125
126
  metaflow/cmd/tutorials_cmd.py,sha256=8FdlKkicTOhCIDKcBR5b0Oz6giDvS-EMY3o9skIrRqw,5156
126
127
  metaflow/cmd/util.py,sha256=jS_0rUjOnGGzPT65fzRLdGjrYAOOLA4jU2S0HJLV0oc,406
127
128
  metaflow/cmd/develop/__init__.py,sha256=p1Sy8yU1MEKSrH5ttOWOZvNcI1qYu6J6jghdTHwPgOw,689
@@ -194,9 +195,9 @@ metaflow/plugins/aws/aws_client.py,sha256=mO8UD6pxFaOnxDb3hTP3HB7Gqb_ZxoR-76LT68
194
195
  metaflow/plugins/aws/aws_utils.py,sha256=kNd61C54Y3WxrL7KSjoKydRjBQ1p3exc9QXux-jZyDE,7510
195
196
  metaflow/plugins/aws/batch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
197
  metaflow/plugins/aws/batch/batch.py,sha256=e9ssahWM18GnipPK2sqYB-ztx9w7Eoo7YtWyEtufYxs,17787
197
- metaflow/plugins/aws/batch/batch_cli.py,sha256=gVQMWBLeuqO3U3PhVJSHLwa-CNHsmW0Cvmv-K0C-DoA,11758
198
+ metaflow/plugins/aws/batch/batch_cli.py,sha256=aRLuLky0xLGU5zR8jqUQQ0Fbs9o5Wq4Z4M8MLF9XSSw,11807
198
199
  metaflow/plugins/aws/batch/batch_client.py,sha256=J50RMEXeEXFe5RqNUM1HN22BuDQFYFVQ4FSMOK55VWY,28798
199
- metaflow/plugins/aws/batch/batch_decorator.py,sha256=zRq0jF-FlzZsvv-ZKCsmSzUFIaflb1dLmEtkoPStNA4,17525
200
+ metaflow/plugins/aws/batch/batch_decorator.py,sha256=Ks2boTyseI0ohN1ecwD2qWZx_YeERRXTSjbeughVBpc,17878
200
201
  metaflow/plugins/aws/secrets_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
201
202
  metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py,sha256=bBrGw4gRcKX9SLD8iKqPm_S_Zw5Y6F8AjxP6jPbkPpI,8136
202
203
  metaflow/plugins/aws/step_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -300,7 +301,7 @@ metaflow/plugins/metadata_providers/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uz
300
301
  metaflow/plugins/metadata_providers/local.py,sha256=Z0CXaGZJbAkj4II3WspJi-uCCtShH64yaXZQ5i9Ym7g,24390
301
302
  metaflow/plugins/metadata_providers/service.py,sha256=K0Ym6lcmegX6wBC5uZbeAFQJSDFc8e6DzJiCB1VIqjc,22554
302
303
  metaflow/plugins/pypi/__init__.py,sha256=0YFZpXvX7HCkyBFglatual7XGifdA1RwC3U4kcizyak,1037
303
- metaflow/plugins/pypi/bootstrap.py,sha256=oRfJkAp99R338nYX2wq4FrV0Ax5h4QqqifNmAXrM3CY,14892
304
+ metaflow/plugins/pypi/bootstrap.py,sha256=XAz832qSLFxIXW6SP02N8PQ_7CKiqrCfirkE80Iwarc,14881
304
305
  metaflow/plugins/pypi/conda_decorator.py,sha256=piFcE4uGmWhhbGlxMK0GHd7BGEyqy6r9BFy8Mjoi80Q,15937
305
306
  metaflow/plugins/pypi/conda_environment.py,sha256=d5BAiY_aJJdlJ5h3N5nGSDmVoOY-8BVKqEbA5nrCpCY,22113
306
307
  metaflow/plugins/pypi/micromamba.py,sha256=LLJ2dGGOEyld07W8iI6dtE01h2Y1PQnBhU-dMBssZ3c,16502
@@ -318,7 +319,7 @@ metaflow/runner/deployer_impl.py,sha256=Kab9rLoA3EiBJDtTTulhPCeKzqiljW366nx2Tm0L
318
319
  metaflow/runner/metaflow_runner.py,sha256=L302ew_BPBPs-NnW8n92dqqbqmHwrwGL5D6kTZvl5vY,16074
319
320
  metaflow/runner/nbdeploy.py,sha256=Sp5w-6nCZwjHaRBHWxi8udya-RYnJOB76KNLjB4L7Gs,4166
320
321
  metaflow/runner/nbrun.py,sha256=LhJu-Teoi7wTkNxg0kpNPVXFxH_9P4lvtp0ysMEIFJ8,7299
321
- metaflow/runner/subprocess_manager.py,sha256=K6uZXnqdgeW0vHUAVwoolSpDSLp1EVHiBtyD7f_vwac,22050
322
+ metaflow/runner/subprocess_manager.py,sha256=vqrOgRAtQYfTEBFFF7iRzrqbhFaVTRZohms4Kt7jxJA,22300
322
323
  metaflow/runner/utils.py,sha256=jC-Z5xzGEEa6Qc71U_5r1wHsS-qYV7-czv1BO-q2MSs,10213
323
324
  metaflow/sidecar/__init__.py,sha256=1mmNpmQ5puZCpRmmYlCOeieZ4108Su9XQ4_EqF1FGOU,131
324
325
  metaflow/sidecar/sidecar.py,sha256=EspKXvPPNiyRToaUZ51PS5TT_PzrBNAurn_wbFnmGr0,1334
@@ -360,9 +361,12 @@ metaflow/user_configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
360
361
  metaflow/user_configs/config_decorators.py,sha256=qCKVAvd0NKgaCxQ2OThes5-DYHXq6A1HqURubYNeFdw,20481
361
362
  metaflow/user_configs/config_options.py,sha256=m6jccSpzI4qUJ7vyYkYBIf8G3V0Caunxg_k7zg4Zlqg,21067
362
363
  metaflow/user_configs/config_parameters.py,sha256=oeJGVKu1ao_YQX6Lg6P2FEv5k5-_F4sARLlVpTW9ezM,15502
363
- metaflow-2.14.3.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
364
- metaflow-2.14.3.dist-info/METADATA,sha256=rHc2E2ro1WrUOXzDZvDnLOtEGk8tbknNlSo-GbpIwII,6121
365
- metaflow-2.14.3.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
366
- metaflow-2.14.3.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
367
- metaflow-2.14.3.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
368
- metaflow-2.14.3.dist-info/RECORD,,
364
+ metaflow-2.15.0.data/data/share/metaflow/devtools/Makefile,sha256=x9Q2FsScc9XQa0uVV2oNpA3VHwet_6oc8aQN0ztbM2Q,12907
365
+ metaflow-2.15.0.data/data/share/metaflow/devtools/Tiltfile,sha256=ednswaJXxyH4wRWPNQZMzb5Kg1TiukHUNXgUh_DP8mU,20016
366
+ metaflow-2.15.0.data/data/share/metaflow/devtools/pick_services.sh,sha256=DCnrMXwtApfx3B4S-YiZESMyAFHbXa3VuNL0MxPLyiE,2196
367
+ metaflow-2.15.0.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
368
+ metaflow-2.15.0.dist-info/METADATA,sha256=cvzxosgu6SxOBgmkE_UHfdyJEIkvadf54EsYe3npi8k,6118
369
+ metaflow-2.15.0.dist-info/WHEEL,sha256=3HfeesdN7jshHPzN8HJ8UeFRlEd6ApplqndzbPTvPl8,109
370
+ metaflow-2.15.0.dist-info/entry_points.txt,sha256=RvEq8VFlgGe_FfqGOZi0D7ze1hLD0pAtXeNyGfzc_Yc,103
371
+ metaflow-2.15.0.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
372
+ metaflow-2.15.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  metaflow = metaflow.cmd.main_cli:start
3
+ metaflow-dev = metaflow.cmd.make_wrapper:main