-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(CONT-1026) Add publish top-level command
To reduce complexity, this change introduces a new `publish` top-level command. The intention is to replace `pdk release` and `pdk release publish`
- Loading branch information
Showing
2 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
require 'pdk/cli/util' | ||
require 'pdk/validate' | ||
require 'pdk/util/bundler' | ||
require 'pdk/cli/util/interview' | ||
require 'pdk/util/changelog_generator' | ||
require 'pdk/module/build' | ||
|
||
module PDK | ||
module CLI | ||
@publish_cmd = @base_cmd.define_command do # rubocop:disable Metrics/BlockLength | ||
name 'publish' | ||
usage 'publish [options] <tarball>' | ||
summary 'Publishes the module <tarball> to the Forge.' | ||
|
||
flag nil, :force, 'Publish the module automatically, with no prompts.' | ||
flag nil, :'skip-validation', 'Skips the module validation check.' | ||
flag nil, :'skip-changelog', 'Skips the automatic changelog generation.' | ||
flag nil, :'skip-dependency', 'Skips the module dependency check.' | ||
flag nil, :'skip-documentation', 'Skips the documentation update.' | ||
flag nil, :'skip-build', 'Skips module build.' | ||
flag nil, :'skip-publish', 'Skips publishing the module to the forge.' | ||
|
||
option nil, :'forge-upload-url', 'Set forge upload url path.', | ||
argument: :required, default: 'https://forgeapi.puppetlabs.com/v3/releases' | ||
|
||
option nil, :'forge-token', 'Set Forge API token (you may also set via environment variable PDK_FORGE_TOKEN)', argument: :required | ||
|
||
run do |opts, _args, _cmd| | ||
# Make sure build is being run in a valid module directory with a metadata.json | ||
PDK::CLI::Util.ensure_in_module!( | ||
message: '`pdk publish` can only be run from inside a valid module with a metadata.json.', | ||
log_level: :info | ||
) | ||
opts[:force] = true unless PDK::CLI::Util.interactive? | ||
opts[:'forge-token'] ||= PDK::Util::Env['PDK_FORGE_TOKEN'] | ||
|
||
if opts[:'forge-token'].nil? || opts[:'forge-token'].empty? | ||
PDK.logger.error 'You must supply a Forge API token either via `--forge-token` option or PDK_FORGE_TOKEN environment variable.' | ||
exit 1 | ||
end | ||
|
||
Release.prepare_interview(opts) unless opts[:force] | ||
|
||
Release.send_analytics('publish', opts) | ||
|
||
release = PDK::Module::Release.new(nil, opts) | ||
|
||
release.run | ||
end | ||
|
||
module Release # rubocop:disable Lint/ConstantDefinitionInBlock | ||
# Checks whether the module is compatible with PDK release process | ||
# @param release PDK::Module::Release Object representing the release | ||
# @param opts Options Hash from Cri | ||
def self.module_compatibility_checks!(release, opts) | ||
unless release.module_metadata.forge_ready? | ||
if opts[:force] | ||
PDK.logger.warn "This module is missing the following fields in the metadata.json: #{release.module_metadata.missing_fields.join(', ')}. " \ | ||
'These missing fields may affect the visibility of the module on the Forge.' | ||
else | ||
release.module_metadata.interview_for_forge! | ||
release.write_module_metadata! | ||
end | ||
end | ||
|
||
unless release.pdk_compatible? # rubocop:disable Style/GuardClause Nope! | ||
if opts[:force] | ||
PDK.logger.warn 'This module is not compatible with PDK, so PDK can not validate or test this build.' | ||
else | ||
PDK.logger.info 'This module is not compatible with PDK, so PDK can not validate or test this build. ' \ | ||
'Unvalidated modules may have errors when uploading to the Forge. ' \ | ||
'To make this module PDK compatible and use validate features, cancel the build and run `pdk convert`.' | ||
unless PDK::CLI::Util.prompt_for_yes('Continue build without converting?') | ||
PDK.logger.info 'Build cancelled; exiting.' | ||
PDK::Util.exit_process(1) | ||
end | ||
end | ||
end | ||
end | ||
|
||
# Send_analytics for the given command and Cri options | ||
def self.send_analytics(command, opts) | ||
# Don't pass tokens to analytics | ||
PDK::CLI::Util.analytics_screen_view(command, opts.reject { |k, _| k == :'forge-token' }) | ||
end | ||
|
||
def self.prepare_interview(opts) | ||
questions = [] | ||
|
||
unless opts[:'skip-validation'] | ||
questions << { | ||
name: 'validation', | ||
question: 'Do you want to run the module validation ?', | ||
type: :yes | ||
} | ||
end | ||
unless opts[:'skip-changelog'] | ||
questions << { | ||
name: 'changelog', | ||
question: 'Do you want to run the automatic changelog generation ?', | ||
type: :yes | ||
} | ||
end | ||
unless opts[:version] | ||
questions << { | ||
name: 'setversion', | ||
question: 'Do you want to set the module version ?', | ||
type: :yes | ||
} | ||
end | ||
unless opts[:'skip-dependency'] | ||
questions << { | ||
name: 'dependency', | ||
question: 'Do you want to run the dependency-checker on this module?', | ||
type: :yes | ||
} | ||
end | ||
unless opts[:'skip-documentation'] | ||
questions << { | ||
name: 'documentation', | ||
question: 'Do you want to update the documentation for this module?', | ||
type: :yes | ||
} | ||
end | ||
unless opts[:'skip-publish'] | ||
questions << { | ||
name: 'publish', | ||
question: 'Do you want to publish the module on the Puppet Forge?', | ||
type: :yes | ||
} | ||
end | ||
|
||
prompt = TTY::Prompt.new(help_color: :cyan) | ||
interview = PDK::CLI::Util::Interview.new(prompt) | ||
interview.add_questions(questions) | ||
answers = interview.run | ||
|
||
unless answers.nil? | ||
opts[:'skip-validation'] = !answers['validation'] | ||
opts[:'skip-changelog'] = !answers['changelog'] | ||
opts[:'skip-dependency'] = !answers['dependency'] | ||
opts[:'skip-documentation'] = !answers['documentation'] | ||
opts[:'skip-publish'] = !answers['publish'] | ||
|
||
prepare_version_interview(prompt, opts) if answers['setversion'] | ||
|
||
prepare_publish_interview(prompt, opts) if answers['publish'] | ||
end | ||
answers | ||
end | ||
|
||
def self.prepare_version_interview(prompt, opts) | ||
questions = [ | ||
{ | ||
name: 'version', | ||
question: 'Please set the module version', | ||
help: 'This value is the version that will be used in the changelog generator and building of the module.', | ||
required: true, | ||
validate_pattern: /(\*|\d+(\.\d+){0,2}(\.\*)?)$/i, | ||
validate_message: 'The version format should be in the format x.y.z where x represents the major version, y the minor version and z the build number.' | ||
} | ||
] | ||
interview = PDK::CLI::Util::Interview.new(prompt) | ||
interview.add_questions(questions) | ||
ver_answer = interview.run | ||
opts[:version] = ver_answer['version'] | ||
end | ||
|
||
def self.prepare_publish_interview(prompt, opts) | ||
return if opts[:'forge-token'] | ||
|
||
questions = [ | ||
{ | ||
name: 'apikey', | ||
question: 'Please set the api key(authorization token) to upload on the Puppet Forge', | ||
help: 'This value is used for authentication on the Puppet Forge to upload your module tarball.', | ||
required: true | ||
} | ||
] | ||
interview = PDK::CLI::Util::Interview.new(prompt) | ||
interview.add_questions(questions) | ||
api_answer = interview.run | ||
opts[:'forge-token'] = api_answer['apikey'] | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
require 'spec_helper' | ||
require 'pdk/cli' | ||
|
||
describe 'PDK::CLI release publish' do | ||
let(:help_text) { a_string_matching(/^USAGE\s+pdk release publish/m) } | ||
let(:base_cli_args) { ['release', 'publish'] } | ||
|
||
context 'when not run from inside a module' do | ||
include_context 'run outside module' | ||
|
||
let(:cli_args) { base_cli_args } | ||
|
||
it 'exits with an error' do | ||
expect(logger).to receive(:error).with(a_string_matching(/must be run from inside a valid module/)) | ||
|
||
expect { PDK::CLI.run(cli_args) }.to exit_nonzero | ||
end | ||
|
||
it 'does not submit the command to analytics' do | ||
expect(analytics).not_to receive(:screen_view) | ||
|
||
expect { PDK::CLI.run(cli_args) }.to exit_nonzero | ||
end | ||
end | ||
|
||
context 'when run from inside a module' do | ||
let(:release_object) do | ||
instance_double( | ||
PDK::Module::Release, | ||
pdk_compatible?: true, | ||
module_metadata: mock_metadata_obj, | ||
run: nil | ||
) | ||
end | ||
|
||
let(:mock_metadata_obj) do | ||
instance_double( | ||
PDK::Module::Metadata, | ||
forge_ready?: true | ||
) | ||
end | ||
|
||
let(:cli_args) { base_cli_args << '--forge-token=cli123' } | ||
|
||
before do | ||
allow(PDK::CLI::Util).to receive(:ensure_in_module!).and_return(nil) | ||
allow(PDK::Module::Release).to receive(:new).and_return(release_object) | ||
allow(PDK::Util).to receive(:exit_process).and_raise('exit_process mock should not be called') | ||
end | ||
|
||
it 'calls PDK::Module::Release.run' do | ||
expect(release_object).to receive(:run) | ||
|
||
expect { PDK::CLI.run(cli_args.push('--force')) }.not_to raise_error | ||
end | ||
|
||
it 'skips all but publishing' do | ||
expect(PDK::Module::Release).to receive(:new).with( | ||
Object, | ||
hash_including( | ||
'skip-validation': true, | ||
'skip-changelog': true, | ||
'skip-dependency': true, | ||
'skip-documentation': true, | ||
'skip-build': true | ||
) | ||
) | ||
|
||
expect { PDK::CLI.run(cli_args.push('--force')) }.not_to raise_error | ||
end | ||
|
||
it 'does not start an interview when --force is used' do | ||
expect(PDK::CLI::Util::Interview).not_to receive(:new) | ||
|
||
PDK::CLI.run(cli_args.push('--force')) | ||
end | ||
|
||
it 'implicitly uses --force in non-interactive environments' do | ||
allow(PDK::CLI::Util).to receive(:interactive?).and_return(false) | ||
expect(PDK::Module::Release).to receive(:new).with(Object, hash_including(force: true)) | ||
|
||
expect { PDK::CLI.run(cli_args) }.not_to raise_error | ||
end | ||
|
||
context 'when not passed a forge-token on the command line' do | ||
let(:cli_args) { base_cli_args } | ||
|
||
it 'exits with an error' do | ||
expect(logger).to receive(:error).with(a_string_matching(/must supply a forge api token/i)) | ||
|
||
expect { PDK::CLI.run(cli_args) }.to exit_nonzero | ||
end | ||
|
||
context 'when passed a forge-token via PDK_FORGE_TOKEN' do | ||
before do | ||
allow(PDK::Util::Env).to receive(:[]).with('PDK_DISABLE_ANALYTICS').and_return(true) | ||
allow(PDK::Util::Env).to receive(:[]).with('PDK_FORGE_TOKEN').and_return('env123') | ||
end | ||
|
||
it 'uses forge-token from environment' do | ||
expect(PDK::Module::Release).to receive(:new).with(Object, hash_including('forge-token': 'env123')) | ||
|
||
expect { PDK::CLI.run(cli_args) }.not_to raise_error | ||
end | ||
end | ||
end | ||
|
||
context 'when passed a forge-token on both the command line and via PDK_FORGE_TOKEN' do | ||
let(:cli_args) { base_cli_args << '--forge-token=cli123' } | ||
|
||
before do | ||
allow(PDK::Util::Env).to receive(:[]).with('PDK_DISABLE_ANALYTICS').and_return(true) | ||
allow(PDK::Util::Env).to receive(:[]).with('PDK_FORGE_TOKEN').and_return('env123') | ||
end | ||
|
||
it 'value from command line takes precedence' do | ||
expect(PDK::Module::Release).to receive(:new).with(Object, hash_including('forge-token': 'cli123')) | ||
|
||
expect { PDK::CLI.run(cli_args) }.not_to raise_error | ||
end | ||
end | ||
end | ||
end |