Skip to content

Commit

Permalink
Merge pull request #7 from kate-goldenring/variables-command
Browse files Browse the repository at this point in the history
Add `--variables` deploy flag and `variables` subcommand
  • Loading branch information
kate-goldenring committed May 30, 2023
2 parents c9936cd + 495f1fb commit 7acb219
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 148 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spin plugin install --url https://github.com/fermyon/cloud-plugin/releases/downl
tar -czvf cloud.tar.gz cloud
sha256sum cloud.tar.gz
rm cloud
# Update cloud.json with shasum
# Outputs a shasum to add to cloud.json
```

1. Get the manifest.
Expand All @@ -27,7 +27,7 @@ spin plugin install --url https://github.com/fermyon/cloud-plugin/releases/downl
curl -LRO https://github.com/fermyon/cloud-plugin/releases/download/canary/cloud.json
```

1. Update the manifest to modify the `url` field to point to the path to local package (i.e. `"url": "file:///path/to/cloud-plugin/plugin/cloud.tar.gz"`).
1. Update the manifest to modify the `url` field to point to the path to local package (i.e. `"url": "file:///path/to/cloud-plugin/plugin/cloud.tar.gz"`) and update the shasum.

1. Install the plugin, pointing to the path to the manifest.

Expand Down
24 changes: 22 additions & 2 deletions crates/cloud/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ use cloud_openapi::{
device_codes_api::api_device_codes_post,
key_value_pairs_api::api_key_value_pairs_post,
revisions_api::{api_revisions_get, api_revisions_post},
variable_pairs_api::api_variable_pairs_post,
Error, ResponseContent,
},
models::{
AppItemPage, ChannelItem, ChannelItemPage, ChannelRevisionSelectionStrategy,
CreateAppCommand, CreateChannelCommand, CreateDeviceCodeCommand, CreateKeyValuePairCommand,
DeviceCodeItem, GetChannelLogsVm, RefreshTokenCommand, RegisterRevisionCommand,
RevisionItemPage, TokenInfo, UpdateEnvironmentVariableDto,
CreateVariablePairCommand, DeviceCodeItem, GetChannelLogsVm, RefreshTokenCommand,
RegisterRevisionCommand, RevisionItemPage, TokenInfo, UpdateEnvironmentVariableDto,
},
};
use reqwest::header;
Expand Down Expand Up @@ -333,6 +334,25 @@ impl Client {
.await
.map_err(format_response_error)
}

pub async fn add_variable_pair(
&self,
app_id: Uuid,
variable: String,
value: String,
) -> anyhow::Result<()> {
api_variable_pairs_post(
&self.configuration,
CreateVariablePairCommand {
app_id,
variable,
value,
},
None,
)
.await
.map_err(format_response_error)
}
}

#[derive(Deserialize, Debug)]
Expand Down
282 changes: 154 additions & 128 deletions src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ use std::{
use url::Url;
use uuid::Uuid;

use crate::{opts::*, parse_buildinfo};
use crate::{commands::variables::set_variables, opts::*, parse_buildinfo};

use super::login::{LoginCommand, LoginConnection};

const SPIN_DEPLOY_CHANNEL_NAME: &str = "spin-deploy";
const SPIN_DEFAULT_KV_STORE: &str = "default";
const BINDLE_REGISTRY_URL_PATH: &str = "api/registry";

/// Package and upload an application to the Fermyon Platform.
/// Package and upload an application to the Fermyon Cloud.
#[derive(Parser, Debug)]
#[clap(about = "Package and upload an application to the Fermyon Platform")]
#[clap(about = "Package and upload an application to the Fermyon Cloud")]
pub struct DeployCommand {
/// The application to deploy. This may be a manifest (spin.toml) file, or a
/// directory containing a spin.toml file.
Expand Down Expand Up @@ -105,118 +105,17 @@ pub struct DeployCommand {
/// Can be used multiple times.
#[clap(long = "key-value", parse(try_from_str = parse_kv))]
pub key_values: Vec<(String, String)>,

/// Set a variable pair (variable=value) in the deployed application.
/// Any existing value will be overwritten.
/// Can be used multiple times.
#[clap(long = "variable", parse(try_from_str = parse_kv))]
pub variables: Vec<(String, String)>,
}

impl DeployCommand {
pub async fn run(self) -> Result<()> {
let path = self.config_file_path()?;

// log in if config.json does not exist or cannot be read
let data = match fs::read_to_string(path.clone()).await {
Ok(d) => d,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
match self.deployment_env_id {
Some(name) => {
// TODO: allow auto redirect to login preserving the name
eprintln!("You have no instance saved as '{}'", name);
eprintln!("Run `spin login --environment-name {}` to log in", name);
std::process::exit(1);
}
None => {
// log in, then read config
// TODO: propagate deployment id (or bail if nondefault?)
LoginCommand::parse_from(vec!["login"]).run().await?;
fs::read_to_string(path.clone()).await?
}
}
}
Err(e) => {
bail!("Could not log in: {}", e);
}
};

let mut login_connection: LoginConnection = serde_json::from_str(&data)?;
let expired = match has_expired(&login_connection) {
Ok(val) => val,
Err(err) => {
eprintln!("{}\n", err);
eprintln!("Run `spin login` to log in again");
std::process::exit(1);
}
};

if expired {
// if we have a refresh token available, let's try to refresh the token
match login_connection.refresh_token {
Some(refresh_token) => {
// Only Cloud has support for refresh tokens
let connection_config = ConnectionConfig {
url: login_connection.url.to_string(),
insecure: login_connection.danger_accept_invalid_certs,
token: login_connection.token.clone(),
};
let client = CloudClient::new(connection_config.clone());

match client
.refresh_token(login_connection.token, refresh_token)
.await
{
Ok(token_info) => {
login_connection.token = token_info.token;
login_connection.refresh_token = Some(token_info.refresh_token);
login_connection.expiration = Some(token_info.expiration);
// save new token info
let path = self.config_file_path()?;
std::fs::write(path, serde_json::to_string_pretty(&login_connection)?)?;
}
Err(e) => {
eprintln!("Failed to refresh token: {}", e);
match self.deployment_env_id {
Some(name) => {
eprintln!(
"Run `spin login --environment-name {}` to log in again",
name
);
}
None => {
eprintln!("Run `spin login` to log in again");
}
}
std::process::exit(1);
}
}
}
None => {
// session has expired and we have no way to refresh the token - log back in
match self.deployment_env_id {
Some(name) => {
// TODO: allow auto redirect to login preserving the name
eprintln!("Your login to this environment has expired");
eprintln!(
"Run `spin login --environment-name {}` to log in again",
name
);
std::process::exit(1);
}
None => {
LoginCommand::parse_from(vec!["login"]).run().await?;
let new_data = fs::read_to_string(path.clone()).await.context(
format!("Cannot find spin config at {}", path.to_string_lossy()),
)?;
login_connection = serde_json::from_str(&new_data)?;
}
}
}
}
}

let sloth_guard = sloth::warn_if_slothful(
2500,
format!("Checking status ({})\n", login_connection.url),
);
check_healthz(&login_connection.url).await?;
// Server has responded - we don't want to keep the sloth timer running.
drop(sloth_guard);
let login_connection = login_connection(self.deployment_env_id.as_deref()).await?;

const DEVELOPER_CLOUD_FAQ: &str = "https://developer.fermyon.com/cloud/faq";

Expand All @@ -229,23 +128,6 @@ impl DeployCommand {
crate::manifest::resolve_file_path(&self.app_source)
}

// TODO: unify with login
fn config_file_path(&self) -> Result<PathBuf> {
let root = dirs::config_dir()
.context("Cannot find configuration directory")?
.join("fermyon");

let file_stem = match &self.deployment_env_id {
None => "config",
Some(id) => id,
};
let file = format!("{}.json", file_stem);

let path = root.join(file);

Ok(path)
}

async fn deploy_cloud(self, login_connection: LoginConnection) -> Result<()> {
let connection_config = ConnectionConfig {
url: login_connection.url.to_string(),
Expand Down Expand Up @@ -332,6 +214,8 @@ impl DeployCommand {
.context("Problem creating key/value")?;
}

set_variables(&client, app_id, &self.variables).await?;

existing_channel_id
}
Err(_) => {
Expand Down Expand Up @@ -369,6 +253,8 @@ impl DeployCommand {
.context("Problem creating key/value")?;
}

set_variables(&client, app_id, &self.variables).await?;

channel_id
}
};
Expand Down Expand Up @@ -715,3 +601,143 @@ fn has_expired(login_connection: &LoginConnection) -> Result<bool> {
None => Ok(false),
}
}

pub async fn login_connection(deployment_env_id: Option<&str>) -> Result<LoginConnection> {
let path = config_file_path(deployment_env_id)?;

// log in if config.json does not exist or cannot be read
let data = match fs::read_to_string(path.clone()).await {
Ok(d) => d,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
match deployment_env_id {
Some(name) => {
// TODO: allow auto redirect to login preserving the name
eprintln!("You have no instance saved as '{}'", name);
eprintln!("Run `spin login --environment-name {}` to log in", name);
std::process::exit(1);
}
None => {
// log in, then read config
// TODO: propagate deployment id (or bail if nondefault?)
LoginCommand::parse_from(vec!["login"]).run().await?;
fs::read_to_string(path.clone()).await?
}
}
}
Err(e) => {
bail!("Could not log in: {}", e);
}
};

let mut login_connection: LoginConnection = serde_json::from_str(&data)?;
let expired = match has_expired(&login_connection) {
Ok(val) => val,
Err(err) => {
eprintln!("{}\n", err);
eprintln!("Run `spin login` to log in again");
std::process::exit(1);
}
};

if expired {
// if we have a refresh token available, let's try to refresh the token
match login_connection.refresh_token {
Some(refresh_token) => {
// Only Cloud has support for refresh tokens
let connection_config = ConnectionConfig {
url: login_connection.url.to_string(),
insecure: login_connection.danger_accept_invalid_certs,
token: login_connection.token.clone(),
};
let client = CloudClient::new(connection_config.clone());

match client
.refresh_token(login_connection.token, refresh_token)
.await
{
Ok(token_info) => {
login_connection.token = token_info.token;
login_connection.refresh_token = Some(token_info.refresh_token);
login_connection.expiration = Some(token_info.expiration);
// save new token info
let path = config_file_path(deployment_env_id)?;
std::fs::write(path, serde_json::to_string_pretty(&login_connection)?)?;
}
Err(e) => {
eprintln!("Failed to refresh token: {}", e);
match deployment_env_id {
Some(name) => {
eprintln!(
"Run `spin login --environment-name {}` to log in again",
name
);
}
None => {
eprintln!("Run `spin login` to log in again");
}
}
std::process::exit(1);
}
}
}
None => {
// session has expired and we have no way to refresh the token - log back in
match deployment_env_id {
Some(name) => {
// TODO: allow auto redirect to login preserving the name
eprintln!("Your login to this environment has expired");
eprintln!(
"Run `spin login --environment-name {}` to log in again",
name
);
std::process::exit(1);
}
None => {
LoginCommand::parse_from(vec!["login"]).run().await?;
let new_data = fs::read_to_string(path.clone()).await.context(format!(
"Cannot find spin config at {}",
path.to_string_lossy()
))?;
login_connection = serde_json::from_str(&new_data)?;
}
}
}
}
}

let sloth_guard = sloth::warn_if_slothful(
2500,
format!("Checking status ({})\n", login_connection.url),
);
check_healthz(&login_connection.url).await?;
// Server has responded - we don't want to keep the sloth timer running.
drop(sloth_guard);

Ok(login_connection)
}

pub async fn get_app_id_cloud(cloud_client: &CloudClient, name: String) -> Result<Uuid> {
let apps_vm = CloudClient::list_apps(cloud_client).await?;
let app = apps_vm.items.iter().find(|&x| x.name == name.clone());
match app {
Some(a) => Ok(a.id),
None => bail!("No app with name: {}", name),
}
}

// TODO: unify with login
pub fn config_file_path(deployment_env_id: Option<&str>) -> Result<PathBuf> {
let root = dirs::config_dir()
.context("Cannot find configuration directory")?
.join("fermyon");

let file_stem = match deployment_env_id {
None => "config",
Some(id) => id,
};
let file = format!("{}.json", file_stem);

let path = root.join(file);

Ok(path)
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod deploy;
pub mod login;
pub mod variables;
Loading

0 comments on commit 7acb219

Please sign in to comment.