Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,10 @@ release-push: ## Push new Vector version
release-s3: ## Release artifacts to S3
@cargo vdev release s3

.PHONY: release-vdev
release-vdev: ## Release a new vdev version (VDEV_RELEASE_TYPE: major, minor, or patch; default: minor)
@cargo vdev release vdev $${VDEV_RELEASE_TYPE:-minor} --yes

.PHONY: sha256sum
sha256sum: ## Generate SHA256 checksums of CI artifacts
scripts/checksum.sh
Expand Down
1 change: 1 addition & 0 deletions vdev/src/commands/release/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ crate::cli_subcommands! {
mod prepare,
mod push,
s3,
mod vdev,
}

crate::script_wrapper! {
Expand Down
7 changes: 1 addition & 6 deletions vdev/src/commands/release/prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![allow(clippy::print_stderr)]

use crate::git;
use crate::util::run_command;
use crate::util::{get_repo_root, run_command};
use anyhow::{anyhow, Result};
use reqwest::blocking::Client;
use semver::Version;
Expand Down Expand Up @@ -57,7 +57,6 @@ struct Prepare {

impl Cli {
pub fn exec(self) -> Result<()> {

let repo_root = get_repo_root();
env::set_current_dir(repo_root.clone())?;

Expand Down Expand Up @@ -389,10 +388,6 @@ impl Prepare {

// FREE FUNCTIONS AFTER THIS LINE

fn get_repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}

fn get_latest_version_from_vector_tags() -> Result<Version> {
let tags = run_command("git tag --list --sort=-v:refname");
let latest_tag = tags
Expand Down
147 changes: 147 additions & 0 deletions vdev/src/commands/release/vdev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use anyhow::{Result, bail, Context as _};
use clap::{Args, ValueEnum};
use semver::Version;
use std::fs;
use std::path::PathBuf;

use crate::{git, util};

const PUBLISH_URL: &str = "https://github.com/vectordotdev/vector/actions/workflows/vdev_publish.yml";
const MAIN_BRANCH: &str = "master";

/// Release a new version of vdev
///
/// This command automates the vdev release process:
/// 1. Validates the new version
/// 2. Updates vdev/Cargo.toml
/// 3. Commits the change
/// 4. Creates and pushes the release tag
#[derive(Args, Debug)]
#[command()]
pub struct Cli {
/// The release type: major, minor, or patch
#[arg(value_enum)]
release_type: ReleaseType,

/// Skip confirmation prompts
#[arg(long)]
yes: bool,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
enum ReleaseType {
Major,
Minor,
Patch,
}

impl Cli {
pub fn exec(self) -> Result<()> {
// Check that we're on the master branch
let current_branch = git::current_branch()?;
if current_branch != MAIN_BRANCH {
bail!("Release cancelled. You must be on the '{MAIN_BRANCH}' branch to release (currently on: {current_branch})");
}

// Check git status
if !git::check_git_repository_clean()? {
bail!("Release cancelled. You have uncommitted changes in your repository.");
}

let repo_root = util::get_repo_root();
let vdev_dir = repo_root.join("vdev");
let vdev_cargo_toml = vdev_dir.join("Cargo.toml");
if !vdev_cargo_toml.exists() {
bail!("Could not find Cargo.toml at {}", vdev_cargo_toml.display());
}
let current_version = read_vdev_version(&vdev_cargo_toml)?;
info!("Current vdev version: {current_version}");

let new_version = match self.release_type {
ReleaseType::Major => Version::new(current_version.major + 1, 0, 0),
ReleaseType::Minor => Version::new(current_version.major, current_version.minor + 1, 0),
ReleaseType::Patch => Version::new(current_version.major, current_version.minor, current_version.patch + 1),
};

let new_version_str = new_version.to_string();
info!("Release version: {new_version_str} ({:?})", self.release_type);

let tag_name = format!("vdev-v{new_version_str}");
// Confirm before proceeding
if !self.yes {
let summary = format_release_plan(&new_version_str, &tag_name);
info!("{summary}");
if !util::confirm("Proceed with release?")? {
bail!("Release cancelled");
}
}

// Update Cargo.toml
info!("Updating Cargo.toml to version {new_version_str}");
update_vdev_version(&vdev_cargo_toml, &current_version, &new_version)?;

let vdev_cargo_toml_relative = vdev_cargo_toml.strip_prefix(&repo_root)
.unwrap_or(&vdev_cargo_toml);
git::run_and_check_output(&["add", &vdev_cargo_toml_relative.display().to_string()])?;
let commit_message = format!("chore(vdev): bump version to {new_version_str}");
git::commit(&commit_message)?;
debug!("Created commit: {commit_message}");

info!("Creating tag {tag_name}");
git::tag_version(&tag_name)?;

info!("Pushing to origin");
git::push_branch(MAIN_BRANCH)?;
debug!("Pushed to {MAIN_BRANCH} branch.");
Comment on lines +93 to +95
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this push a commit directly to master? I don't think this is a good idea and I also don't think it will work since we have branch protection rules

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the simplest way. Only repo maintainers will ever use this script and they can push to master. I considered bumping locally, publishing, and making a workflow commit the new version. But I don't see why the latter is better really.

Alternative is to make a PR, update the version and then run this script to update the tag. You can think about it a bit more if you want.


git::push_branch(&tag_name)?;
debug!("Pushed tag: {tag_name}");

info!("Monitor release workflow: {PUBLISH_URL}");
Ok(())
}
}

fn read_vdev_version(cargo_toml_path: &PathBuf) -> Result<Version> {
let contents = fs::read_to_string(cargo_toml_path)
.with_context(|| format!("Failed to read {}", cargo_toml_path.display()))?;

let cargo_toml: util::CargoToml = toml::from_str(&contents)
.with_context(|| format!("Failed to parse {}", cargo_toml_path.display()))?;

Version::parse(&cargo_toml.package.version)
.context("Failed to parse version number")
}

fn update_vdev_version(
cargo_toml_path: &PathBuf,
old_version: &Version,
new_version: &Version,
) -> Result<()> {
let contents = fs::read_to_string(cargo_toml_path)
.with_context(|| format!("Failed to read {}", cargo_toml_path.display()))?;

let old_line = format!("version = \"{old_version}\"");
let new_line = format!("version = \"{new_version}\"");

if !contents.contains(&old_line) {
bail!("Could not find exact version line '{old_line}' in {}", cargo_toml_path.display());
}

let updated_contents = contents.replace(&old_line, &new_line);

fs::write(cargo_toml_path, updated_contents)
.with_context(|| format!("Failed to write {}", cargo_toml_path.display()))?;

Ok(())
}

fn format_release_plan(version: &str, tag_name: &str) -> String {
format!(
"\nThis will:\n\
1. Update Cargo.toml to version {version}\n\
2. Commit the change\n\
3. Create tag {tag_name}\n\
4. Push the commit and tag to origin\n"
)
}
32 changes: 28 additions & 4 deletions vdev/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]

use std::{
collections::BTreeMap,
ffi::{OsStr, OsString},
fmt::Debug,
fs,
io::{ErrorKind, IsTerminal},
path::Path,
path::{Path, PathBuf},
process,
process::{Command, Output},
sync::LazyLock,
Expand Down Expand Up @@ -91,6 +88,7 @@ impl<T: AsRef<OsStr>> ChainArgs for [T] {
}
}

#[allow(clippy::print_stderr)]
pub fn run_command(cmd: &str) -> String {
let output = Command::new("sh")
.arg("-c")
Expand All @@ -108,3 +106,29 @@ pub fn run_command(cmd: &str) -> String {

String::from_utf8_lossy(&output.stdout).to_string()
}

/// Get the root directory of the Vector repository
///
/// This is calculated relative to the vdev crate's manifest directory
pub fn get_repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("vdev must be in a subdirectory of the repo")
.to_path_buf()
}

/// Prompt the user for confirmation with a yes/no question
///
/// Returns `Ok(true)` if the user confirms (y/yes), `Ok(false)` otherwise
#[allow(clippy::print_stdout)]
pub fn confirm(prompt: &str) -> Result<bool> {
use std::io::{self, Write};

print!("{prompt} [y/N] ");
io::stdout().flush()?;

let mut input = String::new();
io::stdin().read_line(&mut input)?;

Ok(input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes"))
}
Loading