Add support for versioned releases (#13750)
* Add release scripts * Use `delete` over `gsub` with an empty string Co-authored-by: Michael Kohl <citizen428@dev.to> * wip * wip * Include stable release-channel branches in builds * Add changelog stub * Include the union of changes to the changelog * Add things * Add stuff * Remove test changelog content * Fix String#delete call Extraneous `.` made Ruby parse it as a range * Fix suggested release command * Use backticks to denote a command to run * Add debugging output to script * Build more appropriate containers for stable This includes tags * Check if branch *starts with* release channel name * Cache tag builds from production tag * Skip trying to build containers for untagged pushes * Clear out stubbed changelog * Update PR template to incorporate CHANGELOG.md * Run Rubocop on release scripts This excludes some linter rules that only make sense in code that is running as part of the Rails app. For example, we can't rely on `ActiveSupport::TimeWithZone` because these scripts don't load Rails, and we define top-level methods because they're scripts rather than complex applications. Co-authored-by: Michael Kohl <citizen428@dev.to>
This commit is contained in:
parent
71fc58c497
commit
f0edbcef90
7 changed files with 226 additions and 0 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -8,3 +8,4 @@
|
|||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.erb diff=html
|
||||
/CHANGELOG.md merge=union
|
||||
|
|
|
|||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -60,6 +60,8 @@ will share this change with the people who need to know about it._
|
|||
[Admin Guide](https://admin.forem.com/), or
|
||||
[Storybook](https://storybook.forem.com/) (for Crayons components)
|
||||
- [ ] I've updated the README or added inline documentation
|
||||
- [ ] I've added an entry to
|
||||
[`CHANGELOG.md`](https://github.com/forem/forem/tree/main/CHANGELOG.md)
|
||||
- [ ] I will share this change in a [Changelog](https://forem.dev/t/changelog)
|
||||
or in a [forem.dev](http://forem.dev) post
|
||||
- [ ] I will share this change internally with the appropriate teams
|
||||
|
|
|
|||
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## 2021-07-23
|
||||
|
||||
Initial versioned release
|
||||
|
|
@ -44,6 +44,9 @@
|
|||
"{app,spec,config,lib}/**/*.rb": [
|
||||
"bundle exec rubocop --require rubocop-rspec --auto-correct"
|
||||
],
|
||||
"scripts/{release,stage_release}": [
|
||||
"bundle exec rubocop --require rubocop-rspec --auto-correct --except Style/TopLevelMethodDefinition,Rails/Date"
|
||||
],
|
||||
"app/views/**/*.jbuilder": [
|
||||
"bundle exec rubocop --require rubocop-rspec --auto-correct"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -102,6 +102,56 @@ function create_production_containers {
|
|||
|
||||
}
|
||||
|
||||
function create_release_containers {
|
||||
BRANCH=$1
|
||||
|
||||
# Pull images if available for caching
|
||||
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder ||:
|
||||
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":production ||:
|
||||
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing ||:
|
||||
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":development ||:
|
||||
|
||||
# Build the builder image
|
||||
docker build --target builder \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder .
|
||||
|
||||
# Build the production image
|
||||
docker build --target production \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_COMMIT:0:7} \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BRANCH} .
|
||||
|
||||
# Build the testing image
|
||||
docker build --target testing \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-${BRANCH} .
|
||||
|
||||
# Build the development image
|
||||
docker build --target development \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":development-${BRANCH} .
|
||||
|
||||
# If the env var for the git tag doesn't exist or is an empty string, then we
|
||||
# won't build a container image for a cut release.
|
||||
if [ -v BUILDKITE_TAG || ! -z "${BUILDKITE_TAG}" ]; then
|
||||
docker build --target production \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
|
||||
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
|
||||
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_TAG} .
|
||||
fi
|
||||
|
||||
# Push images to Quay
|
||||
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BRANCH}
|
||||
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":development-${BRANCH}
|
||||
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-${BRANCH}
|
||||
|
||||
}
|
||||
|
||||
function prune_containers {
|
||||
|
||||
|
|
@ -111,6 +161,11 @@ function prune_containers {
|
|||
|
||||
trap prune_containers ERR INT EXIT
|
||||
|
||||
echo "Branch: $BUILDKITE_BRANCH"
|
||||
echo "PR : $BUILDKITE_PULL_REQUEST"
|
||||
echo "Commit: $BUILDKITE_COMMIT"
|
||||
echo "Tag : $BUILDKITE_TAG"
|
||||
|
||||
if [ ! -v BUILDKITE_BRANCH ]; then
|
||||
|
||||
echo "Not running in Buildkite. Building Production Containers..."
|
||||
|
|
@ -127,6 +182,11 @@ elif [[ "${BUILDKITE_BRANCH}" = "master" || "${BUILDKITE_BRANCH}" = "main" ]]; t
|
|||
echo "Building Production Containers..."
|
||||
create_production_containers
|
||||
|
||||
elif [[ ${BUILDKITE_BRANCH} = stable* ]]; then
|
||||
|
||||
echo "Building Production Containers for ${BUILDKITE_BRANCH}..."
|
||||
create_release_containers "${BUILDKITE_BRANCH}"
|
||||
|
||||
else
|
||||
|
||||
if [ ! -v BUILDKITE_PULL_REQUEST ]; then
|
||||
|
|
|
|||
93
scripts/release
Executable file
93
scripts/release
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "date"
|
||||
require "optparse"
|
||||
|
||||
def run(command, abort_on_fail: true)
|
||||
puts "> #{command}" if $VERBOSE
|
||||
|
||||
system(command, exception: abort_on_fail)
|
||||
rescue StandardError => e
|
||||
raise "#{command.inspect} failed: #{e}"
|
||||
end
|
||||
|
||||
channel = ENV.fetch("FOREM_RELEASE_CHANNEL", "stable")
|
||||
date = ENV.fetch("FOREM_RELEASE_DATE", Date.today.to_s).delete("-")
|
||||
hotfix = ENV.fetch("FOREM_RELEASE_HOTFIX", 0)
|
||||
remote = ENV.fetch("FOREM_RELEASE_REMOTE", "origin")
|
||||
source = ENV.fetch("FOREM_STAGING_FROM", "#{channel}-next")
|
||||
|
||||
OptionParser.new ARGV.dup do |options|
|
||||
options.banner = <<~USAGE
|
||||
Usage: #{$PROGRAM_NAME} [options]
|
||||
|
||||
Releases are named CHANNEL.DATE.HOTFIX_COUNT. Example: #{channel}.#{date}.#{hotfix}
|
||||
|
||||
USAGE
|
||||
|
||||
options.on "-c", "--channel CHANNEL",
|
||||
"Specify the release channel (defaults to #{channel.inspect})" do |release_channel|
|
||||
channel = release_channel
|
||||
end
|
||||
|
||||
options.on "-d", "--date DATE", "Specify the release date (defaults to today)" do |release_date|
|
||||
date = release_date.delete("-")
|
||||
end
|
||||
|
||||
options.on "-f", "--from BRANCH/COMMIT/TAG",
|
||||
"Specify the source branch for this release (defaults to #{source.inspect})" do |from_branch|
|
||||
source = from_branch
|
||||
end
|
||||
|
||||
options.on "-h", "--hotfix HOTFIX",
|
||||
"Specify the hotfix count for this release branch (defaults to #{hotfix.inspect})" do |hotfix_opt|
|
||||
hotfix = hotfix_opt
|
||||
end
|
||||
|
||||
options.on "-r", "--remote REMOTE", "Git remote to push to (defaults to #{remote.inspect})" do |git_remote|
|
||||
remote = git_remote
|
||||
end
|
||||
|
||||
options.on "-v", "--verbose", "Enable verbose mode" do
|
||||
$VERBOSE = true
|
||||
end
|
||||
end.parse!
|
||||
|
||||
original_branch = `git branch --show-current`.strip
|
||||
|
||||
begin
|
||||
tag = "#{channel}.#{date}.#{hotfix}"
|
||||
puts "Building #{tag}..."
|
||||
|
||||
[source, channel].each do |branch|
|
||||
run "git checkout --quiet #{branch}"
|
||||
run "git pull --quiet --ff-only #{remote} #{branch}"
|
||||
puts "Pulled latest #{branch.inspect}"
|
||||
end
|
||||
|
||||
run "git merge --quiet #{source} --no-ff --message 'Merge #{source} into #{channel}'"
|
||||
puts "Merged #{source} into #{channel}"
|
||||
|
||||
begin
|
||||
run "git tag #{tag}"
|
||||
puts "Tagged #{tag}"
|
||||
rescue StandardError
|
||||
# If `git tag` fails, it's because the tag already exists. In this case, git
|
||||
# will have already output an error that looks like this:
|
||||
#
|
||||
# fatal: tag 'stable.20210512.0' already exists
|
||||
#
|
||||
warn "Should the hotfix count be set to something other than #{hotfix}?" \
|
||||
"Or maybe you need to delete the tag locally (hint: `git tag --delete #{tag}`)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
run "git push --quiet #{remote} #{channel}"
|
||||
puts "Pushed #{channel} to #{remote}"
|
||||
|
||||
run "git push --quiet #{remote} #{tag}"
|
||||
puts "Pushed #{tag} to #{remote}"
|
||||
ensure
|
||||
# Go back to the branch we were on before running this script
|
||||
run "git checkout --quiet #{original_branch}"
|
||||
end
|
||||
64
scripts/stage_release
Executable file
64
scripts/stage_release
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "date"
|
||||
require "optparse"
|
||||
|
||||
def run(command, abort_on_fail: true)
|
||||
puts "> #{command}" if $VERBOSE
|
||||
|
||||
system(command, exception: abort_on_fail)
|
||||
rescue StandardError => e
|
||||
raise "#{command.inspect} failed: #{e}"
|
||||
end
|
||||
|
||||
remote = ENV.fetch("FOREM_RELEASE_REMOTE", "origin")
|
||||
source = ENV.fetch("FOREM_RELEASE_FROM", "main")
|
||||
target = "#{ENV.fetch('FOREM_RELEASE_CHANNEL', 'stable')}-next"
|
||||
|
||||
OptionParser.new ARGV.dup do |options|
|
||||
options.banner = <<~USAGE
|
||||
Usage: #{$PROGRAM_NAME} [options]
|
||||
|
||||
Staging banches are named CHANNEL-next. Example: #{target}
|
||||
|
||||
USAGE
|
||||
|
||||
options.on "-c", "--channel CHANNEL",
|
||||
"Specify the release channel (defaults to #{target.sub(/-next$/, '').inspect})" do |release_channel|
|
||||
target = "#{release_channel}-next"
|
||||
end
|
||||
|
||||
options.on "-f", "--from BRANCH/COMMIT/TAG",
|
||||
"Specify the source branch for this release (defaults to #{source.inspect})" do |from_branch|
|
||||
source = from_branch
|
||||
end
|
||||
|
||||
options.on "-r", "--remote REMOTE", "Git remote to push to (defaults to #{remote.inspect})" do |git_remote|
|
||||
remote = git_remote
|
||||
end
|
||||
|
||||
options.on "-v", "--verbose", "Enable verbose mode" do
|
||||
$VERBOSE = true
|
||||
end
|
||||
end.parse!
|
||||
|
||||
original_branch = `git branch --show-current`.strip
|
||||
|
||||
begin
|
||||
puts "Fetching from #{remote}..."
|
||||
[source, target].each do |branch|
|
||||
run "git checkout --quiet #{branch}"
|
||||
run "git pull --ff-only --quiet #{remote} #{branch}"
|
||||
puts "Pulled latest #{branch.inspect}"
|
||||
end
|
||||
|
||||
run "git merge --quiet #{source}"
|
||||
puts "Merged latest #{source} into #{target}"
|
||||
|
||||
run "git push --quiet #{remote} #{target}"
|
||||
puts "Pushed #{target}, run the following to release:"
|
||||
puts " scripts/release --channel #{target.sub(/-next$/, '')} --remote #{remote} --from #{target}"
|
||||
ensure
|
||||
# Go back to the branch we were on before running this script
|
||||
run "git checkout --quiet #{original_branch}"
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue