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

pubsys: add SSM parameter publishing #1060

Merged
merged 1 commit into from
Aug 24, 2020
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
79 changes: 76 additions & 3 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ PUBLISH_KEY = "default"
# AMIs. (You can also specify PUBLISH_ROOT_VOLUME_SIZE to override the root
# volume size; by default it's the image size, rounded up.)
PUBLISH_DATA_VOLUME_SIZE = "20"
# You can also set PUBLISH_REGIONS to override the list of regions for AMIs
# from Infra.toml; it's a comma-separated list like "us-west-2,us-east-1".
# You can also set NO_PROGRESS to not print progress bars during snapshot upload.
# This can be overridden with -e to change the path to the directory containing
# SSM parameter template files. These files determine the parameter names and
# values that will be published to SSM when you run `cargo make ssm`.
PUBLISH_SSM_TEMPLATES_PATH = "${BUILDSYS_ROOT_DIR}/tools/pubsys/policies/ssm"

# You can also set PUBLISH_REGIONS to override the list of regions from
# Infra.toml for AMI and SSM commands; it's a comma-separated list like
# "us-west-2,us-east-1".
# You can set NO_PROGRESS=true to not print progress bars during snapshot upload.
# You can use ALLOW_CLOBBER=true with the `ssm` task to make it overwrite existing values.
# (This is not required with `promote-ssm` because the intent of promotion is overwriting.)

# Disallow pulling directly Upstream URLs when lookaside cache results in MISSes as a fallback.
# To use the upstream source as fallback, override this on the command line and set it to 'true'
Expand Down Expand Up @@ -472,6 +480,71 @@ pubsys \
'''
]

[tasks.ssm]
# Rather than depend on "build", which currently rebuilds images each run, we
# depend on publish-tools and check for the input file below to save time.
# This does mean that `cargo make ami` must be run before `cargo make ssm`.
dependencies = ["publish-tools"]
script_runner = "bash"
script = [
'''
set -e

export PATH="${BUILDSYS_TOOLS_DIR}/bin:${PATH}"

ami_input="${BUILDSYS_OUTPUT_DIR}/${BUILDSYS_NAME_FULL}-amis.json"
if [ ! -s "${ami_input}" ]; then
echo "AMI input file doesn't exist for the current version/commit - ${BUILDSYS_VERSION_FULL} - please run 'cargo make ami'" >&2
exit 1
fi

pubsys \
--infra-config-path "${PUBLISH_INFRA_CONFIG_PATH}" \
\
ssm \
\
--ami-input "${ami_input}" \
--arch "${BUILDSYS_ARCH}" \
--variant "${BUILDSYS_VARIANT}" \
--version "${BUILDSYS_VERSION_FULL}" \
--template-dir "${PUBLISH_SSM_TEMPLATES_PATH}" \
\
${PUBLISH_REGIONS:+--regions "${PUBLISH_REGIONS}"} \
${ALLOW_CLOBBER:+--allow-clobber}
'''
]

[tasks.promote-ssm]
dependencies = ["publish-tools"]
script_runner = "bash"
script = [
'''
set -e

export PATH="${BUILDSYS_TOOLS_DIR}/bin:${PATH}"

source="${SSM_SOURCE:-${BUILDSYS_VERSION_FULL}}"
target="${SSM_TARGET}"
if [ -z "${target}" ]; then
echo "SSM_TARGET is mandatory for promote-ssm; please give the version (or pointer like "latest") to which you want to promote ${source}" >&2
exit 1
fi

pubsys \
--infra-config-path "${PUBLISH_INFRA_CONFIG_PATH}" \
\
promote-ssm \
\
--arch "${BUILDSYS_ARCH}" \
--variant "${BUILDSYS_VARIANT}" \
--source "${source}" \
--target "${target}" \
--template-dir "${PUBLISH_SSM_TEMPLATES_PATH}" \
\
${PUBLISH_REGIONS:+--regions "${PUBLISH_REGIONS}"}
'''
]

[tasks.clean]
script_runner = "bash"
script = [
Expand Down
28 changes: 27 additions & 1 deletion tools/Cargo.lock

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

4 changes: 3 additions & 1 deletion tools/pubsys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ rusoto_credential = "0.45.0"
rusoto_ebs = "0.45.0"
rusoto_ec2 = "0.45.0"
rusoto_signature = "0.45.0"
rusoto_ssm = "0.45.0"
rusoto_sts = "0.45.0"
simplelog = "0.8"
snafu = "0.6"
semver = "0.10.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
structopt = { version = "0.3", default-features = false }
tokio = "0.2.21"
tinytemplate = "1.1"
tokio = { version = "0.2.21", features = ["time"] }
toml = "0.5"
tough = { version = "0.8", features = ["http"] }
tough-ssm = "0.3"
Expand Down
2 changes: 2 additions & 0 deletions tools/pubsys/Infra.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ regions = ["us-west-2", "us-east-1", "us-east-2"]
profile = "my-profile"
# If specified, we assume this role before making any API calls.
role = "arn:aws:iam::012345678901:role/assume-global"
# If specified, this string will be prefixed on all parameter names published to SSM.
ssm_prefix = "/your/prefix/here"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path to the ssm policy is not specified in Infra.toml?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, though we could consider moving it, and there's also some separate discussion about combining some of our configuration sources...

The way we were thinking about the configuration sources is that the Makefile would have default values/paths for things that should work for most people and rarely need to change; Infra.toml would have user-specific settings that rarely need to change; parameters handle things that always need to be specified or frequently change; and JSON files as a "runtime store" for things that change each time, like the AMI IDs.


[aws.region.us-west-2]
# If specified, we assume this role before making any API calls in this region.
Expand Down
30 changes: 30 additions & 0 deletions tools/pubsys/policies/ssm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Parameter templates

Files in this directory contain template strings that are used to generate SSM parameter names and values.
You can pass a different directory to `pubsys` to use a different set of parameters.

The directory is expected to contain a file named `defaults.toml` with a table entry per parameter, like this:

```
[[parameter]]
name = "{variant}/{arch}/{image_version}/image_id"
value = "{image_id}"
```

The `name` and `value` can contain template variables that will be replaced with information from the current build and from the AMI registered from that build.

The available variables include:
* `variant`, for example "aws-k8s-1.17"
* `arch`, for example "x86_64"
* `image_id`, for example "ami-0123456789abcdef0"
* `image_name`, for example "bottlerocket-aws-k8s-1.17-x86_64-v0.5.0-e0ddf1b"
* `image_version`, for example "0.5.0-e0ddf1b"
* `region`, for example "us-west-2"

# Overrides

You can also add or override parameters that are specific to `variant` or `arch`.
To do so, create a directory named "variant" or "arch" inside parameters directory, and create a file named after the specific variant or arch for which you want overrides.

For example, to add extra parameters just for the "aarch64" architecture, create `arch/aarch64.toml`.
Inside you can put the same types of `[[parameter]]` declarations that you see in `defaults.toml`, but they'll only be applied for `aarch64` builds.
7 changes: 7 additions & 0 deletions tools/pubsys/policies/ssm/defaults.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[[parameter]]
name = "{variant}/{arch}/{image_version}/image_id"
value = "{image_id}"

[[parameter]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have one of these for current? Should that be in the defaults.toml as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For cargo ssm, the image_version in the template is replaced by the version you're building, and for promote-ssm, by the version you specify with -e SSM_TARGET=. So when we want to publish latest, we'd do cargo make promote-ssm -e SSM_TARGET=latest. In our release process we're planning to promote once from the full version (e.g. 0.5.0-abcdef) to the short version (0.5.0) and again to latest.

name = "{variant}/{arch}/{image_version}/image_version"
value = "{image_version}"
45 changes: 35 additions & 10 deletions tools/pubsys/src/aws/ami/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use register::{get_ami_id, register_image};
use rusoto_core::{Region, RusotoError};
use rusoto_ebs::EbsClient;
use rusoto_ec2::{CopyImageError, CopyImageRequest, CopyImageResult, Ec2, Ec2Client};
use serde::{Deserialize, Serialize};
use snafu::{ensure, OptionExt, ResultExt};
use std::collections::{HashMap, VecDeque};
use std::fs::File;
Expand Down Expand Up @@ -70,11 +71,11 @@ pub(crate) struct AmiArgs {
/// Common entrypoint from main()
pub(crate) async fn run(args: &Args, ami_args: &AmiArgs) -> Result<()> {
match _run(args, ami_args).await {
Ok(ami_ids) => {
Ok(amis) => {
// Write the AMI IDs to file if requested
if let Some(ref path) = ami_args.ami_output {
let file = File::create(path).context(error::FileCreate { path })?;
serde_json::to_writer_pretty(file, &ami_ids).context(error::Serialize { path })?;
serde_json::to_writer_pretty(file, &amis).context(error::Serialize { path })?;
info!("Wrote AMI data to {}", path.display());
}
Ok(())
Expand All @@ -83,8 +84,8 @@ pub(crate) async fn run(args: &Args, ami_args: &AmiArgs) -> Result<()> {
}
}

async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>> {
let mut ami_ids = HashMap::new();
async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, Image>> {
let mut amis = HashMap::new();

info!(
"Using infra config from path: {}",
Expand Down Expand Up @@ -160,11 +161,14 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>
(new_id, false)
};

ami_ids.insert(base_region.name().to_string(), image_id.clone());
amis.insert(
base_region.name().to_string(),
Image::new(&image_id, &ami_args.name),
);

// If we don't need to copy AMIs, we're done.
if regions.is_empty() {
return Ok(ami_ids);
return Ok(amis);
}

// Wait for AMI to be available so it can be copied
Expand Down Expand Up @@ -212,7 +216,7 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>
region.name(),
id
);
ami_ids.insert(region.name().to_string(), id.clone());
amis.insert(region.name().to_string(), Image::new(&id, &ami_args.name));
continue;
}
let request = CopyImageRequest {
Expand Down Expand Up @@ -240,7 +244,7 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>

// If all target regions already have the AMI, we're done.
if copy_requests.is_empty() {
return Ok(ami_ids);
return Ok(amis);
}

// Start requests; they return almost immediately and the copying work is done by the service
Expand Down Expand Up @@ -268,7 +272,10 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>
region.name(),
image_id,
);
ami_ids.insert(region.name().to_string(), image_id);
amis.insert(
region.name().to_string(),
Image::new(&image_id, &ami_args.name),
);
} else {
saw_error = true;
error!(
Expand All @@ -287,7 +294,25 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result<HashMap<String, String>

ensure!(!saw_error, error::AmiCopy);

Ok(ami_ids)
Ok(amis)
}

/// If JSON output was requested, we serialize out a mapping of region to AMI information; this
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
/// struct holds the information we save about each AMI. The `ssm` subcommand uses this
/// information to populate templates representing SSM parameter names and values.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct Image {
pub(crate) id: String,
pub(crate) name: String,
}

impl Image {
fn new(id: &str, name: &str) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
}
}
}

mod error {
Expand Down
11 changes: 11 additions & 0 deletions tools/pubsys/src/aws/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use rusoto_credential::{
};
use rusoto_ebs::EbsClient;
use rusoto_ec2::Ec2Client;
use rusoto_ssm::SsmClient;
use rusoto_sts::{StsAssumeRoleSessionCredentialsProvider, StsClient};
use snafu::ResultExt;

Expand Down Expand Up @@ -37,6 +38,16 @@ impl NewWith for Ec2Client {
}
}

impl NewWith for SsmClient {
fn new_with<P, D>(request_dispatcher: D, credentials_provider: P, region: Region) -> Self
where
P: ProvideAwsCredentials + Send + Sync + 'static,
D: DispatchSignedRequest + Send + Sync + 'static,
{
Self::new_with(request_dispatcher, credentials_provider, region)
}
}

/// Create a rusoto client of the given type using the given region and configuration.
pub(crate) fn build_client<T: NewWith>(
region: &Region,
Expand Down
2 changes: 2 additions & 0 deletions tools/pubsys/src/aws/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use snafu::ResultExt;
pub(crate) mod client;

pub(crate) mod ami;
pub(crate) mod promote_ssm;
pub(crate) mod publish_ami;
pub(crate) mod ssm;

/// Builds a Region from the given region name, and uses the custom endpoint from the AWS config,
/// if specified in aws.region.REGION.endpoint.
Expand Down
Loading