Skip to main content

mz_deploy/cli/commands/
describe.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Describe command - show detailed information about a specific deployment.
11
12use crate::cli::CliError;
13use crate::client::{Client, DeploymentDetails, DeploymentKind};
14use crate::config::Settings;
15use crate::log;
16use crate::project::ir::object_id::ObjectId;
17use chrono::{DateTime, Local};
18use owo_colors::{OwoColorize, Stream, Style};
19use std::collections::BTreeMap;
20use std::fmt;
21
22#[derive(serde::Serialize)]
23struct DescribeOutput {
24    deploy_id: String,
25    details: DeploymentDetails,
26    objects: BTreeMap<ObjectId, String>,
27}
28
29impl fmt::Display for DescribeOutput {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        let deployment_style = Style::new().yellow().bold();
32        writeln!(
33            f,
34            "{} {} [{}]",
35            "deployment".if_supports_color(Stream::Stderr, |t| deployment_style.style(t)),
36            self.deploy_id
37                .if_supports_color(Stream::Stderr, |t| t.cyan()),
38            self.details
39                .kind
40                .to_string()
41                .if_supports_color(Stream::Stderr, |t| t.dimmed()),
42        )?;
43
44        if let Some(commit_sha) = &self.details.git_commit {
45            writeln!(
46                f,
47                "{}: {}",
48                "Commit".if_supports_color(Stream::Stderr, |t| t.dimmed()),
49                commit_sha
50            )?;
51        }
52
53        writeln!(
54            f,
55            "{}: {}",
56            "Deployed by".if_supports_color(Stream::Stderr, |t| t.dimmed()),
57            self.details
58                .deployed_by
59                .if_supports_color(Stream::Stderr, |t| t.yellow())
60        )?;
61
62        let deployed_datetime: DateTime<Local> = self.details.deployed_at.with_timezone(&Local);
63        let deployed_str = deployed_datetime
64            .format("%a %b %d %H:%M:%S %Y %z")
65            .to_string();
66        writeln!(
67            f,
68            "{}: {}",
69            "Deployed at".if_supports_color(Stream::Stderr, |t| t.dimmed()),
70            deployed_str
71        )?;
72
73        if let Some(promoted) = self.details.promoted_at {
74            if self.details.kind == DeploymentKind::Objects {
75                let promoted_datetime: DateTime<Local> = promoted.with_timezone(&Local);
76                let promoted_str = promoted_datetime
77                    .format("%a %b %d %H:%M:%S %Y %z")
78                    .to_string();
79                writeln!(
80                    f,
81                    "{}: {}",
82                    "Promoted at".if_supports_color(Stream::Stderr, |t| t.dimmed()),
83                    promoted_str
84                )?;
85            }
86        } else {
87            writeln!(
88                f,
89                "{}: {}",
90                "Status".if_supports_color(Stream::Stderr, |t| t.dimmed()),
91                "staging".if_supports_color(Stream::Stderr, |t| t.yellow())
92            )?;
93        }
94
95        writeln!(f)?;
96
97        // Display schemas
98        writeln!(
99            f,
100            "{} ({}):",
101            "Schemas".if_supports_color(Stream::Stderr, |t| t.bold()),
102            self.details.schemas.len()
103        )?;
104        for sq in &self.details.schemas {
105            writeln!(
106                f,
107                "    {}.{}",
108                sq.database
109                    .if_supports_color(Stream::Stderr, |t| t.dimmed()),
110                sq.schema
111            )?;
112        }
113        writeln!(f)?;
114
115        // Display objects
116        writeln!(
117            f,
118            "{} ({}):",
119            "Objects".if_supports_color(Stream::Stderr, |t| t.bold()),
120            self.objects.len()
121        )?;
122        for (object_id, hash) in &self.objects {
123            let short_hash = &hash[..hash.len().min(12)];
124            writeln!(
125                f,
126                "    {}  {}",
127                object_id
128                    .to_string()
129                    .if_supports_color(Stream::Stderr, |t| t.dimmed()),
130                short_hash.if_supports_color(Stream::Stderr, |t| t.dimmed())
131            )?;
132        }
133
134        Ok(())
135    }
136}
137
138/// Show detailed information about a specific deployment.
139///
140/// This command:
141/// - Queries deployment metadata (when deployed, by whom, git commit, etc.)
142/// - Lists all objects included in the deployment with their hashes
143///
144/// Use `mz-deploy history` to see a list of deployment IDs, then use this
145/// command to drill into a specific deployment's details.
146///
147/// # Arguments
148/// * `settings` - Resolved CLI settings (profile, project directory, etc.)
149/// * `deploy_id` - The deployment ID to describe
150///
151/// # Returns
152/// Ok(()) if the deployment is found and displayed
153///
154/// # Errors
155/// Returns `CliError::Connection` for database errors
156/// Returns `CliError::Message` if deployment is not found
157pub async fn run(settings: &Settings, deploy_id: &str) -> Result<(), CliError> {
158    let profile = settings.connection();
159    let client = Client::connect_with_profile(profile.clone())
160        .await
161        .map_err(CliError::Connection)?;
162
163    super::setup::verify(&client, settings.emulator()).await?;
164    super::setup::validate_connection(&client, settings.emulator()).await?;
165
166    // Get deployment metadata
167    let details = client
168        .deployments()
169        .get_deployment_details(deploy_id)
170        .await?;
171    let Some(details) = details else {
172        return Err(CliError::Message(format!(
173            "Deployment '{}' not found",
174            deploy_id
175        )));
176    };
177
178    // Get deployment objects
179    let snapshot = client
180        .deployments()
181        .get_deployment_objects(Some(deploy_id))
182        .await?;
183
184    let output = DescribeOutput {
185        deploy_id: deploy_id.to_string(),
186        details,
187        objects: snapshot.objects,
188    };
189    log::output(&output);
190
191    Ok(())
192}