Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deployer): add more deployment metadata #943

Merged
merged 3 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cargo-shuttle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ portpicker = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
reqwest-middleware = "0.2.0"
reqwest-retry = "0.2.0"
rmp-serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-native-tls", "postgres"] }
Expand Down
18 changes: 6 additions & 12 deletions cargo-shuttle/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::fmt::Write;

use anyhow::{Context, Result};
use headers::{Authorization, HeaderMapExt};
use reqwest::Response;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder};
use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::RetryTransientMiddleware;
use serde::{Deserialize, Serialize};
use shuttle_common::models::deployment::DeploymentRequest;
use shuttle_common::models::{deployment, project, secret, service, ToJson};
use shuttle_common::project::ProjectName;
use shuttle_common::{resource, ApiKey, ApiUrl, LogItem};
Expand Down Expand Up @@ -36,29 +35,24 @@ impl Client {

pub async fn deploy(
&self,
data: Vec<u8>,
project: &ProjectName,
no_test: bool,
deployment_req: DeploymentRequest,
) -> Result<deployment::Response> {
let mut path = format!(
let path = format!(
"/projects/{}/services/{}",
project.as_str(),
project.as_str()
);

if no_test {
let _ = write!(path, "?no-test");
}
let deployment_req = rmp_serde::to_vec(&deployment_req)
.context("serialize DeploymentRequest as a MessagePack byte vector")?;

let url = format!("{}{}", self.api_url, path);

let mut builder = Self::get_retry_client().post(url);

builder = self.set_builder_auth(builder);

builder
.body(data)
.header("Transfer-Encoding", "chunked")
.body(deployment_req)
.send()
.await
.context("failed to send deployment to the Shuttle server")?
Expand Down
115 changes: 61 additions & 54 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ mod provisioner_server;
use args::LogoutArgs;
use indicatif::ProgressBar;
use shuttle_common::claims::{ClaimService, InjectPropagation};
use shuttle_common::models::deployment::get_deployments_table;
use shuttle_common::models::deployment::{
get_deployments_table, DeploymentRequest, GIT_STRINGS_MAX_LENGTH,
};
use shuttle_common::models::project::IDLE_MINUTES;
use shuttle_common::models::resource::get_resources_table;
use shuttle_common::project::ProjectName;
Expand Down Expand Up @@ -908,14 +910,45 @@ impl Shuttle {
}

async fn deploy(&self, client: &Client, args: DeployArgs) -> Result<CommandOutcome> {
if !args.allow_dirty {
self.is_dirty()?;
let working_directory = self.ctx.working_directory();

let mut deployment_req: DeploymentRequest = DeploymentRequest {
no_test: args.no_test,
..Default::default()
};

if let Ok(repo) = Repository::discover(working_directory) {
let repo_path = repo
.workdir()
.context("getting working directory of repository")?;
let repo_path = dunce::canonicalize(repo_path)?;
trace!(?repo_path, "found git repository");

if !args.allow_dirty {
self.is_dirty(&repo).context("dirty not allowed")?;
}
deployment_req.git_dirty = Some(self.is_dirty(&repo).is_err());

if let Ok(head) = repo.head() {
// This is typically the name of the current branch
// It is "HEAD" when head detached, for example when a tag is checked out
deployment_req.git_branch = head
.shorthand()
.map(|s| s.chars().take(GIT_STRINGS_MAX_LENGTH).collect());
if let Ok(commit) = head.peel_to_commit() {
deployment_req.git_commit_id = Some(commit.id().to_string());
// Summary is None if error or invalid utf-8
deployment_req.git_commit_msg = commit
.summary()
.map(|s| s.chars().take(GIT_STRINGS_MAX_LENGTH).collect());
}
}
}

let data = self.make_archive()?;
deployment_req.data = self.make_archive()?;

let deployment = client
.deploy(data, self.ctx.project_name(), args.no_test)
.deploy(self.ctx.project_name(), deployment_req)
.await?;

let mut stream = client
Expand Down Expand Up @@ -1197,61 +1230,35 @@ impl Shuttle {
Ok(bytes)
}

fn is_dirty(&self) -> Result<()> {
let working_directory = self.ctx.working_directory();
if let Ok(repo) = Repository::discover(working_directory) {
let repo_path = repo
.workdir()
.context("getting working directory of repository")?;

let repo_path = dunce::canonicalize(repo_path)?;

trace!(?repo_path, "found git repository");

let repo_rel_path = working_directory
.strip_prefix(repo_path.as_path())
.context("stripping repository path from working directory")?;

trace!(
?repo_rel_path,
"got working directory path relative to git repository"
fn is_dirty(&self, repo: &Repository) -> Result<()> {
let mut status_options = StatusOptions::new();
status_options.include_untracked(true);
let statuses = repo
.statuses(Some(&mut status_options))
.context("getting status of repository files")?;

if !statuses.is_empty() {
let mut error = format!(
"{} files in the working directory contain changes that were not yet committed into git:\n",
statuses.len()
);

let mut status_options = StatusOptions::new();
status_options
.pathspec(repo_rel_path)
.include_untracked(true);

let statuses = repo
.statuses(Some(&mut status_options))
.context("getting status of repository files")?;

if !statuses.is_empty() {
let mut error: String = format!("{} files in the working directory contain changes that were not yet committed into git:", statuses.len());
writeln!(error).expect("to append error");

for status in statuses.iter() {
trace!(
path = status.path(),
status = ?status.status(),
"found file with updates"
);
for status in statuses.iter() {
trace!(
path = status.path(),
status = ?status.status(),
"found file with updates"
);

let path =
repo_path.join(status.path().context("getting path of changed file")?);
let rel_path = path
.strip_prefix(working_directory)
.expect("getting relative path of changed file")
.display();
let rel_path = status.path().context("getting path of changed file")?;

writeln!(error, "{rel_path}").expect("to append error");
}
writeln!(error, "{rel_path}").expect("to append error");
}

writeln!(error).expect("to append error");
writeln!(error, "To proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag").expect("to append error");
writeln!(error).expect("to append error");
writeln!(error, "To proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag").expect("to append error");

bail!(error);
}
bail!(error);
}

Ok(())
Expand Down
21 changes: 16 additions & 5 deletions common/src/models/deployment.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
use std::{fmt::Display, str::FromStr};

use crate::deployment::State;
use chrono::{DateTime, Utc};
use comfy_table::{
modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, CellAlignment, Color,
ContentArrangement, Table,
};
use crossterm::style::Stylize;
use serde::{Deserialize, Serialize};

use std::{fmt::Display, str::FromStr};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use uuid::Uuid;

use crate::deployment::State;

#[derive(Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(as = shuttle_common::models::deployment::Response))]
Expand Down Expand Up @@ -115,3 +112,17 @@ Most recent {} for {}
)
}
}

#[derive(Default, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(as = shuttle_common::models::deployment::DeploymentRequest))]
pub struct DeploymentRequest {
hseeberger marked this conversation as resolved.
Show resolved Hide resolved
pub data: Vec<u8>,
pub no_test: bool,
pub git_commit_id: Option<String>,
pub git_commit_msg: Option<String>,
pub git_branch: Option<String>,
pub git_dirty: Option<bool>,
}

pub const GIT_STRINGS_MAX_LENGTH: usize = 80;
3 changes: 1 addition & 2 deletions common/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ use std::str::FromStr;
/// as per [IETF RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123).
/// Initially we'll implement a strict subset of the IETF RFC 1123, concretely:
/// - It does not start or end with `-` or `_`.
/// - It does not contain any characters outside of the alphanumeric range, except for `-` or '_'.
/// - It does not contain any characters outside of the alphanumeric range, except for `-` or `_`.
/// - It is not empty.
/// - It does not contain profanity.
/// - It is not a reserved word.
///
#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
pub struct ProjectName(String);

Expand Down
1 change: 1 addition & 0 deletions deployer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ opentelemetry = { workspace = true }
opentelemetry-http = { workspace = true }
pipe = { workspace = true }
portpicker = { workspace = true }
rmp-serde = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sqlx = { workspace = true, features = [
Expand Down
11 changes: 11 additions & 0 deletions deployer/migrations/0003_git.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER TABLE deployments
ADD COLUMN git_commit_id TEXT;

ALTER TABLE deployments
ADD COLUMN git_commit_msg TEXT;

ALTER TABLE deployments
ADD COLUMN git_branch TEXT;

ALTER TABLE deployments
ADD COLUMN git_dirty BOOLEAN;
Loading