mirror of
https://github.com/fleetdm/fleet
synced 2026-05-16 05:28:38 +00:00
888 lines
36 KiB
Bash
Executable file
888 lines
36 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
#
|
|
# ,::;;,
|
|
# ,:;;;:,,;:
|
|
# ,::;;+: ,:;;:
|
|
# ,;;;:,,++;;:,
|
|
# :;;;::++:,
|
|
# ++:, ,;:
|
|
# ,, ,;: ::+;::: ,,,
|
|
# ,:;::;: ::: ,:,;:,*;;;;+,,,,::,,,
|
|
# ;: :+: ,;:, ,:++;;:;**;++::::::;;;;:,
|
|
# ,; ,::,:;:, :,: :,;;+*+;, :: :;:;;:,
|
|
# ,;:;, :;:, ,;:*: ,::?*: :: ,,,, ,:::::;++;;:::::,,
|
|
# :; :;:, ,;+; ,:;;; ,::;;;::::::;;:,,,,:+,,,,,:::;;::,
|
|
# ;, ,;:, :+, ,:;;, ;::;:,,,:: :: :; ,;?+
|
|
# ,;, ,;:, ,;: ,:;:, ,+;, ::, ::,,:;: ,,,::;;;+;
|
|
# ,; ,+;, :::,:;++, :;;;;;;;;;;;;;;+;;;::;;;;;::,,:;:,
|
|
# :: ,;:,;;, ,;;;:,:+, ,,: ,,:::;;;;;;**;,,,, ,:;:,
|
|
# :: ,;:, ,;;, ,;;:, :;;:, ,,::::,,:;:,, ;+*+ ,:;;;,
|
|
# ;, ,::, ,:;;;:, ::;,:,,::::, ,:;: ,++:,::;;:,
|
|
# ,;, :;, ,:;++, ::+:;:,, :;:, ,:;;;:,
|
|
# ,;,;: ,:;++:, ,:;++;, ,;+:, ,:;;;:,
|
|
# :; ,,:;+;:, ,:::,,,,,,, :;;+; ,:;;::,
|
|
# ;: :;+;:, ,:::, ,;+;;;:::;;,:;;::,
|
|
# ;;,;*+:,,:, ,:::, ,;, ,;: :,++:,,
|
|
# :;:*;: :;, ,::, ,:, ::, ::;
|
|
# ,:+:, :: ,:: ,:, ;: ,;;
|
|
# ,;, ,,,: ;;,,,;; ,;; ,:, ,;:,:+;, ,,,,,
|
|
# ;::;;;::*;:;;;:::;;++, ,:, ,+;;::;+;;;:::;+,
|
|
# +;, :?;;:::, ,+, ,:, :;,,,:;;:, :;
|
|
# ,+, :+,,,,,;,,,;; ,:, ;+;;;:;; ,;;
|
|
# :; :; ,::;+, ,:,,;:,,, :: ,,:;;,
|
|
# ;: :; ;; ,: ++;+;; ,;+;;;::,
|
|
# ,;, :: :;: ,: ::,,,::+?*+;:
|
|
# :; :: ;::, ,: ,;:,:;;:,;;:*+;
|
|
# ,;: :: ,: : ,: :;,;:, :+;+,
|
|
# ,;, ;: ;,,: ,:,;+ ,: ,;+:
|
|
# ;;;;: ,: :, ,;:*++;;;+++**+:
|
|
# ;:,, ;, ;:;+::,, ,:;+:::+;
|
|
# ,:,;;:, ,,:;;::::;;:
|
|
# +;:,,,:;;;;;;;;::,
|
|
# ;?+;;;:,:;;::,
|
|
# ,,,+;,,:;
|
|
# :;::,
|
|
#
|
|
#
|
|
# /$$$$$$$$ /$$ /$$$$$$$$ /$$$$$$$$ /$$$$$$$$
|
|
# | $$_____/| $$ | $$_____/| $$_____/|__ $$__/
|
|
# | $$ | $$ | $$ | $$ | $$
|
|
# | $$$$$ | $$ | $$$$$ | $$$$$ | $$
|
|
# | $$__/ | $$ | $$__/ | $$__/ | $$
|
|
# | $$ | $$ | $$ | $$ | $$
|
|
# | $$ | $$$$$$$$| $$$$$$$$| $$$$$$$$ | $$
|
|
# |__/ |________/|________/|________/ |__/
|
|
#
|
|
# /$$$$$$$ /$$$$$$$$ /$$ /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ /$$$$$$$
|
|
# | $$__ $$| $$_____/| $$ | $$_____/ /$$__ $$ /$$__ $$| $$_____/| $$__ $$
|
|
# | $$ \ $$| $$ | $$ | $$ | $$ \ $$| $$ \__/| $$ | $$ \ $$
|
|
# | $$$$$$$/| $$$$$ | $$ | $$$$$ | $$$$$$$$| $$$$$$ | $$$$$ | $$$$$$$/
|
|
# | $$__ $$| $$__/ | $$ | $$__/ | $$__ $$ \____ $$| $$__/ | $$__ $$
|
|
# | $$ \ $$| $$ | $$ | $$ | $$ | $$ /$$ \ $$| $$ | $$ \ $$
|
|
# | $$ | $$| $$$$$$$$| $$$$$$$$| $$$$$$$$| $$ | $$| $$$$$$/| $$$$$$$$| $$ | $$
|
|
# |__/ |__/|________/|________/|________/|__/ |__/ \______/ |________/|__/ |__/
|
|
#
|
|
|
|
failed=false
|
|
|
|
usage() {
|
|
echo "Usage: $0 [options] (optional|start_version)"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " -a, --and_cherry_pick This is a minor release and cherry pick. Used for unscheduled minor releases that are patches + a specific feature"
|
|
echo " -c, --cherry_pick_resolved The script has been run, had merge conflicts, and those have been resolved and all cherry picks completed manually."
|
|
echo " -d, --dry_run Perform a trial run with no changes made"
|
|
echo " -f, --force Skip all confirmations"
|
|
echo " -h, --help Display this help message and exit"
|
|
echo " -g, --tag Run the tag step"
|
|
echo " -k, --skip_dogfood Skip deploying to dogfood. Necessary if you need to release without interrupting demos"
|
|
echo " -m, --minor Increment to a minor version instead of patch (required if including non-bugs)"
|
|
echo " -n, --announce_only Announce the release only, do not publish the release."
|
|
echo " -o, --open_api_key Set the Open API key for calling out to ChatGPT"
|
|
echo " -p, --print If the release is already drafted then print out the helpful info"
|
|
echo " -q, --quiet This will skip notifying in slack"
|
|
echo " -r, --release_notes Update the release notes in the named release on github and exit (requires changelog output from running the script previously)."
|
|
echo " -s, --start_version Set the target starting version (can also be the first positional arg) for the release, defaults to latest release on github"
|
|
echo " -t, --target_date Set the target date for the release, defaults to today if not provided"
|
|
echo " -u, --publish_release Set's release from draft to release, deploys to dogfood."
|
|
echo " -v, --target_version Set the target version for the release"
|
|
echo ""
|
|
echo "Environment Variables:"
|
|
echo " OPEN_API_KEY Open API key used for api requests to chat GPT"
|
|
echo " SLACK_GENERAL_TOKEN Slack token to publish via curl to #general"
|
|
echo " SLACK_HELP_INFRA_TOKEN Slack token to publish via curl to #help-infrastructure"
|
|
echo " SLACK_HELP_ENG_TOKEN Slack token to publish via curl to #help-engineering"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 -d Dry run the script"
|
|
echo " $0 -m -v 4.45.1 Set a minor release targeting version 4.45.1"
|
|
echo " $0 --target_version 4.45.1 --open_api_key examplekey"
|
|
echo ""
|
|
}
|
|
|
|
# ======================================
|
|
# Options
|
|
# ======================================
|
|
|
|
# Initialize variables for the options
|
|
minor_cherry_pick=false
|
|
cherry_pick_resolved=false
|
|
dry_run=false
|
|
force=false
|
|
minor=false
|
|
announce_only=false
|
|
open_api_key=""
|
|
start_version=""
|
|
target_date=""
|
|
target_version=""
|
|
print_info=false
|
|
publish_release=false
|
|
release_notes=false
|
|
do_tag=false
|
|
quiet=false
|
|
skip_deploy_dogfood=false
|
|
|
|
# Parse long options manually
|
|
for arg in "$@"; do
|
|
shift
|
|
case "$arg" in
|
|
"--and_cherry_pick") set -- "$@" "-a" ;;
|
|
"--cherry_pick_resolved") set -- "$@" "-c" ;;
|
|
"--dry-run") set -- "$@" "-d" ;;
|
|
"--force") set -- "$@" "-f" ;;
|
|
"--help") set -- "$@" "-h" ;;
|
|
"--minor") set -- "$@" "-m" ;;
|
|
"--announce_only") set -- "$@" "-n" ;;
|
|
"--open_api_key") set -- "$@" "-o" ;;
|
|
"--print") set -- "$@" "-p" ;;
|
|
"--quiet") set -- "$@" "-q" ;;
|
|
"--skip_dogfood") set -- "$@" "-k" ;;
|
|
"--publish_release") set -- "$@" "-u" ;;
|
|
"--release_notes") set -- "$@" "-r" ;;
|
|
"--start_version") set -- "$@" "-s" ;;
|
|
"--tag") set -- "$@" "-g" ;;
|
|
"--target_date") set -- "$@" "-t" ;;
|
|
"--target_version") set -- "$@" "-v" ;;
|
|
*) set -- "$@" "$arg"
|
|
esac
|
|
done
|
|
|
|
# Extract options and their arguments using getopts
|
|
while getopts "acdfhgkmno:pqrs:t:uv:w" opt; do
|
|
case "$opt" in
|
|
a) minor_cherry_pick=true ;;
|
|
c) cherry_pick_resolved=true ;;
|
|
d) dry_run=true ;;
|
|
f) force=true ;;
|
|
h) usage; exit 0 ;;
|
|
g) do_tag=true ;;
|
|
k) skip_deploy_dogfood=true ;;
|
|
m) minor=true ;;
|
|
n) announce_only=true ;;
|
|
o) open_api_key=$OPTARG ;;
|
|
p) print_info=true ;;
|
|
q) quiet=true ;;
|
|
r) release_notes=true ;;
|
|
s) start_version=$OPTARG ;;
|
|
t) target_date=$OPTARG ;;
|
|
u) publish_release=true ;;
|
|
v) target_version=$OPTARG ;;
|
|
?) usage; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ======================================
|
|
# Helper Functions
|
|
# ======================================
|
|
|
|
# Shift off the options and optional --
|
|
shift $((OPTIND -1))
|
|
|
|
# Usage example: Run a command and show spinner for n seconds
|
|
# Replace `sleep 5` with your command
|
|
# sleep 5 & show_spinner 5
|
|
show_spinner() {
|
|
local pid=$!
|
|
local delay=0.1
|
|
local spinstr='/-\|'
|
|
local elapsedTime=0
|
|
local maxTime=$1
|
|
|
|
printf "Processing "
|
|
while [ $elapsedTime -lt $maxTime ]; do
|
|
local temp=${spinstr#?}
|
|
printf "%c" "$spinstr"
|
|
local spinstr=$temp${spinstr%"$temp"}
|
|
sleep $delay
|
|
printf "\b"
|
|
elapsedTime=$((elapsedTime+1))
|
|
done
|
|
|
|
printf "\nDone.\n"
|
|
}
|
|
|
|
check_grep() {
|
|
# Check if `grep` supports the `-P` option by using it in a no-op search.
|
|
# Redirecting stderr to /dev/null to suppress error messages in case `-P` is not supported.
|
|
if echo "" | grep -P "" >/dev/null 2>&1; then
|
|
return
|
|
else
|
|
# Now check if `ggrep` is available.
|
|
if command -v ggrep >/dev/null 2>&1; then
|
|
return
|
|
else
|
|
echo "Please install latest grep with $(brew install grep)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
check_gh() {
|
|
gh repo set-default
|
|
}
|
|
|
|
ask() {
|
|
ask_prompt=$1
|
|
if [ "$force" = "false" ]; then
|
|
read -r -p "$ask_prompt" response
|
|
case "$response" in
|
|
[yY][eE][sS]|[yY])
|
|
echo
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
check_required_binaries() {
|
|
local missing_counter=0
|
|
# List of required binaries used in the script
|
|
local required_binaries=("jq" "gh" "git" "curl" "awk" "sed" "make" "ack")
|
|
|
|
for bin in "${required_binaries[@]}"; do
|
|
if ! command -v "$bin" &> /dev/null; then
|
|
echo "Error: Required binary '$bin' is not installed." >&2
|
|
missing_counter=$((missing_counter + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $missing_counter -ne 0 ]; then
|
|
echo "Error: $missing_counter required binary(ies) are missing. Install them before running this script." >&2
|
|
exit 1
|
|
fi
|
|
check_grep
|
|
check_gh
|
|
}
|
|
|
|
validate_and_format_date() {
|
|
local input_date="$1"
|
|
local formatted_date
|
|
local correct_format="%b %d, %Y" # e.g., Jan 01, 2024
|
|
|
|
# Try to convert input_date to the correct format
|
|
formatted_date=$(date -d "$input_date" +"$correct_format" 2>/dev/null)
|
|
|
|
if [ $? -ne 0 ]; then
|
|
# date conversion failed
|
|
echo "Error: Incorrect date format. Expected format example: $correct_format (e.g., Jan 01, 2024)" >&2
|
|
exit 1
|
|
else
|
|
# Check if the formatted date matches the expected date format
|
|
if ! date -d "$formatted_date" +"$correct_format" &>/dev/null; then
|
|
# This means the formatted date does not match our correct format
|
|
echo "Error: Incorrect date format after conversion. Expected format example: $correct_format (e.g., Jan 01, 2024)" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# If we reached here, the date is valid and correctly formatted
|
|
target_date="$formatted_date" # Update the target_date with the formatted date
|
|
echo "Validated and formatted date: $target_date"
|
|
}
|
|
|
|
# Function to determine the best grep variant to use
|
|
determine_grep_command() {
|
|
# Check if `ggrep` is available
|
|
if command -v ggrep >/dev/null 2>&1; then
|
|
echo "ggrep" # Use GNU grep if available
|
|
elif echo "" | grep -P "" >/dev/null 2>&1; then
|
|
echo "grep" # Use grep if it supports the -P option
|
|
else
|
|
echo "grep" # Default to grep if ggrep is not available and -P is not supported
|
|
# Note: You might want to handle the lack of -P support differently here
|
|
fi
|
|
}
|
|
|
|
# Assign the best grep variant to a variable
|
|
GREP_CMD=$(determine_grep_command)
|
|
|
|
|
|
# ======================================
|
|
# Changelog
|
|
# ======================================
|
|
|
|
build_changelog() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
make changelog
|
|
|
|
git diff CHANGELOG.md | $GREP_CMD '^+' | sed 's/^+//g' | $GREP_CMD -v CHANGELOG.md > new_changelog
|
|
output=$(cat new_changelog | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g')
|
|
|
|
git checkout CHANGELOG.md
|
|
if [[ "$target_date" == "" ]]; then
|
|
tartget_date=$(date +"%b %d, %Y")
|
|
fi
|
|
echo "## Fleet $target_milestone ($tartget_date)" > temp_changelog
|
|
echo "" >> temp_changelog
|
|
echo "### Bug fixes" >> temp_changelog
|
|
echo "" >> temp_changelog
|
|
echo -e "${output}" >> temp_changelog
|
|
echo "" >> temp_changelog
|
|
cp CHANGELOG.md old_changelog
|
|
cat temp_changelog
|
|
echo
|
|
echo "About to write changelog"
|
|
ask "Does the above changelog look good (edit temp_changelog now to make changes) (n exits)? [y/N] "
|
|
cat temp_changelog > CHANGELOG.md
|
|
cat old_changelog >> CHANGELOG.md
|
|
rm -f old_changelog
|
|
cp CHANGELOG.md /tmp
|
|
else
|
|
echo "DRYRUN: Would have formatted changelog"
|
|
fi
|
|
}
|
|
|
|
changelog_and_versions() {
|
|
branch_for_changelog=$1
|
|
source_branch=$2
|
|
|
|
local_exists=$(git branch | $GREP_CMD $branch_for_changelog)
|
|
if [ "$dry_run" = "false" ]; then
|
|
if [[ $local_exists != "" ]]; then
|
|
# Clear previous
|
|
git branch -D $branch_for_changelog
|
|
fi
|
|
git checkout -b $branch_for_changelog
|
|
cp /tmp/CHANGELOG.md .
|
|
git add CHANGELOG.md
|
|
escaped_start_version=$(echo "$start_milestone" | sed 's/\./\\./g')
|
|
version_files=$(ack -l --ignore-dir=tools/release --ignore-dir=articles --ignore-dir=orbit --ignore-dir=server/service --ignore-file=is:CHANGELOG.md "$escaped_start_version")
|
|
unameOut="$(uname -s)"
|
|
case "${unameOut}" in
|
|
Linux*) echo "$version_files" | xargs sed -i "s/$escaped_start_version/$target_milestone/g";
|
|
sed -i -E 's/(version: v[0-9]+\.[0-9]+\.)([0-9]+)/echo "\1$((\2+1))"/e' charts/fleet/Chart.yaml;;
|
|
Darwin*) echo "$version_files" | xargs sed -i '' "s/$escaped_start_version/$target_milestone/g";
|
|
sed -i '' -E 's/(version: v[0-9]+\.[0-9]+\.)([0-9]+)/echo "\1$((\2+1))"/e' charts/fleet/Chart.yaml;;
|
|
*) echo "unknown distro to parse version"
|
|
esac
|
|
git add terraform charts infrastructure tools
|
|
git commit -m "Adding changes for Fleet v$target_milestone"
|
|
git push origin $branch_for_changelog -f
|
|
gh pr create -f -B $source_branch
|
|
gh workflow run goreleaser-snapshot-fleet.yaml --ref $source_branch # Manually trigger workflow run
|
|
else
|
|
echo "DRYRUN: Would have created Changelog / verison pr from $branch_for_changelog to $source_branch"
|
|
fi
|
|
}
|
|
|
|
create_qa_issue() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
# Check for QA issue
|
|
found=$(gh issue list --search "Release QA: $target_milestone in:title" --json number | jq length)
|
|
if [[ "$found" == "0" ]]; then
|
|
cat .github/ISSUE_TEMPLATE/release-qa.md | awk 'BEGIN {count=0} /^---$/ {count++} count==2 && /^---$/ {getline; count++} count > 2 {print}' > temp_qa_issue_file
|
|
gh issue create --title "Release QA: $target_milestone" -F temp_qa_issue_file \
|
|
--assignee "pezhub" --label "#g-mdm" --label ":release" \
|
|
--assignee "jmwatts" --label "#g-software" \
|
|
--assignee "xpkoala" --label "#g-orchestration"
|
|
rm -f temp_qa_issue_file
|
|
fi
|
|
else
|
|
echo "DRYRUN: Would have searched for and created if not found QA release ticket"
|
|
fi
|
|
}
|
|
|
|
print_announce_info() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
qa_ticket=$(gh issue list --search "Release QA: $target_milestone in:title" --json url | jq -r .[0].url)
|
|
docker_deploy=$(gh run list --workflow goreleaser-snapshot-fleet.yaml --json event,url,headBranch --limit 100 | jq -r "[.[]|select(.headBranch==\"$target_branch\")][0].url")
|
|
echo
|
|
echo "For announcing in #help-engineering"
|
|
echo "===================================================="
|
|
echo "Release $target_milestone QA ticket and docker publish"
|
|
echo "QA ticket for Release $target_milestone " $qa_ticket
|
|
echo "Docker Deploy status " $docker_deploy
|
|
echo "List of tickets pulled into release https://github.com/fleetdm/fleet/milestone/$target_milestone_number"
|
|
echo
|
|
slack_hook_url=https://hooks.slack.com/services
|
|
app_id=T019PP37ALW
|
|
announce_text="Release $target_milestone QA ticket and docker publish\nQA ticket for Release $target_milestone $qa_ticket\nDocker Deploy status $docker_deploy\nList of tickets pulled into release https://github.com/fleetdm/fleet/milestone/$target_milestone_number"
|
|
if [ "$quiet" = "false" ]; then
|
|
curl -X POST -H 'Content-type: application/json' \
|
|
--data "{\"text\":\"$announce_text\"}" \
|
|
$slack_hook_url/$app_id/$SLACK_HELP_ENG_TOKEN
|
|
fi
|
|
else
|
|
echo "DRYRUN: Would have printed announce in #help-engineering text w/ qa ticket, deploy to docker link, and milestone issue list link"
|
|
fi
|
|
}
|
|
|
|
general_announce_info() {
|
|
if [[ "$minor" == "true" ]]; then
|
|
article_url="https://fleetdm.com/releases/fleet-${target_milestone//./-}"
|
|
article_published=$(curl -is "$article_url" | head -n 1 | awk '{print $2}')
|
|
if [[ "$article_published" != "200" ]]; then
|
|
echo "Could't find article at '$article_url'"
|
|
exit 1
|
|
fi
|
|
|
|
# TODO Publish Linkedin post about release article here and save url
|
|
linkedin_post_url="https://www.linkedin.com/feed/update/urn:li:activity:7274913563989721088"
|
|
fi
|
|
echo "========================================================================="
|
|
echo "Update osquery Slack Fleet channel topic to say the correct version $next_ver"
|
|
echo "========================================================================="
|
|
# Slack
|
|
slack_hook_url=https://hooks.slack.com/services
|
|
app_id=T019PP37ALW
|
|
announce_text=":cloud: :rocket: The latest version of Fleet is $target_milestone.\nMore info: https://github.com/fleetdm/fleet/releases/tag/$next_tag"
|
|
if [[ "$minor" == "true" ]]; then
|
|
announce_text=":cloud: :rocket: The latest version of Fleet is $target_milestone.\nMore info: https://github.com/fleetdm/fleet/releases/tag/$next_tag\nRelease article: $article_url\nLinkedIn post: $linkedin_post_url"
|
|
fi
|
|
|
|
echo -e $announce_text
|
|
|
|
if [ "$quiet" = "false" ]; then
|
|
if [ "$dry_run" = "false" ]; then
|
|
curl -X POST -H 'Content-type: application/json' \
|
|
--data "{\"text\":\"$announce_text\"}" \
|
|
$slack_hook_url/$app_id/$SLACK_GENERAL_TOKEN
|
|
|
|
curl -X POST -H 'Content -type: application/json' \
|
|
--data "{\"text\":\"$announce_text\nDogfood Deployed $dogfood_deploy\"}" \
|
|
$slack_hook_url/$app_id/$SLACK_HELP_INFRA_TOKEN
|
|
fi
|
|
fi
|
|
}
|
|
|
|
update_release_notes() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
if [ ! -f temp_changelog ]; then
|
|
echo "cannot find changelog to populate release notes"
|
|
exit 1
|
|
fi
|
|
cat temp_changelog | tail -n +3 > release_notes
|
|
echo "" >> release_notes
|
|
echo "### Upgrading" >> release_notes
|
|
echo "" >> release_notes
|
|
echo "Please visit our [update guide](https://fleetdm.com/docs/deploying/upgrading-fleet) for upgrade instructions." >> release_notes
|
|
echo "" >> release_notes
|
|
echo "### Documentation" >> release_notes
|
|
echo "" >> release_notes
|
|
echo "Documentation for Fleet is available at [fleetdm.com/docs](https://fleetdm.com/docs)." >> release_notes
|
|
echo "" >> release_notes
|
|
echo "### Binary Checksum" >> release_notes
|
|
echo "" >> release_notes
|
|
echo "**SHA256**" >> release_notes
|
|
echo "" >> release_notes
|
|
echo '```' >> release_notes
|
|
gh release download $next_tag -p checksums.txt --clobber
|
|
cat checksums.txt >> release_notes
|
|
echo '```' >> release_notes
|
|
|
|
echo
|
|
echo "============== Release Notes ========================"
|
|
cat release_notes
|
|
echo "============== Release Notes ========================"
|
|
|
|
gh release edit --draft -F release_notes $next_tag
|
|
else
|
|
echo "DRYRUN: Would have created release notes based on temp_changelog"
|
|
fi
|
|
}
|
|
|
|
tag() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
|
found_version=$(cat CHANGELOG.md | $GREP_CMD $target_milestone)
|
|
if [[ "$found_version" == "" ]]; then
|
|
echo "Can't tag if CHANGELOG pr has not been merged yet"
|
|
exit 1
|
|
fi
|
|
if [[ "$current_branch" != "$target_branch" ]]; then
|
|
echo "Can't tag release if you aren't on '$target_branch'"
|
|
exit 1
|
|
fi
|
|
|
|
# Officially tag and push
|
|
git tag $next_tag
|
|
git push origin $next_tag
|
|
|
|
# This lets us wait for github actions to trigger
|
|
# we are specifically waiting for goreleaser to start
|
|
# off the `tag` branch ie: fleet-v4.47.2 to watch until it completes
|
|
# The last step of goreleaser is the create the draft release for us to modify later
|
|
show_spinner 200
|
|
else
|
|
echo "DRYRUN: Would have tagged and pushed $next_tag"
|
|
fi
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
releaser_out=$(gh run list --workflow goreleaser-fleet.yaml --json databaseId,event,headBranch,url | jq "[.[]|select(.headBranch==\"$next_tag\")][0]")
|
|
echo "Releaser running " $(echo $releaser_out | jq -r ".url")
|
|
|
|
gh run watch $(echo $releaser_out | jq -r ".databaseId")
|
|
else
|
|
echo "DRYRUN: Would found goreleaser action and waited for it to complete"
|
|
fi
|
|
|
|
# Update draft release notes w/ changelog / notes / checksums
|
|
update_release_notes
|
|
}
|
|
|
|
|
|
publish() {
|
|
if [ "$dry_run" = "false" ]; then
|
|
if [ "$announce_only" = "false" ]; then
|
|
# TODO more checks to validate we are ready to publish
|
|
gh release edit --draft=false --latest $next_tag
|
|
|
|
if [ "$skip_deploy_dogfood" = "false" ]; then
|
|
if [ "$force" = "false" ]; then
|
|
read -r -p "Are you ABSOLUTELY SURE you want to deploy to Dogfood right now?? Check for demo's: [y/n]" response
|
|
case "$response" in
|
|
[yY][eE][sS]|[yY])
|
|
gh workflow run dogfood-deploy.yml -f DOCKER_IMAGE=fleetdm/fleet:$next_ver
|
|
show_spinner 200
|
|
dogfood_deploy=$(gh run list --workflow=dogfood-deploy.yml --status in_progress -L 1 --json url | jq -r '.[] | .url')
|
|
echo
|
|
;;
|
|
*)
|
|
dogfood_deploy="skipped"
|
|
echo
|
|
;;
|
|
esac
|
|
fi
|
|
fi
|
|
latest_npm=$(npm view fleetctl --json | jq -r '.version' | sed -e 's/^v//')
|
|
latest_local=$(jq -r '.version' tools/fleetctl-npm/package.json | sed -e 's/^v//')
|
|
|
|
if [ "$(node -e "console.log(require('compare-versions').compareVersions('${latest_local}', '${latest_npm}'))")" = "-1" ]; then
|
|
# We're publishing a patch to an older version
|
|
cd tools/fleetctl-npm && npm publish "--tag=last-patched-version"
|
|
else
|
|
# We're publishing the latest version
|
|
cd tools/fleetctl-npm && npm publish
|
|
fi
|
|
|
|
|
|
issues=$(gh issue list -L 500 -m $target_milestone --json number | jq -r '.[] | .number')
|
|
for iss in $issues; do
|
|
is_story=$(gh issue view $iss --json labels | jq -r '.labels | .[] | .name' | grep story)
|
|
# close all non-stories
|
|
if [[ "$is_story" == "" ]]; then
|
|
echo "Closing #$iss"
|
|
gh issue close $iss
|
|
fi
|
|
done
|
|
|
|
echo "Closing milestone"
|
|
gh api repos/fleetdm/fleet/milestones/$target_milestone_number -f state=closed
|
|
fi
|
|
else
|
|
echo "DRYRUN: Would have published $next_tag / deployed to dogfood / closed non-stories / closed milestone / announced in slack"
|
|
fi
|
|
|
|
echo "Send general announce"
|
|
# Send general announcement in #general
|
|
general_announce_info
|
|
}
|
|
|
|
# ======================================
|
|
# Validate ok to run
|
|
# ======================================
|
|
|
|
# Validate we have all commands required to perform this script
|
|
check_required_binaries
|
|
|
|
# Now you can use the $dry_run variable to see if the option was set
|
|
if $dry_run; then
|
|
echo "Dry run mode enabled."
|
|
fi
|
|
|
|
# Check for OPEN_API_KEY environment variable if no key was provided through command-line options
|
|
if [ -z "$open_api_key" ]; then
|
|
if [ -n "$OPEN_API_KEY" ]; then
|
|
open_api_key=$OPEN_API_KEY
|
|
else
|
|
echo "Error: No open API key provided. Set the key via -o/--open-api-key option or OPEN_API_KEY environment variable." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$SLACK_GENERAL_TOKEN" ]; then
|
|
echo "Error: No SLACK_GENERAL_TOKEN environment variable." >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$SLACK_HELP_INFRA_TOKEN" ]; then
|
|
echo "Error: No SLACK_HELP_INFRA_TOKEN environment variable." >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$SLACK_HELP_ENG_TOKEN" ]; then
|
|
echo "Error: No SLACK_HELP_ENG_TOKEN environment variable." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$target_date" != "" ]]; then
|
|
validate_and_format_date $target_date
|
|
fi
|
|
|
|
# ex v4.43.0
|
|
if [ -z "$start_version" ]; then
|
|
if [[ "$1" == "" ]]; then
|
|
# grab latest draft excluding test version 9.99.9
|
|
draft=$(gh release list | $GREP_CMD Draft | $GREP_CMD -v 9.99.9)
|
|
if [[ "$draft" != "" ]]; then
|
|
target_version=$(echo $draft | awk '{print $1}' | cut -d '-' -f2)
|
|
start_version=$(gh release list | $GREP_CMD Draft -A1 | tail -n1 | awk '{print $1}' | cut -d '-' -f2)
|
|
else
|
|
start_version=$(gh release list | $GREP_CMD Latest | awk '{print $1}' | cut -d '-' -f2)
|
|
fi
|
|
else
|
|
start_version="$1"
|
|
fi
|
|
fi
|
|
|
|
if [[ $start_version != v* ]]; then
|
|
start_version=$(echo "v$start_version")
|
|
fi
|
|
|
|
if [[ "$target_version" != "" ]]; then
|
|
if [[ $target_version != v* ]]; then
|
|
target_version=$(echo "v$target_version")
|
|
fi
|
|
next_ver=$target_version
|
|
else
|
|
if [[ "$minor" == "true" ]]; then
|
|
next_ver=$(echo $start_version | awk -F. '{print $1"."($2+1)".0"}')
|
|
else
|
|
next_ver=$(echo $start_version | awk -F. '{print $1"."$2"."($3+1)}')
|
|
fi
|
|
fi
|
|
|
|
start_ver_tag=fleet-$start_version
|
|
|
|
if [[ "$minor" == "true" ]]; then
|
|
echo "Minor release from $start_version to $next_ver"
|
|
# For scheduled minor releases, we want to branch off of main
|
|
start_ver_tag="main"
|
|
else
|
|
echo "Patch release from $start_version to $next_ver"
|
|
fi
|
|
|
|
ask "If this is correct confirm yes to continue? [y/N] "
|
|
|
|
# 4.47.2
|
|
start_milestone="${start_version:1}"
|
|
# 4.48.0
|
|
target_milestone="${next_ver:1}"
|
|
# 79
|
|
target_milestone_number=$(gh api repos/:owner/:repo/milestones | jq -r ".[] | select(.title==\"$target_milestone\") | .number")
|
|
# patch-fleet-v4.48.0
|
|
target_branch="rc-patch-fleet-$next_ver"
|
|
if [[ "$minor" == "true" ]]; then
|
|
target_branch="rc-minor-fleet-$next_ver"
|
|
fi
|
|
update_changelog_prepare_branch="update-changelog-prepare-$target_milestone"
|
|
|
|
# fleet-v4.48.0
|
|
next_tag="fleet-$next_ver"
|
|
|
|
if [[ "$target_milestone_number" == "" && "$announce_only" == "false" && $dry_run == false ]]; then
|
|
echo "Missing milestone $target_milestone, Please create one and tie tickets to the milestone to continue"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found milestone $target_milestone with number $target_milestone_number"
|
|
|
|
# ======================================
|
|
# Validation passed check for skip / one-off functions
|
|
# ======================================
|
|
|
|
if [ "$print_info" = "true" ]; then
|
|
if [ "$announce_only" = "false" ]; then
|
|
print_announce_info
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$do_tag" = "true" ]; then
|
|
if [ "$announce_only" = "false" ]; then
|
|
tag
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$release_notes" = "true" ]; then
|
|
if [ "$announce_only" = "false" ]; then
|
|
update_release_notes
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$publish_release" = "true" ]; then
|
|
publish
|
|
exit 0
|
|
fi
|
|
|
|
|
|
# ======================================
|
|
# Start of script unless running after cherry pick step a second time
|
|
# ======================================
|
|
|
|
if [ "$cherry_pick_resolved" = "false" ]; then
|
|
# TODO Fail if not found
|
|
if [ "$dry_run" = "false" ]; then
|
|
git fetch
|
|
git checkout $start_ver_tag
|
|
git pull origin $start_ver_tag
|
|
else
|
|
echo "DRYRUN: Would have checked out starting at $start_ver_tag"
|
|
fi
|
|
|
|
local_exists=$(git branch | $GREP_CMD $target_branch)
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
if [[ $local_exists != "" ]]; then
|
|
# Clear previous
|
|
git branch -D $target_branch
|
|
fi
|
|
git checkout -b $target_branch
|
|
else
|
|
echo "DRYRUN: Would have cleared / checked out new branch $target_branch"
|
|
fi
|
|
|
|
total_prs=()
|
|
|
|
issue_list=$(gh issue list -L 500 --search 'milestone:"'"$target_milestone"'"' --json number | jq -r '.[] | .number')
|
|
|
|
if [[ "$issue_list" == "" && "$dry_run" == "false" ]]; then
|
|
echo "Milestone $target_milestone has no target issues, please tie tickets to the milestone to continue"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Issue list for new patch $next_ver"
|
|
echo $issue_list
|
|
|
|
for issue in $issue_list; do
|
|
prs_for_issue=$(gh api repos/fleetdm/fleet/issues/$issue/timeline --paginate | jq -r '.[]' | $GREP_CMD "fleetdm/fleet/" | $GREP_CMD -oP "pulls\/\K(?:\d+)")
|
|
echo -n "https://github.com/fleetdm/fleet/issues/$issue"
|
|
if [[ "$prs_for_issue" == "" ]]; then
|
|
echo -n " - No PRs found."
|
|
fi
|
|
for val in $prs_for_issue; do
|
|
echo -n " $val"
|
|
total_prs+=("$val")
|
|
done
|
|
echo
|
|
done
|
|
|
|
ask "Check any issues that have no pull requests, no to cancel and yes to continue? [y/N] "
|
|
|
|
commits=""
|
|
|
|
if [[ "$minor" == "false" || "$minor_cherry_pick" == "true" ]]; then
|
|
echo "Continuing to cherry-pick"
|
|
for pr in ${total_prs[*]};
|
|
do
|
|
output=$(gh pr view $pr --json state,mergeCommit,baseRefName)
|
|
state=$(echo $output | jq -r .state)
|
|
commit=$(echo $output | jq -r .mergeCommit.oid)
|
|
target_pr_branch=$(echo $output | jq -r .baseRefName)
|
|
echo -n "$pr $state $commit $target_pr_branch:"
|
|
if [[ "$state" != "MERGED" || "$target_pr_branch" != "main" ]]; then
|
|
echo " WARNING - Skipping pr https://github.com/fleetdm/fleet/pull/$pr"
|
|
else
|
|
if [[ "$commit" != "" && "$commit" != "null" ]]; then
|
|
echo " Commit looks valid - $commit, adding to cherry-pick"
|
|
commits+="$commit "
|
|
else
|
|
echo " WARNING - invalid commit for pr https://github.com/fleetdm/fleet/pull/$pr - $commit"
|
|
fi
|
|
fi
|
|
#echo "======================================="
|
|
done
|
|
|
|
for commit in $commits;
|
|
do
|
|
# echo $commit
|
|
timestamp=$(git log -n 1 --pretty=format:%at $commit)
|
|
if [ $? -ne 0 ]; then
|
|
echo "Failed to identify $commit, exiting"
|
|
exit 1
|
|
fi
|
|
# echo $timestamp
|
|
time_map[$timestamp]=$commit
|
|
done
|
|
|
|
timestamps=""
|
|
for key in "${!time_map[@]}"; do
|
|
timestamps+="$key\n"
|
|
done
|
|
for ts in $(echo -e $timestamps | sort); do
|
|
commit_hash="${time_map[$ts]}"
|
|
# echo "# $ts $commit_hash"
|
|
if git branch --contains "$commit_hash" | $GREP_CMD -q "$(git rev-parse --abbrev-ref HEAD)"; then
|
|
echo "# Commit $commit_hash is on the current branch."
|
|
is_on_current_branch=true
|
|
else
|
|
# echo "# Commit $commit_hash is not on the current branch."
|
|
if [[ "$failed" == "false" ]]; then
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
git cherry-pick $commit_hash
|
|
if [ $? -ne 0 ]; then
|
|
echo "Cherry pick of $commit_hash failed. Please resolve then continue the cherry-picks manually"
|
|
failed=true
|
|
fi
|
|
else
|
|
echo "DRYRUN: Would have cherry picked $commit_hash"
|
|
fi
|
|
else
|
|
echo "git cherry-pick $commit_hash"
|
|
fi
|
|
is_on_current_branch=false
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# ======================================
|
|
# Automatic cherry pick succeeded or cherry picked manually; continue
|
|
# ======================================
|
|
|
|
if [[ "$failed" == "false" ]]; then
|
|
if [ "$dry_run" = "false" ]; then
|
|
# have to push so we can make the PR's back
|
|
git push origin $target_branch
|
|
ask "Did git push work? [y/n]"
|
|
fi
|
|
|
|
|
|
build_changelog
|
|
|
|
# Create PR for changelog and version to release
|
|
changelog_and_versions $update_changelog_prepare_branch $target_branch
|
|
|
|
ask "Did first changelog work? [y/n]"
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
# Create PR for changelog and version to main
|
|
git checkout main
|
|
git pull origin main
|
|
ask "Are you on main? [y/n]"
|
|
changelog_and_versions "$update_changelog_prepare_branch"-main main
|
|
git checkout $target_branch
|
|
else
|
|
echo "DRYRUN: Would have switched to main and pulled latest"
|
|
fi
|
|
|
|
# Check for QA issue
|
|
create_qa_issue
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
echo "Waiting for github actions to propogate..."
|
|
show_spinner 200
|
|
fi
|
|
|
|
# For announce in #help-engineering
|
|
print_announce_info
|
|
else
|
|
# TODO echo what to do
|
|
echo "Placeholder, Cherry pick failed....figure out what to do..."
|
|
exit 1
|
|
fi
|
|
|