summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 12:26:25 +0000
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /bin
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
downloadgitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'bin')
-rwxr-xr-xbin/changelog21
-rwxr-xr-xbin/feature-flag291
-rwxr-xr-xbin/rspec-stackprof3
3 files changed, 304 insertions, 11 deletions
diff --git a/bin/changelog b/bin/changelog
index 45b6295e331..bdf159a0a22 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -8,16 +8,6 @@
require 'optparse'
require 'yaml'
-Options = Struct.new(
- :amend,
- :author,
- :dry_run,
- :force,
- :merge_request,
- :title,
- :type,
- :ee
-)
INVALID_TYPE = -1
module ChangelogHelpers
@@ -40,6 +30,17 @@ end
class ChangelogOptionParser
extend ChangelogHelpers
+ Options = Struct.new(
+ :amend,
+ :author,
+ :dry_run,
+ :force,
+ :merge_request,
+ :title,
+ :type,
+ :ee
+ )
+
Type = Struct.new(:name, :description)
TYPES = [
Type.new('added', 'New feature'),
diff --git a/bin/feature-flag b/bin/feature-flag
new file mode 100755
index 00000000000..46d93a11ebd
--- /dev/null
+++ b/bin/feature-flag
@@ -0,0 +1,291 @@
+#!/usr/bin/env ruby
+#
+# Generate a feature flag entry file in the correct location.
+#
+# Automatically stages the file and amends the previous commit if the `--amend`
+# argument is used.
+
+require 'optparse'
+require 'yaml'
+require 'fileutils'
+require 'cgi'
+
+require_relative '../lib/feature/shared' unless defined?(Feature::Shared)
+
+module FeatureFlagHelpers
+ Abort = Class.new(StandardError)
+ Done = Class.new(StandardError)
+
+ def capture_stdout(cmd)
+ output = IO.popen(cmd, &:read)
+ fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
+ output
+ end
+
+ def fail_with(message)
+ raise Abort, "\e[31merror\e[0m #{message}"
+ end
+end
+
+class FeatureFlagOptionParser
+ extend FeatureFlagHelpers
+ extend ::Feature::Shared
+
+ Options = Struct.new(
+ :name,
+ :type,
+ :group,
+ :ee,
+ :amend,
+ :dry_run,
+ :force,
+ :introduced_by_url,
+ :rollout_issue_url
+ )
+
+ class << self
+ def parse(argv)
+ options = Options.new
+
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options] <feature-flag>\n\n"
+
+ # Note: We do not provide a shorthand for this in order to match the `git
+ # commit` interface
+ opts.on('--amend', 'Amend the previous commit') do |value|
+ options.amend = value
+ end
+
+ opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
+ options.force = value
+ end
+
+ opts.on('-m', '--introduced-by-url [string]', String, 'URL to Merge Request introducing Feature Flag') do |value|
+ options.introduced_by_url = value
+ end
+
+ opts.on('-i', '--rollout-issue-url [string]', String, 'URL to Issue rolling out Feature Flag') do |value|
+ options.rollout_issue_url = value
+ end
+
+ opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
+ options.dry_run = value
+ end
+
+ opts.on('-g', '--group [string]', String, "The group introducing a feature flag, like: `group::apm`") do |value|
+ options.group = value if value.start_with?('group::')
+ end
+
+ opts.on('-t', '--type [string]', String, "The category of the feature flag, valid options are: #{TYPES.keys.map(&:to_s).join(', ')}") do |value|
+ options.type = value.to_sym if TYPES[value.to_sym]
+ end
+
+ opts.on('-e', '--ee', 'Generate a feature flag entry for GitLab EE') do |value|
+ options.ee = value
+ end
+
+ opts.on('-h', '--help', 'Print help message') do
+ $stdout.puts opts
+ raise Done.new
+ end
+ end
+
+ parser.parse!(argv)
+
+ unless argv.one?
+ $stdout.puts parser.help
+ $stdout.puts
+ raise Abort, 'Feature flag name is required'
+ end
+
+ # Name is a first name
+ options.name = argv.first
+
+ options
+ end
+
+ def read_group
+ $stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:"
+
+ loop do
+ $stdout.print "\n?> "
+ group = $stdin.gets.strip
+ group = nil if group.empty?
+ return group if group.nil? || group.start_with?('group::')
+
+ $stderr.puts "Group needs to include `group::`"
+ end
+ end
+
+ def read_type
+ $stdout.puts ">> Please specify the type of your feature flag:"
+ $stdout.puts
+ TYPES.each do |type, data|
+ $stdout.puts "#{type.to_s.rjust(15)}#{' '*6}#{data[:description]}"
+ end
+
+ loop do
+ $stdout.print "\n?> "
+
+ type = $stdin.gets.strip.to_sym
+ return type if TYPES[type]
+
+ $stderr.puts "Invalid type specified '#{type}'"
+ end
+ end
+
+ def read_issue_url(options)
+ return unless TYPES.dig(options.type, :rollout_issue)
+
+ url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new"
+ title = "[Feature flag] Rollout of `#{options.name}`"
+ description = File.read('.gitlab/issue_templates/Feature Flag Roll Out.md')
+ description.sub!(':feature_name', options.name)
+
+ issue_new_url = url + "?" +
+ "issue[title]=" + CGI.escape(title) + "&"
+ # TODO: We should be able to pick `issueable_template`
+ # + "issue[description]=" + CGI.escape(description)
+
+ $stdout.puts ">> Open this URL and fill the rest of details:"
+ $stdout.puts issue_new_url
+ $stdout.puts
+
+ $stdout.puts ">> Paste URL here, or enter to skip:"
+
+ loop do
+ $stdout.print "\n?> "
+ created_url = $stdin.gets.strip
+ created_url = nil if created_url.empty?
+ return created_url if created_url.nil? || created_url.start_with?('https://')
+
+ $stderr.puts "URL needs to start with https://"
+ end
+ end
+ end
+end
+
+class FeatureFlagCreator
+ include FeatureFlagHelpers
+
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def execute
+ assert_feature_branch!
+ assert_name!
+ assert_existing_feature_flag!
+
+ # Read type from $stdin unless is already set
+ options.type ||= FeatureFlagOptionParser.read_type
+ options.group ||= FeatureFlagOptionParser.read_group
+ options.rollout_issue_url ||= FeatureFlagOptionParser.read_issue_url(options)
+
+ $stdout.puts "\e[32mcreate\e[0m #{file_path}"
+ $stdout.puts contents
+
+ unless options.dry_run
+ write
+ amend_commit if options.amend
+ end
+
+ if editor
+ system("#{editor} '#{file_path}'")
+ end
+ end
+
+ private
+
+ def contents
+ YAML.dump(
+ 'name' => options.name,
+ 'introduced_by_url' => options.introduced_by_url,
+ 'rollout_issue_url' => options.rollout_issue_url,
+ 'group' => options.group.to_s,
+ 'type' => options.type.to_s,
+ 'default_enabled' => false
+ ).strip
+ end
+
+ def write
+ FileUtils.mkdir_p(File.dirname(file_path))
+ File.write(file_path, contents)
+ end
+
+ def editor
+ ENV['EDITOR']
+ end
+
+ def amend_commit
+ fail_with "git add failed" unless system(*%W[git add #{file_path}])
+
+ Kernel.exec(*%w[git commit --amend])
+ end
+
+ def assert_feature_branch!
+ return unless branch_name == 'master'
+
+ fail_with "Create a branch first!"
+ end
+
+ def assert_existing_feature_flag!
+ existing_path = all_feature_flag_names[options.name]
+ return unless existing_path
+ return if options.force
+
+ fail_with "#{existing_path} already exists! Use `--force` to overwrite."
+ end
+
+ def assert_name!
+ return if options.name.match(/\A[a-z0-9_-]+\Z/)
+
+ fail_with "Provide a name for the feature flag that is [a-z0-9_-]"
+ end
+
+ def file_path
+ feature_flags_paths.last
+ .sub('**', options.type.to_s)
+ .sub('*.yml', options.name + '.yml')
+ end
+
+ def all_feature_flag_names
+ @all_feature_flag_names ||=
+ feature_flags_paths.map do |glob_path|
+ Dir.glob(glob_path).map do |path|
+ [File.basename(path, '.yml'), path]
+ end
+ end.flatten(1).to_h
+ end
+
+ def feature_flags_paths
+ paths = []
+ paths << File.join('config', 'feature_flags', '**', '*.yml')
+ paths << File.join('ee', 'config', 'feature_flags', '**', '*.yml') if ee?
+ paths
+ end
+
+ def ee?
+ options.ee
+ end
+
+ def branch_name
+ @branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
+ end
+end
+
+if $0 == __FILE__
+ begin
+ options = FeatureFlagOptionParser.parse(ARGV)
+ FeatureFlagCreator.new(options).execute
+ rescue FeatureFlagHelpers::Abort => ex
+ $stderr.puts ex.message
+ exit 1
+ rescue FeatureFlagHelpers::Done
+ exit
+ end
+end
+
+# vim: ft=ruby
diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof
index 8058d165196..3bef45c607c 100755
--- a/bin/rspec-stackprof
+++ b/bin/rspec-stackprof
@@ -8,9 +8,10 @@ require 'spec_helper'
filename = ARGV[0].split('/').last
interval = ENV.fetch('INTERVAL', 1000).to_i
limit = ENV.fetch('LIMIT', 20)
+raw = ENV.fetch('RAW', false) == 'true'
output_file = "tmp/#{filename}.dump"
-StackProf.run(mode: :wall, out: output_file, interval: interval) do
+StackProf.run(mode: :wall, out: output_file, interval: interval, raw: raw) do
RSpec::Core::Runner.run(ARGV, $stderr, $stdout)
end