mirror of
https://github.com/fleetdm/fleet
synced 2026-05-21 16:08:47 +00:00
881 lines
36 KiB
Bash
Executable file
881 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, --main_release This is a release based off of main and not a tagged patch."
|
|
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 " -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 ""
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
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
|
|
prompt=$'I am creating a changelog for an open source project from a list of commit messages. Please format it for me using the following rules:\n1. Correct spelling and punctuation.\n2. Sentence casing.\n3. Past tense.\n4. Each list item is designated with an asterisk.\n5. Output in markdown format.'
|
|
if [[ "$main_release" == "true" ]]; then
|
|
# Place to make a main targeted prompt
|
|
prompt=$'I am creating a changelog for an open source project from a list of commit messages. Please format it for me using the following rules: Organize updates into three categories: Endpoint Operations, Device Management (MDM), and Vulnerability Management, with all bug fixes and misc. improvements listed under "Bug fixes and improvements". Start each entry with a past tense verb, using hyphens for bullet points. Include specific details for new features, bug fixes, API changes, and any necessary user actions. Note changes in user interfaces, system feedback, and significant architectural updates. Highlight mandatory actions and major impacts, especially for system administrators. Order seemingly important features at the top of their respective lists.'
|
|
fi
|
|
|
|
content=$(cat new_changelog | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g')
|
|
question="${prompt}\n\n${content}"
|
|
|
|
# API endpoint for ChatGPT
|
|
api_endpoint="https://api.openai.com/v1/chat/completions"
|
|
output="null"
|
|
|
|
while [[ "$output" == "null" ]]; do
|
|
data_payload=$(jq -n \
|
|
--arg prompt "$question" \
|
|
--arg model "gpt-3.5-turbo" \
|
|
'{model: $model, messages: [{"role": "user", "content": $prompt}]}')
|
|
|
|
response=$(curl -s -X POST $api_endpoint \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $open_api_key" \
|
|
--data "$data_payload")
|
|
|
|
output=`echo $response | jq -r .choices[0].message.content`
|
|
echo "${output}"
|
|
done
|
|
|
|
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"
|
|
if [ "$force" = "false" ]; then
|
|
read -r -p "Does the above changelog look good (edit temp_changelog now to make changes) (n exits)? [y/N] " response
|
|
case "$response" in
|
|
[yY][eE][sS]|[yY])
|
|
echo
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
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-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";;
|
|
Darwin*) echo "$version_files" | xargs sed -i '' "s/$escaped_start_version/$target_milestone/g";;
|
|
*) 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 "georgekarrv" --assignee "xpkoala" --label ":release" --label "#g-mdm" --label "#g-endpoint-ops"
|
|
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_patch_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 [[ "$main_release" == "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=""
|
|
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 [[ "$main_release" == "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`
|
|
if [[ "$main_release" == "true" && "$current_branch" != "main" ]]; then
|
|
echo "Can't tag main release if you aren't on 'main'"
|
|
exit 1
|
|
fi
|
|
if [[ "$main_release" == "true" ]]; then
|
|
# in main
|
|
found_version=`cat CHANGELOG.md | $GREP_CMD $target_milestone`
|
|
if [[ "$found_version" == "" ]]; then
|
|
echo "Can't tag main if CHANGELOG pr has not been merged yet"
|
|
exit 1
|
|
fi
|
|
else
|
|
# patch release
|
|
if [[ "$current_branch" != "$target_patch_branch" ]]; then
|
|
echo "Can't tag patch release if you aren't on '$target_patch_branch'"
|
|
exit 1
|
|
fi
|
|
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
|
|
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'`
|
|
cd tools/fleetctl-npm && npm publish
|
|
|
|
issues=`gh issue list -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 we have all commands required to perform this script
|
|
check_required_binaries
|
|
|
|
# Initialize variables for the options
|
|
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
|
|
main_release=false
|
|
quiet=false
|
|
|
|
# Parse long options manually
|
|
for arg in "$@"; do
|
|
shift
|
|
case "$arg" in
|
|
"--main_release") 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" ;;
|
|
"--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 "acdfhgmno:pqrs:t:uv:" opt; do
|
|
case "$opt" in
|
|
a) main_release=true ;;
|
|
c) cherry_pick_resolved=true ;;
|
|
d) dry_run=true ;;
|
|
f) force=true ;;
|
|
h) usage; exit 0 ;;
|
|
g) do_tag=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
|
|
|
|
# Shift off the options and optional --
|
|
shift $((OPTIND -1))
|
|
|
|
# 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)
|
|
|
|
# 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 [[ "$main_release" == "true" ]]; then
|
|
# Main releases are always minor releases
|
|
minor=true
|
|
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 [[ "$main_release" == "true" ]]; then
|
|
echo "Main release from $start_version to $next_ver"
|
|
start_ver_tag=main
|
|
else
|
|
echo "Patch release from $start_version to $next_ver"
|
|
fi
|
|
|
|
if [ "$force" = "false" ]; then
|
|
read -r -p "If this is correct confirm yes to continue? [y/N] " response
|
|
case "$response" in
|
|
[yY][eE][sS]|[yY])
|
|
echo
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
# 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_patch_branch="patch-fleet-$next_ver"
|
|
if [[ "$main_release" == "true" ]]; then
|
|
target_patch_branch="prepare-fleet-$next_ver"
|
|
fi
|
|
|
|
# fleet-v4.48.0
|
|
next_tag="fleet-$next_ver"
|
|
|
|
if [[ "$target_milestone_number" == "" ]]; 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"
|
|
|
|
if [ "$print_info" = "true" ]; then
|
|
print_announce_info
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$do_tag" = "true" ]; then
|
|
tag
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$release_notes" = "true" ]; then
|
|
update_release_notes
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$publish_release" = "true" ]; then
|
|
publish
|
|
exit 0
|
|
fi
|
|
|
|
|
|
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 tag $start_ver_tag"
|
|
fi
|
|
|
|
local_exists=`git branch | $GREP_CMD $target_patch_branch`
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
if [[ $local_exists != "" ]]; then
|
|
# Clear previous
|
|
git branch -D $target_patch_branch
|
|
fi
|
|
git checkout -b $target_patch_branch
|
|
else
|
|
echo "DRYRUN: Would have cleared / checked out new branch $target_patch_branch"
|
|
fi
|
|
|
|
total_prs=()
|
|
|
|
issue_list=`gh issue list --search 'milestone:"'"$target_milestone"'"' --json number | jq -r '.[] | .number'`
|
|
if [[ "$issue_list" == "" ]]; 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 PR's found, please verify they are not missing in the issue, if no PR's were required for this ticket please reconsider adding it to this release."
|
|
fi
|
|
for val in $prs_for_issue; do
|
|
echo -n " $val"
|
|
total_prs+=("$val")
|
|
done
|
|
echo
|
|
done
|
|
|
|
|
|
if [ "$force" = "false" ]; then
|
|
read -r -p "Check any issues that have no pull requests, no to cancel and yes to continue? [y/N] " response
|
|
case "$response" in
|
|
[yY][eE][sS]|[yY])
|
|
echo
|
|
;;
|
|
*)
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
commits=""
|
|
|
|
if [[ "$main_release" == "false" ]]; 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_branch=`echo $output | jq -r .baseRefName`
|
|
echo -n "$pr $state $commit $target_branch:"
|
|
if [[ "$state" != "MERGED" || "$target_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
|
|
|
|
if [[ "$failed" == "false" ]]; then
|
|
if [ "$dry_run" = "false" ]; then
|
|
# have to push so we can make the PR's back
|
|
git push origin $target_patch_branch -f
|
|
fi
|
|
|
|
build_changelog
|
|
|
|
if [[ "$main_release" == "false" ]]; then
|
|
# Create PR for changelog and version to patch
|
|
update_changelog_patch_branch="update-changelog-pb-$target_milestone"
|
|
changelog_and_versions $update_changelog_patch_branch $target_patch_branch
|
|
fi
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
# Create PR for changelog and version to main
|
|
git checkout main
|
|
git pull origin main
|
|
else
|
|
echo "DRYRUN: Would have switched to main and pulled latest"
|
|
fi
|
|
update_changelog_branch="update-changelog-$target_milestone"
|
|
changelog_and_versions $update_changelog_branch main
|
|
|
|
if [ "$dry_run" = "false" ]; then
|
|
# Back on patch / prepare
|
|
git checkout $target_patch_branch
|
|
else
|
|
echo "DRYRUN: Would have switched back to branch $target_patch_branch"
|
|
fi
|
|
|
|
if [[ "$main_release" == "false" ]]; then
|
|
# Cherry-pick from update-changelog-branch
|
|
ch_commit=`git log -n 1 --pretty=format:"%H" $update_changelog_branch`
|
|
git cherry-pick $ch_commit
|
|
git push origin $target_patch_branch -f
|
|
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
|
|
|