## Copyright:: Copyright (c) 2015-2019 Chef Software Inc.# License:: Apache License, Version 2.0## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.#require_relative"base"require_relative"../ui"require_relative"../pager"require_relative"../policyfile/differ"require_relative"../policyfile/comparison_base"require_relative"../policyfile/storage_config"require_relative"../configurable"require_relative"../dist"require"chef/server_api"moduleChefCLImoduleCommandclassDiff<BaseincludeConfigurableincludePolicyfile::StorageConfigDelegationbanner(<<~BANNER)
Usage: #{ChefCLI::Dist::EXEC} diff [POLICYFILE] [--head | --git GIT_REF | POLICY_GROUP | POLICY_GROUP...POLICY_GROUP ]
`#{ChefCLI::Dist::EXEC} diff` displays an itemized diff comparing two revisions of a
Policyfile lock.
When the `--git` option is given, `#{ChefCLI::Dist::EXEC} diff` either compares a given
git reference against the current lockfile revision on disk or compares
between two git references. Examples:
* `#{ChefCLI::Dist::EXEC} diff --git HEAD`: compares the current lock with the latest
commit on the current branch.
* `#{ChefCLI::Dist::EXEC} diff --git master`: compares the current lock with the latest
commit to master.
* `#{ChefCLI::Dist::EXEC} diff --git v1.0.0`: compares the current lock with the revision
as of the `v1.0.0` tag.
* `#{ChefCLI::Dist::EXEC} diff --git master...dev-branch`: compares the Policyfile lock on
master with the revision on the `dev-branch` branch.
* `#{ChefCLI::Dist::EXEC} diff --git v1.0.0...master`: compares the Policyfile lock at the
`v1.0.0` tag with the lastest revision on the master branch.
`#{ChefCLI::Dist::EXEC} diff --head` is a shortcut for `#{ChefCLI::Dist::EXEC} diff --git HEAD`.
When no git-specific flag is given, `#{ChefCLI::Dist::EXEC} diff` either compares the
current lockfile revision on disk to one on the #{ChefCLI::Dist::SERVER_PRODUCT} or compares
two lockfiles on the #{ChefCLI::Dist::SERVER_PRODUCT}. Lockfiles on the #{ChefCLI::Dist::SERVER_PRODUCT}
are specified by Policy Group. Examples:
* `#{ChefCLI::Dist::EXEC} diff staging`: compares the current lock with the one currently
assigned to the `staging` Policy Group.
* `#{ChefCLI::Dist::EXEC} diff production...staging`: compares the lock currently assigned
to the `production` Policy Group to the lock currently assigned to the
`staging` Policy Group.
Options:
BANNERoption:git,short: "-g GIT_REF",long: "--git GIT_REF",description: "Compare local lock against GIT_REF, or between two git commits."option:head,long: "--head",description: "Compare local lock against last git commit.",boolean: trueoption:pager,long: "--[no-]pager",description: "Enable/disable paged diff ouput (default: enabled).",default: true,boolean: trueoption:config_file,short: "-c CONFIG_FILE",long: "--config CONFIG_FILE",description: "Path to configuration file."option:debug,short: "-D",long: "--debug",description: "Enable stacktraces and other debug output.",default: falseattr_accessor:uiattr_reader:old_baseattr_reader:new_baseattr_reader:storage_configdefinitialize(*args)super@ui=UI.new@old_base=nil@new_base=nil@policyfile_relative_path=nil@storage_config=nil@http_client=nil@old_lock=nil@new_lock=nilenddefdebug?!!config[:debug]enddefrun(params=[])return1unlessapply_params!(params)print_diff0rescuePolicyfileServiceError=>ehandle_error(e)1enddefhandle_error(error)ui.err("Error: #{error.message}")iferror.respond_to?(:reason)ui.err("Reason: #{error.reason}")ui.err("")ui.err(error.extended_error_info)ifdebug?ui.err(error.cause.backtrace.join("\n"))ifdebug?endenddefprint_diff# eagerly evaluate locks so we hit any errors before we've entered# pagerland. Also, git commands behave weirdly when run while the pager# is active, doing this eagerly also avoids that issuematerialize_locksPager.new(enable_pager: config[:pager]).with_pagerdo|pager|differ=differ(pager.ui)differ.run_reportendenddefdiffer(ui=self.ui)Policyfile::Differ.new(old_name: old_base.name,old_lock: old_lock,new_name: new_base.name,new_lock: new_lock,ui: ui)enddefhttp_client@http_client||=Chef::ServerAPI.new(chef_config.chef_server_url,signing_key_filename: chef_config.client_key,client_name: chef_config.node_name)enddefold_lockmaterialize_locksunless@old_lock@old_lockenddefnew_lockmaterialize_locksunless@new_lock@new_lockenddefpolicy_namelocal_lock["name"]enddeflocal_lock@local_lock||=local_lock_comparison_base.lockend# ComparisonBase for the local lockfile. This is used to get the# policy_name which is needed to query the server for the lockfile of a# particular policy_group.deflocal_lock_comparison_basePolicyfile::ComparisonBase::Local.new(policyfile_lock_relpath)enddefpolicyfile_lock_relpathstorage_config.policyfile_lock_filenameenddefapply_params!(params)remaining_args=parse_options(params)ifno_comparison_specified?(remaining_args)ui.err("No comparison specified")ui.err("")ui.err(opt_parser)falseelsifconflicting_args_and_opts_given?(remaining_args)ui.err("Conflicting arguments and options: git and Policy Group comparisons cannot be mixed")ui.err("")ui.err(opt_parser)falseelsifconflicting_git_options_given?ui.err("Conflicting git options: --head and --git are exclusive")ui.err("")ui.err(opt_parser)falseelsifconfig[:head]set_policyfile_path_from_args(remaining_args)@old_base=Policyfile::ComparisonBase::Git.new("HEAD",policyfile_lock_relpath)@new_base=Policyfile::ComparisonBase::Local.new(policyfile_lock_relpath)trueelsifconfig[:git]set_policyfile_path_from_args(remaining_args)parse_git_comparison(config[:git])elseset_policyfile_path_from_args(remaining_args)parse_server_comparison(remaining_args)endenddefparse_server_comparison(args)comparison_string=args.lastifcomparison_string.include?("...")old_pgroup,new_pgroup,*extra=comparison_string.split("...")@old_base,@new_base=[old_pgroup,new_pgroup].mapdo|g|Policyfile::ComparisonBase::PolicyGroup.new(g,policy_name,http_client)endunlessextra.empty?ui.err("Unable to parse policy group comparison `#{comparison_string}`. Only 2 references can be specified.")returnfalseendelse@old_base=Policyfile::ComparisonBase::PolicyGroup.new(comparison_string,policy_name,http_client)@new_base=Policyfile::ComparisonBase::Local.new(policyfile_lock_relpath)endtrueenddefparse_git_comparison(git_ref)ifgit_ref.include?("...")old_ref,new_ref,*extra=git_ref.split("...")@old_base,@new_base=[old_ref,new_ref].mapdo|r|Policyfile::ComparisonBase::Git.new(r,policyfile_lock_relpath)endunlessextra.empty?ui.err("Unable to parse git comparison `#{git_ref}`. Only 2 references can be specified.")returnfalseendelse@old_base=Policyfile::ComparisonBase::Git.new(git_ref,policyfile_lock_relpath)@new_base=Policyfile::ComparisonBase::Local.new(policyfile_lock_relpath)endtrueenddefno_comparison_specified?(args)!policy_group_comparison?(args)&&!config[:head]&&!config[:git]enddefconflicting_args_and_opts_given?(args)(config[:git]||config[:head])&&policy_group_comparison?(args)enddefconflicting_git_options_given?config[:git]&&config[:head]enddefcomparing_policy_groups?!(config[:git]||config[:head])end# Try to detect if the only argument given is a policyfile path. This is# necessary because we support an optional argument with the path to the# ruby policyfile. It would be easier if we used an option like `-f`, but# that would be inconsistent with other commands (`chef install`, `chef# push`, etc.).defpolicy_group_comparison?(args)returnfalseifargs.empty?returntrueifargs.size>1!(args.first=~/\.rb\Z/)enddefset_policyfile_path_from_args(args)policyfile_relative_path=if!comparing_policy_groups?args.first||"Policyfile.rb"elsifargs.size==1"Policyfile.rb"elseargs.firstend@storage_config=Policyfile::StorageConfig.new.use_policyfile(policyfile_relative_path)enddefmaterialize_locks@old_lock=old_base.lock@new_lock=new_base.lockendendendend