Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
c0bdf412
Unverified
Commit
c0bdf412
authored
Apr 24, 2025
by
hhzhang16
Committed by
GitHub
Apr 24, 2025
Browse files
feat: improve dynamo deployment CLI (#798)
Co-authored-by:
Julien Mancuso
<
jmancuso@nvidia.com
>
parent
4761baa6
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
135 additions
and
43 deletions
+135
-43
deploy/dynamo/api-store/ai_dynamo_store/api/deployments.py
deploy/dynamo/api-store/ai_dynamo_store/api/deployments.py
+41
-9
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
+2
-4
deploy/dynamo/sdk/src/dynamo/sdk/cli/deployment.py
deploy/dynamo/sdk/src/dynamo/sdk/cli/deployment.py
+91
-29
examples/hello_world/README.md
examples/hello_world/README.md
+1
-1
No files found.
deploy/dynamo/api-store/ai_dynamo_store/api/deployments.py
View file @
c0bdf412
...
...
@@ -112,7 +112,7 @@ async def create_deployment(deployment: CreateDeploymentSchema):
deployment_schema
=
DeploymentFullSchema
(
**
resource
.
dict
(),
status
=
"
runn
ing"
,
status
=
"
deploy
ing"
,
kube_namespace
=
kube_namespace
,
creator
=
creator
,
cluster
=
cluster
,
...
...
@@ -159,21 +159,53 @@ def get_deployment(name: str) -> DeploymentFullSchema:
raise
HTTPException
(
status_code
=
500
,
detail
=
str
(
e
))
# function to look for a condition with type "Ready" in the status of the deployment
# and return the "message" field
def
get_deployment_status
(
resource
:
Dict
[
str
,
Any
])
->
str
:
# look for a condition with type "Ready" in the status of the deployment
for
condition
in
resource
.
get
(
"status"
,
{}).
get
(
"conditions"
,
[]):
"""
Get the current status of a deployment.
Maps operator status to BentoML status values.
Returns lowercase status values matching BentoML's DeploymentStatus enum.
"""
status
=
resource
.
get
(
"status"
,
{})
conditions
=
status
.
get
(
"conditions"
,
[])
state
=
status
.
get
(
"state"
,
""
)
# First check Ready condition
for
condition
in
conditions
:
if
condition
.
get
(
"type"
)
==
"Ready"
:
return
condition
.
get
(
"message"
,
"unknown"
)
if
condition
.
get
(
"status"
)
==
"True"
:
# If state is "successful", map to "running"
if
state
==
"successful"
:
return
"running"
return
condition
.
get
(
"message"
,
"running"
).
lower
()
elif
condition
.
get
(
"message"
):
return
condition
.
get
(
"message"
).
lower
()
# If no Ready condition or not True, check state
if
state
==
"failed"
:
return
"failed"
elif
state
==
"pending"
:
return
"deploying"
# map pending to deploying to match BentoML states
# Default fallback
return
"unknown"
def
get_urls
(
resource
:
Dict
[
str
,
Any
])
->
List
[
str
]:
"""
Get the URLs for a deployment.
Returns URLs as soon as they are available from EndpointExposed condition.
"""
urls
=
[]
for
condition
in
resource
.
get
(
"status"
,
{}).
get
(
"conditions"
,
[]):
if
condition
.
get
(
"type"
)
==
"EndpointExposed"
:
urls
.
append
(
condition
.
get
(
"message"
))
conditions
=
resource
.
get
(
"status"
,
{}).
get
(
"conditions"
,
[])
# Check for EndpointExposed condition
for
condition
in
conditions
:
if
(
condition
.
get
(
"type"
)
==
"EndpointExposed"
and
condition
.
get
(
"status"
)
==
"True"
):
if
message
:
=
condition
.
get
(
"message"
):
urls
.
append
(
message
)
return
urls
...
...
deploy/dynamo/sdk/src/dynamo/sdk/cli/cli.py
View file @
c0bdf412
...
...
@@ -32,10 +32,8 @@ def create_bentoml_cli() -> click.Command:
from
dynamo.sdk.cli.bentos
import
bento_command
from
dynamo.sdk.cli.cloud
import
cloud_command
from
dynamo.sdk.cli.deployment
import
deployment_command
from
dynamo.sdk.cli.deployment
import
deploy_command
,
deployment_command
from
dynamo.sdk.cli.env
import
env_command
# from dynamo.sdk.cli.deploy import deploy_command
from
dynamo.sdk.cli.run
import
run_command
from
dynamo.sdk.cli.serve
import
serve_command
from
dynamo.sdk.cli.utils
import
DynamoCommandGroup
...
...
@@ -63,7 +61,7 @@ def create_bentoml_cli() -> click.Command:
bentoml_cli
.
add_single_command
(
bento_command
,
"get"
)
bentoml_cli
.
add_subcommands
(
serve_command
)
bentoml_cli
.
add_subcommands
(
run_command
)
#
bentoml_cli.add_command(deploy_command)
bentoml_cli
.
add_command
(
deploy_command
)
# bentoml_cli.add_command(containerize_command)
bentoml_cli
.
add_command
(
deployment_command
)
bentoml_cli
.
add_command
(
env_command
)
...
...
deploy/dynamo/sdk/src/dynamo/sdk/cli/deployment.py
View file @
c0bdf412
...
...
@@ -37,6 +37,9 @@ from dynamo.sdk.lib.logging import configure_server_logging
from
.utils
import
resolve_service_config
# Configure logging to suppress INFO HTTP logs
logging
.
getLogger
(
"httpx"
).
setLevel
(
logging
.
WARNING
)
# HTTP client library logs
logging
.
getLogger
(
"httpcore"
).
setLevel
(
logging
.
WARNING
)
# HTTP core library logs
configure_server_logging
()
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -264,7 +267,6 @@ def create_deployment(
)
->
Deployment
:
# Load config from file and serialize to env
service_configs
=
resolve_service_config
(
config_file
=
config_file
,
args
=
args
)
print
(
f
"service_configs:
{
service_configs
}
"
)
env_dicts
=
[]
if
service_configs
:
config_json
=
json
.
dumps
(
service_configs
)
...
...
@@ -288,39 +290,80 @@ def create_deployment(
console
=
Console
(
highlight
=
False
)
with
Spinner
(
console
=
console
)
as
spinner
:
spinner
.
update
(
"Creating deployment on Dynamo Cloud"
)
try
:
# Create deployment with initial status message
spinner
.
update
(
"Creating deployment on Dynamo Cloud..."
)
deployment
=
_cloud_client
.
deployment
.
create
(
deployment_config_params
=
config_params
)
deployment
.
admin_console
=
_get_urls
(
deployment
)
# remove dashboard url
spinner
.
log
(
f
':white_check_mark: Created deployment "
{
deployment
.
name
}
" in cluster "
{
deployment
.
cluster
}
"'
)
if
wait
:
spinner
.
update
(
"[bold blue]Waiting for deployment to be ready, you can use --no-wait to skip this process[/]"
,
# Update spinner text for waiting phase
spinner
.
log
(
"[bold blue]Waiting for deployment to be ready, you can use --no-wait to skip this process[/]"
)
retcode
=
deployment
.
wait_until_ready
(
timeout
=
timeout
,
spinner
=
spinner
)
if
retcode
!=
0
:
sys
.
exit
(
retcode
)
_display_deployment_info
(
spinner
,
deployment
)
return
deployment
except
BentoMLException
as
e
:
error_msg
=
str
(
e
)
if
"already exists"
in
error_msg
:
# Extract deployment name from error message and clean it
match
=
re
.
search
(
r
'"([^"]+?)(?:\\+)?" already exists'
,
error_msg
)
dep_name
=
match
.
group
(
1
).
rstrip
(
"
\\
"
)
if
match
else
name
error_msg
=
(
f
'Error: Deployment "
{
dep_name
}
" already exists. To create a new deployment:
\n
'
f
"1. Use a different name with the --name flag
\n
"
f
"2. Or delete the existing deployment with: dynamo deployment delete
{
dep_name
}
"
spinner
.
log
(
"[red]:x: Error:[/] "
f
'Deployment "
{
dep_name
}
" already exists. To create a new deployment:
\n
'
" 1. Use a different name with the --name flag
\n
"
f
" 2. Or delete the existing deployment with: dynamo deployment delete
{
dep_name
}
"
)
print
(
error_msg
)
sys
.
exit
(
1
)
print
(
f
"
Error:
{
str
(
e
)
}
"
)
spinner
.
log
(
f
"[red]:x:
Error:
[/]
{
str
(
e
)
}
"
)
sys
.
exit
(
1
)
def
_get_urls
(
deployment
:
Deployment
)
->
list
[
str
]:
"""Get URLs from deployment."""
latest
=
deployment
.
_client
.
v2
.
get_deployment
(
deployment
.
name
,
deployment
.
cluster
)
urls
=
latest
.
urls
if
hasattr
(
latest
,
"urls"
)
else
None
return
urls
if
urls
is
not
None
else
[]
def
_display_deployment_info
(
spinner
:
Spinner
,
deployment
:
Deployment
)
->
None
:
"""Helper function to display deployment status and URLs consistently."""
# Get status directly from schema and escape any Rich markup
status
=
deployment
.
_schema
.
status
if
deployment
.
_schema
.
status
else
"unknown"
# Escape any characters that are interpreted as markup
reformatted_status
=
status
.
replace
(
"["
,
"
\\
["
)
spinner
.
log
(
f
"[bold]Status:[/]
{
reformatted_status
}
"
)
# Get URLs directly from schema
spinner
.
log
(
"[bold]Ingress URLs:[/]"
)
try
:
# Get latest deployment info for URLs
urls
=
_get_urls
(
deployment
)
if
urls
:
for
url
in
urls
:
spinner
.
log
(
f
" -
{
url
}
"
)
else
:
spinner
.
log
(
" No URLs available"
)
except
Exception
:
# If refresh fails, fall back to existing URLs
if
deployment
.
_urls
:
for
url
in
deployment
.
_urls
:
spinner
.
log
(
f
" -
{
url
}
"
)
else
:
spinner
.
log
(
" No URLs available"
)
@
inject
def
get_deployment
(
name
:
str
,
...
...
@@ -330,19 +373,27 @@ def get_deployment(
"""Get deployment details from Dynamo Cloud."""
console
=
Console
(
highlight
=
False
)
with
Spinner
(
console
=
console
)
as
spinner
:
spinner
.
update
(
f
'Getting deployment "
{
name
}
" from Dynamo Cloud'
)
try
:
spinner
.
update
(
f
'Getting deployment "
{
name
}
" from Dynamo Cloud...'
)
deployment
=
_cloud_client
.
deployment
.
get
(
name
=
name
,
cluster
=
cluster
)
spinner
.
log
(
f
':white_check_mark: Found deployment "
{
deployment
.
name
}
" in cluster "
{
deployment
.
cluster
}
"'
)
_display_deployment_info
(
spinner
,
deployment
)
return
deployment
except
BentoMLException
as
e
:
if
"No cloud context default found"
in
str
(
e
):
raise
BentoMLException
(
"Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
from
None
raise_deployment_config_error
(
e
,
"get"
)
error_msg
=
str
(
e
)
if
"No cloud context default found"
in
error_msg
:
spinner
.
log
(
"[red]:x: Error:[/] Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
sys
.
exit
(
1
)
if
"404 Not Found"
in
error_msg
or
"Deployment not found"
in
error_msg
:
cluster_msg
=
f
" in cluster
{
cluster
}
"
if
cluster
else
""
spinner
.
log
(
f
"[red]:x: Deployment '
{
name
}
' not found
{
cluster_msg
}
"
)
sys
.
exit
(
1
)
spinner
.
log
(
f
"[red]:x: Error:[/] Failed to get deployment:
{
error_msg
}
"
)
sys
.
exit
(
1
)
@
inject
...
...
@@ -354,16 +405,23 @@ def delete_deployment(
"""Delete a deployment from Dynamo Cloud."""
console
=
Console
(
highlight
=
False
)
with
Spinner
(
console
=
console
)
as
spinner
:
spinner
.
update
(
f
'Deleting deployment "
{
name
}
" from Dynamo Cloud'
)
try
:
spinner
.
update
(
f
'Deleting deployment "
{
name
}
" from Dynamo Cloud...'
)
_cloud_client
.
deployment
.
delete
(
name
=
name
,
cluster
=
cluster
)
spinner
.
log
(
f
':white_check_mark:
D
eleted deployment "
{
name
}
"'
)
spinner
.
log
(
f
':white_check_mark:
Successfully d
eleted deployment "
{
name
}
"'
)
except
BentoMLException
as
e
:
if
"No cloud context default found"
in
str
(
e
):
raise
BentoMLException
(
"Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
from
None
raise_deployment_config_error
(
e
,
"delete"
)
error_msg
=
str
(
e
)
if
"No cloud context default found"
in
error_msg
:
spinner
.
log
(
"[red]:x: Error:[/] Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
sys
.
exit
(
1
)
if
"404 Not Found"
in
error_msg
or
"Deployment not found"
in
error_msg
:
cluster_msg
=
f
" in cluster
{
cluster
}
"
if
cluster
else
""
spinner
.
log
(
f
"[red]:x: Deployment '
{
name
}
' not found
{
cluster_msg
}
"
)
sys
.
exit
(
1
)
spinner
.
log
(
f
"[red]:x: Error:[/]
{
error_msg
}
"
)
sys
.
exit
(
1
)
@
inject
...
...
@@ -378,7 +436,6 @@ def list_deployments(
"""List all deployments from Dynamo Cloud."""
console
=
Console
(
highlight
=
False
)
with
Spinner
(
console
=
console
)
as
spinner
:
spinner
.
update
(
"Getting all deployments from Dynamo Cloud"
)
try
:
# Handle label-based filtering
if
labels
is
not
None
:
...
...
@@ -388,6 +445,8 @@ def list_deployments(
else
:
q
=
label_query
spinner
.
update
(
"Getting deployments from Dynamo Cloud..."
)
# Get all deployments in a single call by setting count=1000
deployments
=
_cloud_client
.
deployment
.
list
(
cluster
=
cluster
,
search
=
search
,
dev
=
dev
,
q
=
q
)
...
...
@@ -398,10 +457,13 @@ def list_deployments(
spinner
.
log
(
":white_check_mark: Found deployments:"
)
for
deployment
in
deployments
:
spinner
.
log
(
f
" •
{
deployment
.
name
}
(cluster:
{
deployment
.
cluster
}
)"
)
spinner
.
log
(
f
"
\n
•
{
deployment
.
name
}
(cluster:
{
deployment
.
cluster
}
)"
)
_display_deployment_info
(
spinner
,
deployment
)
except
BentoMLException
as
e
:
if
"No cloud context default found"
in
str
(
e
):
raise
BentoMLException
(
"Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
from
None
raise_deployment_config_error
(
e
,
"list"
)
spinner
.
log
(
"[red]:x: Error:[/] Not logged in to Dynamo Cloud. Please run 'dynamo cloud login' first."
)
sys
.
exit
(
1
)
spinner
.
log
(
f
"[red]:x: Error:[/] Failed to list deployments:
{
str
(
e
)
}
"
)
sys
.
exit
(
1
)
examples/hello_world/README.md
View file @
c0bdf412
...
...
@@ -164,7 +164,7 @@ DYNAMO_TAG=$(dynamo build hello_world:Frontend | grep "Successfully built" | awk
```
bash
echo
$DYNAMO_TAG
export
HELM_RELEASE
=
ci-hw
dynamo deployment create
$DYNAMO_TAG
--no-wait
-n
$HELM_RELEASE
dynamo deployment create
$DYNAMO_TAG
-n
$HELM_RELEASE
```
4.
**Test the deployment**
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment