summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSwati Keshari <62278765+skeshari12@users.noreply.github.com>2021-06-16 20:23:47 +0530
committerGitHub <noreply@github.com>2021-06-16 20:23:47 +0530
commit53c35e9abc2b0c233f3cb70e941adf7bcb2bb8a8 (patch)
tree8b57fa5ce47950713d70ba1b561a346c61765936
parent8e0c8efffa986f92d018949630a7de2584379c8a (diff)
parente2fe8eb41634152e192575c3daf27d338fc8f816 (diff)
downloadchef-53c35e9abc2b0c233f3cb70e941adf7bcb2bb8a8.tar.gz
Merge branch 'master' into update-expeditor-configuration
-rw-r--r--.expeditor/config.yml77
-rw-r--r--.expeditor/habitat-test.pipeline.yml6
-rw-r--r--.expeditor/release.omnibus.yml29
-rwxr-xr-x.expeditor/scripts/bk_container_prep.sh27
-rwxr-xr-x.expeditor/scripts/bk_linux_exec.sh51
-rw-r--r--.expeditor/scripts/bk_win_functional.ps133
-rw-r--r--.expeditor/scripts/bk_win_integration.ps1 (renamed from scripts/bk_tests/bk_win_integration.ps1)0
-rw-r--r--.expeditor/scripts/bk_win_prep.ps115
-rw-r--r--.expeditor/scripts/bk_win_unit.ps1 (renamed from scripts/bk_tests/bk_win_unit.ps1)0
-rw-r--r--.expeditor/scripts/ensure-minimum-viable-hab.ps1 (renamed from scripts/ci/ensure-minimum-viable-hab.ps1)0
-rwxr-xr-x.expeditor/scripts/install-hab.sh (renamed from scripts/ci/install-hab.sh)0
-rw-r--r--.expeditor/scripts/verify-plan.ps137
-rwxr-xr-x.expeditor/scripts/verify-plan.sh (renamed from scripts/ci/verify-plan.sh)0
-rwxr-xr-x.expeditor/update_bundler_dep.sh29
-rwxr-xr-x[-rw-r--r--].expeditor/update_dep.sh13
-rwxr-xr-x.expeditor/update_version.sh1
-rw-r--r--.expeditor/verify.pipeline.yml508
-rw-r--r--.github/workflows/func_spec.yml25
-rw-r--r--.github/workflows/kitchen.yml126
-rw-r--r--.github/workflows/lint.yml36
-rw-r--r--.github/workflows/unit_specs.yml26
-rw-r--r--.gitignore12
-rw-r--r--.rubocop.yml28
-rw-r--r--CHANGELOG.md430
-rw-r--r--CHEF_MVPS.md2
-rw-r--r--Dockerfile4
-rw-r--r--Gemfile27
-rw-r--r--Gemfile.lock295
-rw-r--r--RELEASE_NOTES.md4079
-rw-r--r--Rakefile15
-rw-r--r--VERSION2
-rw-r--r--azure-pipelines.yml91
-rwxr-xr-xbin/knife2
-rwxr-xr-xchef-bin/bin/chef-service-manager37
-rwxr-xr-xchef-bin/bin/chef-windows-service33
-rw-r--r--chef-bin/lib/chef-bin/version.rb2
-rw-r--r--chef-config/chef-config.gemspec2
-rw-r--r--chef-config/lib/chef-config/config.rb6
-rw-r--r--chef-config/lib/chef-config/path_helper.rb2
-rw-r--r--chef-config/lib/chef-config/version.rb2
-rw-r--r--chef-config/spec/unit/config_spec.rb2
-rw-r--r--chef-config/spec/unit/path_helper_spec.rb10
-rw-r--r--chef-universal-mingw32.gemspec2
-rw-r--r--chef-utils/README.md2
-rw-r--r--chef-utils/chef-utils.gemspec6
-rw-r--r--chef-utils/lib/chef-utils/dist.rb44
-rw-r--r--chef-utils/lib/chef-utils/dsl/cloud.rb31
-rw-r--r--chef-utils/lib/chef-utils/dsl/introspection.rb11
-rw-r--r--chef-utils/lib/chef-utils/dsl/platform.rb15
-rw-r--r--chef-utils/lib/chef-utils/dsl/platform_family.rb13
-rw-r--r--chef-utils/lib/chef-utils/dsl/windows.rb2
-rw-r--r--chef-utils/lib/chef-utils/internal.rb2
-rw-r--r--chef-utils/lib/chef-utils/mash.rb15
-rw-r--r--chef-utils/lib/chef-utils/parallel_map.rb131
-rw-r--r--chef-utils/lib/chef-utils/version.rb2
-rw-r--r--chef-utils/spec/unit/dsl/cloud_spec.rb4
-rw-r--r--chef-utils/spec/unit/dsl/introspection_spec.rb12
-rw-r--r--chef-utils/spec/unit/dsl/platform_family_spec.rb30
-rw-r--r--chef-utils/spec/unit/dsl/platform_spec.rb14
-rw-r--r--chef-utils/spec/unit/parallel_map_spec.rb156
-rw-r--r--chef.gemspec53
-rw-r--r--cspell.json149
-rw-r--r--distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dllbin171008 -> 171008 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/Chef.PowerShell.dllbin6144 -> 6144 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/Newtonsoft.Json.dllbin664576 -> 701992 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dllbin98304 -> 98304 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dllbin5632 -> 5632 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdbbin11716 -> 11704 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json302
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dllbin220552 -> 220536 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dllbin572288 -> 572280 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dllbin808840 -> 809360 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dllbin336264 -> 336248 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dllbin182664 -> 182672 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dllbin154504 -> 154488 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dllbin228744 -> 228752 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dllbin286600 -> 286584 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dllbin143744 -> 143760 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dllbin693680 -> 695336 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dllbin0 -> 799624 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dllbin130440 -> 130952 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dllbin437128 -> 436624 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dllbin7389576 -> 7389560 bytes
-rw-r--r--distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dllbin275840 -> 276352 bytes
-rw-r--r--distro/ruby_bin_folder/x86/Chef.PowerShell.dllbin6656 -> 6656 bytes
-rw-r--r--distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dllbin145920 -> 145920 bytes
-rw-r--r--distro/ruby_bin_folder/x86/Newtonsoft.Json.dllbin664576 -> 701992 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dllbin83456 -> 83456 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dllbin6144 -> 6144 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdbbin11720 -> 11708 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json302
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dllbin220552 -> 220536 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dllbin572288 -> 572280 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dllbin808840 -> 809360 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dllbin336264 -> 336248 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dllbin182664 -> 182672 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dllbin154504 -> 154488 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dllbin228744 -> 228752 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dllbin286600 -> 286584 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dllbin143744 -> 143760 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dllbin693680 -> 695336 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dllbin0 -> 799624 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dllbin130440 -> 130952 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dllbin437128 -> 436624 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dllbin7389576 -> 7389560 bytes
-rw-r--r--distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dllbin275840 -> 276352 bytes
-rw-r--r--dobi.yaml3
-rw-r--r--docs/dev/design_documents/action_collection.md33
-rw-r--r--docs/dev/devtools/README.md5
-rw-r--r--docs/dev/devtools/chef-apply.md129
-rw-r--r--docs/dev/how_to/adding_new_platforms_to_infra.md57
-rw-r--r--docs/dev/how_to/bumping_the_major_version.md26
-rw-r--r--docs/dev/how_to/releasing_chef_infra.md14
-rw-r--r--docs/dev/how_to/running_adhoc_builds.md45
-rw-r--r--docs/dev/how_to/updating_dependencies.md2
-rw-r--r--docs/dev/policy/release_and_support_schedule.md1
-rw-r--r--habitat/plan.ps12
-rw-r--r--habitat/plan.sh10
-rw-r--r--habitat/tests/spec.ps12
-rw-r--r--habitat/tests/test.pester.ps17
-rwxr-xr-xhabitat/tests/test.sh4
-rw-r--r--kitchen-tests/Gemfile4
-rw-r--r--kitchen-tests/cookbooks/end_to_end/attributes/default.rb7
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/certs/ca.cert.pem36
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/certs/chef.northwindbaking.com.chained.cert.pem73
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/certs/intermediate.cert.pem35
-rw-r--r--kitchen-tests/cookbooks/end_to_end/files/certs/steveb.pfxbin0 -> 4405 bytes
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/_packages.rb13
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/_windows_defender.rb25
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb38
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/linux.rb24
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/macos.rb27
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/windows.rb101
-rw-r--r--kitchen-tests/data_bags/users/adam.json2
-rw-r--r--kitchen-tests/kitchen.azure.yml1
-rw-r--r--kitchen-tests/kitchen.yml46
-rw-r--r--knife/Gemfile26
-rw-r--r--knife/LICENSE201
-rw-r--r--knife/Rakefile33
-rwxr-xr-xknife/bin/knife24
-rw-r--r--knife/knife.gemspec60
-rw-r--r--knife/lib/chef/application/knife.rb234
-rw-r--r--knife/lib/chef/chef_fs/knife.rb162
-rw-r--r--knife/lib/chef/knife.rb673
-rw-r--r--knife/lib/chef/knife/acl_add.rb (renamed from lib/chef/knife/acl_add.rb)0
-rw-r--r--knife/lib/chef/knife/acl_base.rb (renamed from lib/chef/knife/acl_base.rb)0
-rw-r--r--knife/lib/chef/knife/acl_bulk_add.rb (renamed from lib/chef/knife/acl_bulk_add.rb)0
-rw-r--r--knife/lib/chef/knife/acl_bulk_remove.rb (renamed from lib/chef/knife/acl_bulk_remove.rb)0
-rw-r--r--knife/lib/chef/knife/acl_remove.rb (renamed from lib/chef/knife/acl_remove.rb)0
-rw-r--r--knife/lib/chef/knife/acl_show.rb (renamed from lib/chef/knife/acl_show.rb)0
-rw-r--r--knife/lib/chef/knife/bootstrap.rb1191
-rw-r--r--knife/lib/chef/knife/bootstrap/chef_vault_handler.rb160
-rw-r--r--knife/lib/chef/knife/bootstrap/client_builder.rb212
-rw-r--r--knife/lib/chef/knife/bootstrap/templates/README.md (renamed from lib/chef/knife/bootstrap/templates/README.md)0
-rw-r--r--knife/lib/chef/knife/bootstrap/templates/chef-full.erb (renamed from lib/chef/knife/bootstrap/templates/chef-full.erb)0
-rw-r--r--knife/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb (renamed from lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb)0
-rw-r--r--knife/lib/chef/knife/bootstrap/train_connector.rb334
-rw-r--r--knife/lib/chef/knife/client_bulk_delete.rb104
-rw-r--r--knife/lib/chef/knife/client_create.rb101
-rw-r--r--knife/lib/chef/knife/client_delete.rb62
-rw-r--r--knife/lib/chef/knife/client_edit.rb52
-rw-r--r--knife/lib/chef/knife/client_key_create.rb (renamed from lib/chef/knife/client_key_create.rb)0
-rw-r--r--knife/lib/chef/knife/client_key_delete.rb (renamed from lib/chef/knife/client_key_delete.rb)0
-rw-r--r--knife/lib/chef/knife/client_key_edit.rb (renamed from lib/chef/knife/client_key_edit.rb)0
-rw-r--r--knife/lib/chef/knife/client_key_list.rb (renamed from lib/chef/knife/client_key_list.rb)0
-rw-r--r--knife/lib/chef/knife/client_key_show.rb (renamed from lib/chef/knife/client_key_show.rb)0
-rw-r--r--knife/lib/chef/knife/client_list.rb41
-rw-r--r--knife/lib/chef/knife/client_reregister.rb58
-rw-r--r--knife/lib/chef/knife/client_show.rb48
-rw-r--r--knife/lib/chef/knife/config_get.rb (renamed from lib/chef/knife/config_get.rb)0
-rw-r--r--knife/lib/chef/knife/config_get_profile.rb (renamed from lib/chef/knife/config_get_profile.rb)0
-rw-r--r--knife/lib/chef/knife/config_list.rb139
-rw-r--r--knife/lib/chef/knife/config_list_profiles.rb (renamed from lib/chef/knife/config_list_profiles.rb)0
-rw-r--r--knife/lib/chef/knife/config_show.rb (renamed from lib/chef/knife/config_show.rb)0
-rw-r--r--knife/lib/chef/knife/config_use.rb (renamed from lib/chef/knife/config_use.rb)0
-rw-r--r--knife/lib/chef/knife/config_use_profile.rb (renamed from lib/chef/knife/config_use_profile.rb)0
-rw-r--r--knife/lib/chef/knife/configure.rb150
-rw-r--r--knife/lib/chef/knife/configure_client.rb (renamed from lib/chef/knife/configure_client.rb)0
-rw-r--r--knife/lib/chef/knife/cookbook_bulk_delete.rb71
-rw-r--r--knife/lib/chef/knife/cookbook_delete.rb151
-rw-r--r--knife/lib/chef/knife/cookbook_download.rb142
-rw-r--r--knife/lib/chef/knife/cookbook_list.rb (renamed from lib/chef/knife/cookbook_list.rb)0
-rw-r--r--knife/lib/chef/knife/cookbook_metadata.rb106
-rw-r--r--knife/lib/chef/knife/cookbook_metadata_from_file.rb49
-rw-r--r--knife/lib/chef/knife/cookbook_show.rb98
-rw-r--r--knife/lib/chef/knife/cookbook_upload.rb292
-rw-r--r--knife/lib/chef/knife/core/bootstrap_context.rb264
-rw-r--r--knife/lib/chef/knife/core/cookbook_scm_repo.rb159
-rw-r--r--knife/lib/chef/knife/core/cookbook_site_streaming_uploader.rb249
-rw-r--r--knife/lib/chef/knife/core/formatting_options.rb49
-rw-r--r--knife/lib/chef/knife/core/gem_glob_loader.rb134
-rw-r--r--knife/lib/chef/knife/core/generic_presenter.rb (renamed from lib/chef/knife/core/generic_presenter.rb)0
-rw-r--r--knife/lib/chef/knife/core/hashed_command_loader.rb100
-rw-r--r--knife/lib/chef/knife/core/node_editor.rb130
-rw-r--r--knife/lib/chef/knife/core/node_presenter.rb133
-rw-r--r--knife/lib/chef/knife/core/object_loader.rb115
-rw-r--r--knife/lib/chef/knife/core/status_presenter.rb147
-rw-r--r--knife/lib/chef/knife/core/subcommand_loader.rb208
-rw-r--r--knife/lib/chef/knife/core/text_formatter.rb (renamed from lib/chef/knife/core/text_formatter.rb)0
-rw-r--r--knife/lib/chef/knife/core/ui.rb338
-rw-r--r--knife/lib/chef/knife/core/windows_bootstrap_context.rb405
-rw-r--r--knife/lib/chef/knife/data_bag_create.rb81
-rw-r--r--knife/lib/chef/knife/data_bag_delete.rb49
-rw-r--r--knife/lib/chef/knife/data_bag_edit.rb74
-rw-r--r--knife/lib/chef/knife/data_bag_from_file.rb113
-rw-r--r--knife/lib/chef/knife/data_bag_list.rb42
-rw-r--r--knife/lib/chef/knife/data_bag_secret_options.rb122
-rw-r--r--knife/lib/chef/knife/data_bag_show.rb69
-rw-r--r--knife/lib/chef/knife/delete.rb125
-rw-r--r--knife/lib/chef/knife/deps.rb156
-rw-r--r--knife/lib/chef/knife/diff.rb83
-rw-r--r--knife/lib/chef/knife/download.rb84
-rw-r--r--knife/lib/chef/knife/edit.rb88
-rw-r--r--knife/lib/chef/knife/environment_compare.rb128
-rw-r--r--knife/lib/chef/knife/environment_create.rb52
-rw-r--r--knife/lib/chef/knife/environment_delete.rb44
-rw-r--r--knife/lib/chef/knife/environment_edit.rb44
-rw-r--r--knife/lib/chef/knife/environment_from_file.rb84
-rw-r--r--knife/lib/chef/knife/environment_list.rb41
-rw-r--r--knife/lib/chef/knife/environment_show.rb47
-rw-r--r--knife/lib/chef/knife/exec.rb99
-rw-r--r--knife/lib/chef/knife/group_add.rb (renamed from lib/chef/knife/group_add.rb)0
-rw-r--r--knife/lib/chef/knife/group_create.rb (renamed from lib/chef/knife/group_create.rb)0
-rw-r--r--knife/lib/chef/knife/group_destroy.rb (renamed from lib/chef/knife/group_destroy.rb)0
-rw-r--r--knife/lib/chef/knife/group_list.rb (renamed from lib/chef/knife/group_list.rb)0
-rw-r--r--knife/lib/chef/knife/group_remove.rb (renamed from lib/chef/knife/group_remove.rb)0
-rw-r--r--knife/lib/chef/knife/group_show.rb (renamed from lib/chef/knife/group_show.rb)0
-rw-r--r--knife/lib/chef/knife/key_create.rb112
-rw-r--r--knife/lib/chef/knife/key_create_base.rb (renamed from lib/chef/knife/key_create_base.rb)0
-rw-r--r--knife/lib/chef/knife/key_delete.rb55
-rw-r--r--knife/lib/chef/knife/key_edit.rb118
-rw-r--r--knife/lib/chef/knife/key_edit_base.rb (renamed from lib/chef/knife/key_edit_base.rb)0
-rw-r--r--knife/lib/chef/knife/key_list.rb90
-rw-r--r--knife/lib/chef/knife/key_list_base.rb (renamed from lib/chef/knife/key_list_base.rb)0
-rw-r--r--knife/lib/chef/knife/key_show.rb53
-rw-r--r--knife/lib/chef/knife/list.rb177
-rw-r--r--knife/lib/chef/knife/node_bulk_delete.rb75
-rw-r--r--knife/lib/chef/knife/node_create.rb47
-rw-r--r--knife/lib/chef/knife/node_delete.rb46
-rw-r--r--knife/lib/chef/knife/node_edit.rb70
-rw-r--r--knife/lib/chef/knife/node_environment_set.rb53
-rw-r--r--knife/lib/chef/knife/node_from_file.rb51
-rw-r--r--knife/lib/chef/knife/node_list.rb44
-rw-r--r--knife/lib/chef/knife/node_policy_set.rb79
-rw-r--r--knife/lib/chef/knife/node_run_list_add.rb104
-rw-r--r--knife/lib/chef/knife/node_run_list_remove.rb67
-rw-r--r--knife/lib/chef/knife/node_run_list_set.rb66
-rw-r--r--knife/lib/chef/knife/node_show.rb63
-rw-r--r--knife/lib/chef/knife/null.rb (renamed from lib/chef/knife/null.rb)0
-rw-r--r--knife/lib/chef/knife/org_create.rb70
-rw-r--r--knife/lib/chef/knife/org_delete.rb32
-rw-r--r--knife/lib/chef/knife/org_edit.rb48
-rw-r--r--knife/lib/chef/knife/org_list.rb44
-rw-r--r--knife/lib/chef/knife/org_show.rb31
-rw-r--r--knife/lib/chef/knife/org_user_add.rb62
-rw-r--r--knife/lib/chef/knife/org_user_remove.rb103
-rw-r--r--knife/lib/chef/knife/raw.rb123
-rw-r--r--knife/lib/chef/knife/recipe_list.rb (renamed from lib/chef/knife/recipe_list.rb)0
-rw-r--r--knife/lib/chef/knife/rehash.rb (renamed from lib/chef/knife/rehash.rb)0
-rw-r--r--knife/lib/chef/knife/role_bulk_delete.rb66
-rw-r--r--knife/lib/chef/knife/role_create.rb53
-rw-r--r--knife/lib/chef/knife/role_delete.rb46
-rw-r--r--knife/lib/chef/knife/role_edit.rb45
-rw-r--r--knife/lib/chef/knife/role_env_run_list_add.rb87
-rw-r--r--knife/lib/chef/knife/role_env_run_list_clear.rb55
-rw-r--r--knife/lib/chef/knife/role_env_run_list_remove.rb57
-rw-r--r--knife/lib/chef/knife/role_env_run_list_replace.rb60
-rw-r--r--knife/lib/chef/knife/role_env_run_list_set.rb70
-rw-r--r--knife/lib/chef/knife/role_from_file.rb51
-rw-r--r--knife/lib/chef/knife/role_list.rb42
-rw-r--r--knife/lib/chef/knife/role_run_list_add.rb87
-rw-r--r--knife/lib/chef/knife/role_run_list_clear.rb55
-rw-r--r--knife/lib/chef/knife/role_run_list_remove.rb56
-rw-r--r--knife/lib/chef/knife/role_run_list_replace.rb60
-rw-r--r--knife/lib/chef/knife/role_run_list_set.rb69
-rw-r--r--knife/lib/chef/knife/role_show.rb48
-rw-r--r--knife/lib/chef/knife/search.rb194
-rw-r--r--knife/lib/chef/knife/serve.rb65
-rw-r--r--knife/lib/chef/knife/show.rb72
-rw-r--r--knife/lib/chef/knife/ssh.rb645
-rw-r--r--knife/lib/chef/knife/ssl_check.rb284
-rw-r--r--knife/lib/chef/knife/ssl_fetch.rb162
-rw-r--r--knife/lib/chef/knife/status.rb95
-rw-r--r--knife/lib/chef/knife/supermarket_download.rb119
-rw-r--r--knife/lib/chef/knife/supermarket_install.rb192
-rw-r--r--knife/lib/chef/knife/supermarket_list.rb (renamed from lib/chef/knife/supermarket_list.rb)0
-rw-r--r--knife/lib/chef/knife/supermarket_search.rb (renamed from lib/chef/knife/supermarket_search.rb)0
-rw-r--r--knife/lib/chef/knife/supermarket_share.rb166
-rw-r--r--knife/lib/chef/knife/supermarket_show.rb (renamed from lib/chef/knife/supermarket_show.rb)0
-rw-r--r--knife/lib/chef/knife/supermarket_unshare.rb61
-rw-r--r--knife/lib/chef/knife/tag_create.rb52
-rw-r--r--knife/lib/chef/knife/tag_delete.rb60
-rw-r--r--knife/lib/chef/knife/tag_list.rb47
-rw-r--r--knife/lib/chef/knife/upload.rb86
-rw-r--r--knife/lib/chef/knife/user_create.rb178
-rw-r--r--knife/lib/chef/knife/user_delete.rb151
-rw-r--r--knife/lib/chef/knife/user_dissociate.rb (renamed from lib/chef/knife/user_dissociate.rb)0
-rw-r--r--knife/lib/chef/knife/user_edit.rb94
-rw-r--r--knife/lib/chef/knife/user_invite_add.rb (renamed from lib/chef/knife/user_invite_add.rb)0
-rw-r--r--knife/lib/chef/knife/user_invite_list.rb (renamed from lib/chef/knife/user_invite_list.rb)0
-rw-r--r--knife/lib/chef/knife/user_invite_rescind.rb (renamed from lib/chef/knife/user_invite_rescind.rb)0
-rw-r--r--knife/lib/chef/knife/user_key_create.rb (renamed from lib/chef/knife/user_key_create.rb)0
-rw-r--r--knife/lib/chef/knife/user_key_delete.rb (renamed from lib/chef/knife/user_key_delete.rb)0
-rw-r--r--knife/lib/chef/knife/user_key_edit.rb (renamed from lib/chef/knife/user_key_edit.rb)0
-rw-r--r--knife/lib/chef/knife/user_key_list.rb (renamed from lib/chef/knife/user_key_list.rb)0
-rw-r--r--knife/lib/chef/knife/user_key_show.rb (renamed from lib/chef/knife/user_key_show.rb)0
-rw-r--r--knife/lib/chef/knife/user_list.rb43
-rw-r--r--knife/lib/chef/knife/user_password.rb70
-rw-r--r--knife/lib/chef/knife/user_reregister.rb59
-rw-r--r--knife/lib/chef/knife/user_show.rb52
-rw-r--r--knife/lib/chef/knife/version.rb24
-rw-r--r--knife/lib/chef/knife/xargs.rb282
-rw-r--r--knife/lib/chef/knife/yaml_convert.rb (renamed from lib/chef/knife/yaml_convert.rb)0
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/changelog5
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/compat1
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/control13
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/copyright34
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/files1
-rwxr-xr-xknife/spec/data/apt/chef-integration-test-1.0/debian/rules13
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.0/debian/source/format1
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/changelog11
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/compat1
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/control13
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/copyright34
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/files1
-rwxr-xr-xknife/spec/data/apt/chef-integration-test-1.1/debian/rules13
-rw-r--r--knife/spec/data/apt/chef-integration-test-1.1/debian/source/format1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog5
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log45
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control10
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/compat1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles1
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/control13
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright34
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/files1
-rwxr-xr-xknife/spec/data/apt/chef-integration-test2-1.0/debian/rules13
-rw-r--r--knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format1
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gzbin0 -> 1369 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0-1.dsc18
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build91
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes31
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.debbin0 -> 1694 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gzbin0 -> 248 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes22
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.0-1_amd64.debbin0 -> 1680 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gzbin0 -> 237 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes22
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.1-1_amd64.debbin0 -> 1722 bytes
-rw-r--r--knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gzbin0 -> 237 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/conf/distributions7
-rw-r--r--knife/spec/data/apt/var/www/apt/conf/incoming4
-rw-r--r--knife/spec/data/apt/var/www/apt/conf/pulls3
-rw-r--r--knife/spec/data/apt/var/www/apt/db/checksums.dbbin0 -> 16384 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/db/contents.cache.dbbin0 -> 16384 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/db/packages.dbbin0 -> 16384 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/db/references.dbbin0 -> 16384 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/db/release.caches.dbbin0 -> 20480 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/db/version4
-rw-r--r--knife/spec/data/apt/var/www/apt/dists/sid/Release19
-rw-r--r--knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages16
-rw-r--r--knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gzbin0 -> 394 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release5
-rw-r--r--knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages (renamed from spec/data/trusted_certs_empty/.gitkeep)0
-rw-r--r--knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.debbin0 -> 1680 bytes
-rw-r--r--knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.debbin0 -> 1722 bytes
-rw-r--r--knife/spec/data/bad-config.rb1
-rw-r--r--knife/spec/data/bootstrap/encrypted_data_bag_secret1
-rw-r--r--knife/spec/data/bootstrap/no_proxy.erb2
-rw-r--r--knife/spec/data/bootstrap/secret.erb9
-rw-r--r--knife/spec/data/bootstrap/test-hints.erb12
-rw-r--r--knife/spec/data/bootstrap/test.erb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt0
-rw-r--r--knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb0
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/README.rdoc3
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb1
-rw-r--r--knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb0
-rw-r--r--knife/spec/data/checksum/random.txt1
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-00
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-01
-rw-r--r--knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-01
-rw-r--r--knife/spec/data/client.d_00/00-foo.rb2
-rw-r--r--knife/spec/data/client.d_00/01-bar.rb1
-rw-r--r--knife/spec/data/client.d_00/02-strings.rb2
-rw-r--r--knife/spec/data/client.d_00/bar1
-rw-r--r--knife/spec/data/client.d_01/foo/bar.rb1
-rw-r--r--knife/spec/data/client.d_02/foo.rb/foo.txt1
-rw-r--r--knife/spec/data/config.rb6
-rw-r--r--knife/spec/data/cookbooks/angrybash/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/angrybash/recipes/default.rb8
-rw-r--r--knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl2
-rw-r--r--knife/spec/data/cookbooks/apache2/metadata.json33
-rw-r--r--knife/spec/data/cookbooks/apache2/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/apache2/recipes/default.rb3
-rw-r--r--knife/spec/data/cookbooks/borken/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/borken/recipes/default.rb2
-rw-r--r--knife/spec/data/cookbooks/borken/templates/default/borken.erb2
-rw-r--r--knife/spec/data/cookbooks/chefignore8
-rw-r--r--knife/spec/data/cookbooks/ignorken/files/default/not_me.rb2
-rw-r--r--knife/spec/data/cookbooks/ignorken/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/ignorken/recipes/default.rb1
-rw-r--r--knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb2
-rw-r--r--knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb2
-rw-r--r--knife/spec/data/cookbooks/irssi/files/default/irssi.response2
-rw-r--r--knife/spec/data/cookbooks/java/files/default/java.response2
-rw-r--r--knife/spec/data/cookbooks/java/metadata.json33
-rw-r--r--knife/spec/data/cookbooks/java/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md4
-rw-r--r--knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb8
-rw-r--r--knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb8
-rw-r--r--knife/spec/data/cookbooks/openldap/.root_dotfile0
-rw-r--r--knife/spec/data/cookbooks/openldap/attributes/default.rb16
-rw-r--r--knife/spec/data/cookbooks/openldap/attributes/smokey.rb1
-rw-r--r--knife/spec/data/cookbooks/openldap/definitions/client.rb5
-rw-r--r--knife/spec/data/cookbooks/openldap/definitions/server.rb5
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/.dotfile1
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa1
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir1
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb2
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt3
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt3
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile1
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt3
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt3
-rw-r--r--knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt3
-rw-r--r--knife/spec/data/cookbooks/openldap/libraries/openldap.rb4
-rw-r--r--knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb3
-rw-r--r--knife/spec/data/cookbooks/openldap/metadata.rb8
-rw-r--r--knife/spec/data/cookbooks/openldap/recipes/default.rb4
-rw-r--r--knife/spec/data/cookbooks/openldap/recipes/gigantor.rb3
-rw-r--r--knife/spec/data/cookbooks/openldap/recipes/one.rb15
-rw-r--r--knife/spec/data/cookbooks/openldap/recipes/return.rb2
-rw-r--r--knife/spec/data/cookbooks/openldap/spec/spec_helper.rb0
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb4
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/helpers.erb14
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb4
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb1
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb4
-rw-r--r--knife/spec/data/cookbooks/openldap/templates/default/test.erb1
-rw-r--r--knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed1
-rw-r--r--knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed4
-rw-r--r--knife/spec/data/cookbooks/preseed/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed1
-rw-r--r--knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed1
-rw-r--r--knife/spec/data/cookbooks/starter/chefignore8
-rw-r--r--knife/spec/data/cookbooks/starter/files/sample.txt1
-rw-r--r--knife/spec/data/cookbooks/starter/metadata.rb2
-rw-r--r--knife/spec/data/cookbooks/starter/recipes/default.rb4
-rw-r--r--knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb5
-rw-r--r--knife/spec/data/cookbooks/wget/files/default/wget.response2
-rw-r--r--knife/spec/data/definitions/test.rb5
-rw-r--r--knife/spec/data/dsc_lcm.pfxbin0 -> 2597 bytes
-rw-r--r--knife/spec/data/environment-config.rb5
-rw-r--r--knife/spec/data/file-providers-method-snapshot-chef-11-4.json127
-rw-r--r--knife/spec/data/fileedit/blank0
-rw-r--r--knife/spec/data/fileedit/hosts4
-rw-r--r--knife/spec/data/gems/chef-integration-test-0.1.0.gembin0 -> 7680 bytes
-rw-r--r--knife/spec/data/git_bundles/example-repo.gitbundlebin0 -> 1214 bytes
-rw-r--r--knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundlebin0 -> 3527 bytes
-rw-r--r--knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundlebin0 -> 2330 bytes
-rw-r--r--knife/spec/data/git_bundles/sinatra-test-app.gitbundlebin0 -> 2053 bytes
-rw-r--r--knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md4
-rw-r--r--knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb13
-rw-r--r--knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb8
-rw-r--r--knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md4
-rw-r--r--knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb9
-rw-r--r--knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb8
-rw-r--r--knife/spec/data/kitchen/chefignore6
-rw-r--r--knife/spec/data/kitchen/openldap/attributes/default.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/attributes/robinson.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/definitions/client.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/recipes/gigantor.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/recipes/ignoreme.rb3
-rw-r--r--knife/spec/data/kitchen/openldap/recipes/woot.rb3
-rw-r--r--knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb0
-rw-r--r--knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb0
-rw-r--r--knife/spec/data/knife_subcommand/test_explicit_category.rb7
-rw-r--r--knife/spec/data/knife_subcommand/test_name_mapping.rb4
-rw-r--r--knife/spec/data/knife_subcommand/test_yourself.rb21
-rw-r--r--knife/spec/data/lwrp/providers/buck_passer.rb28
-rw-r--r--knife/spec/data/lwrp/providers/buck_passer_2.rb26
-rw-r--r--knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb28
-rw-r--r--knife/spec/data/lwrp/providers/inline_compiler.rb24
-rw-r--r--knife/spec/data/lwrp/providers/monkey_name_printer.rb5
-rw-r--r--knife/spec/data/lwrp/providers/paint_drying_watcher.rb7
-rw-r--r--knife/spec/data/lwrp/providers/thumb_twiddler.rb7
-rw-r--r--knife/spec/data/lwrp/resources/bar.rb4
-rw-r--r--knife/spec/data/lwrp/resources/buck_passer.rb6
-rw-r--r--knife/spec/data/lwrp/resources/buck_passer_2.rb4
-rw-r--r--knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb4
-rw-r--r--knife/spec/data/lwrp/resources/foo.rb6
-rw-r--r--knife/spec/data/lwrp/resources/inline_compiler.rb4
-rw-r--r--knife/spec/data/lwrp/resources/monkey_name_printer.rb6
-rw-r--r--knife/spec/data/lwrp/resources/paint_drying_watcher.rb4
-rw-r--r--knife/spec/data/lwrp/resources/thumb_twiddler.rb4
-rw-r--r--knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb3
-rw-r--r--knife/spec/data/lwrp_const_scoping/resources/conflict.rb1
-rw-r--r--knife/spec/data/lwrp_override/providers/buck_passer.rb5
-rw-r--r--knife/spec/data/lwrp_override/resources/foo.rb11
-rw-r--r--knife/spec/data/mac_users/10.9.plist.xml560
-rw-r--r--knife/spec/data/mac_users/10.9.shadow.xml21
-rw-r--r--knife/spec/data/metadata/quick_start/metadata.rb14
-rw-r--r--knife/spec/data/mixin/invalid_data.rb3
-rw-r--r--knife/spec/data/mixin/real_data.rb2
-rw-r--r--knife/spec/data/nested.json2
-rw-r--r--knife/spec/data/nodes/default.rb15
-rw-r--r--knife/spec/data/nodes/test.example.com.rb17
-rw-r--r--knife/spec/data/nodes/test.rb15
-rw-r--r--knife/spec/data/null_config.rb1
-rw-r--r--knife/spec/data/object_loader/environments/test.json7
-rw-r--r--knife/spec/data/object_loader/environments/test.rb2
-rw-r--r--knife/spec/data/object_loader/environments/test_json_class.json8
-rw-r--r--knife/spec/data/object_loader/nodes/test.json7
-rw-r--r--knife/spec/data/object_loader/nodes/test.rb2
-rw-r--r--knife/spec/data/object_loader/nodes/test_json_class.json8
-rw-r--r--knife/spec/data/object_loader/roles/test.json7
-rw-r--r--knife/spec/data/object_loader/roles/test.rb2
-rw-r--r--knife/spec/data/object_loader/roles/test_json_class.json8
-rw-r--r--knife/spec/data/old_home_dir/my-dot-emacs0
-rw-r--r--knife/spec/data/old_home_dir/my-dot-vim0
-rw-r--r--knife/spec/data/partial_one.erb1
-rw-r--r--knife/spec/data/prefer_metadata_json/metadata.json51
-rw-r--r--knife/spec/data/prefer_metadata_json/metadata.rb6
-rw-r--r--knife/spec/data/prefer_metadata_json/recipes/default.rb0
-rw-r--r--knife/spec/data/recipes.tgzbin0 -> 293 bytes
-rw-r--r--knife/spec/data/recipes/test.rb7
-rw-r--r--knife/spec/data/remote_directory_data/remote_dir_file.txt1
-rw-r--r--knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt1
-rw-r--r--knife/spec/data/remote_file/nyan_cat.pngbin0 -> 15202 bytes
-rw-r--r--knife/spec/data/remote_file/nyan_cat.png.gzbin0 -> 14944 bytes
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb1
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb1
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb2
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb3
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb1
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb2
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb3
-rw-r--r--knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb3
-rw-r--r--knife/spec/data/root_alias_cookbooks/simple/attributes.rb1
-rw-r--r--knife/spec/data/root_alias_cookbooks/simple/metadata.rb2
-rw-r--r--knife/spec/data/root_alias_cookbooks/simple/recipe.rb3
-rw-r--r--knife/spec/data/rubygems.org/latest_specs.4.8.gzbin0 -> 86 bytes
-rw-r--r--knife/spec/data/rubygems.org/nonexistent_gembin0 -> 4 bytes
-rw-r--r--knife/spec/data/rubygems.org/nonexistent_gem-info1
-rw-r--r--knife/spec/data/rubygems.org/sexp_processorbin0 -> 2737 bytes
-rw-r--r--knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rzbin0 -> 519 bytes
-rw-r--r--knife/spec/data/rubygems.org/sexp_processor-info49
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/include/recipes/default.rb24
-rw-r--r--knife/spec/data/run_context/cookbooks/include/recipes/includee.rb3
-rw-r--r--knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb3
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb3
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb2
-rw-r--r--knife/spec/data/run_context/cookbooks/test/attributes/default.rb0
-rw-r--r--knife/spec/data/run_context/cookbooks/test/attributes/george.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb9
-rw-r--r--knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb5
-rw-r--r--knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test/providers/provider.rb1
-rw-r--r--knife/spec/data/run_context/cookbooks/test/recipes/default.rb5
-rw-r--r--knife/spec/data/run_context/cookbooks/test/recipes/one.rb7
-rw-r--r--knife/spec/data/run_context/cookbooks/test/recipes/two.rb7
-rw-r--r--knife/spec/data/run_context/cookbooks/test/resources/resource.rb3
-rw-r--r--knife/spec/data/run_context/nodes/run_context.rb5
-rw-r--r--knife/spec/data/sample_msu1.xml10
-rw-r--r--knife/spec/data/sample_msu2.xml14
-rw-r--r--knife/spec/data/sample_msu3.xml16
-rw-r--r--knife/spec/data/search_queries_to_transform.txt98
-rw-r--r--knife/spec/data/shef-config.rb11
-rw-r--r--knife/spec/data/snap_package/async_result_success.json6
-rw-r--r--knife/spec/data/snap_package/change_id_result.json175
-rw-r--r--knife/spec/data/snap_package/find_result_failure.json10
-rw-r--r--knife/spec/data/snap_package/find_result_success.json70
-rw-r--r--knife/spec/data/snap_package/get_by_name_result_failure.json10
-rw-r--r--knife/spec/data/snap_package/get_by_name_result_success.json38
-rw-r--r--knife/spec/data/snap_package/get_conf_success.json10
-rw-r--r--knife/spec/data/snap_package/result_failure.json9
-rw-r--r--knife/spec/data/ssl/5e707473.018
-rw-r--r--knife/spec/data/ssl/binary/chef-rspec-der.certbin0 -> 1174 bytes
-rw-r--r--knife/spec/data/ssl/binary/chef-rspec-der.keybin0 -> 1191 bytes
-rw-r--r--knife/spec/data/ssl/chef-rspec.cert27
-rw-r--r--knife/spec/data/ssl/chef-rspec.key27
-rw-r--r--knife/spec/data/ssl/key.pem15
-rw-r--r--knife/spec/data/ssl/private_key.pem27
-rw-r--r--knife/spec/data/ssl/private_key_with_whitespace.pem32
-rw-r--r--knife/spec/data/standalone_cookbook/Gemfile1
-rw-r--r--knife/spec/data/standalone_cookbook/chefignore9
-rw-r--r--knife/spec/data/standalone_cookbook/recipes/default.rb3
-rw-r--r--knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb1
-rw-r--r--knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt1
-rw-r--r--knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt1
-rw-r--r--knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt1
-rw-r--r--knife/spec/data/templates/failed.erb5
-rw-r--r--knife/spec/data/templates/seattle.txt1
-rw-r--r--knife/spec/data/trusted_certs/example.crt22
-rw-r--r--knife/spec/data/trusted_certs/example_no_cn.crt36
-rw-r--r--knife/spec/data/trusted_certs/intermediate.pem27
-rw-r--r--knife/spec/data/trusted_certs/opscode.pem57
-rw-r--r--knife/spec/data/trusted_certs/root.pem22
-rw-r--r--knife/spec/data/windows_certificates/base64_test.cer20
-rw-r--r--knife/spec/data/windows_certificates/othertest.cer20
-rw-r--r--knife/spec/data/windows_certificates/test.cer20
-rw-r--r--knife/spec/data/windows_certificates/test.p7bbin0 -> 2619 bytes
-rw-r--r--knife/spec/data/windows_certificates/test.pem20
-rw-r--r--knife/spec/data/windows_certificates/test.pfxbin0 -> 2637 bytes
-rw-r--r--knife/spec/functional/configure_spec.rb33
-rw-r--r--knife/spec/functional/cookbook_delete_spec.rb156
-rw-r--r--knife/spec/functional/exec_spec.rb55
-rw-r--r--knife/spec/functional/rehash_spec.rb39
-rw-r--r--knife/spec/functional/smoke_test.rb (renamed from spec/functional/knife/smoke_test.rb)0
-rw-r--r--knife/spec/functional/ssh_spec.rb352
-rw-r--r--knife/spec/functional/version_spec.rb26
-rw-r--r--knife/spec/integration/chef_fs_data_store_spec.rb557
-rw-r--r--knife/spec/integration/chef_repo_path_spec.rb962
-rw-r--r--knife/spec/integration/chef_repository_file_system_spec.rb200
-rw-r--r--knife/spec/integration/chefignore_spec.rb301
-rw-r--r--knife/spec/integration/client_bulk_delete_spec.rb131
-rw-r--r--knife/spec/integration/client_create_spec.rb70
-rw-r--r--knife/spec/integration/client_delete_spec.rb64
-rw-r--r--knife/spec/integration/client_key_create_spec.rb66
-rw-r--r--knife/spec/integration/client_key_delete_spec.rb43
-rw-r--r--knife/spec/integration/client_key_list_spec.rb61
-rw-r--r--knife/spec/integration/client_key_show_spec.rb45
-rw-r--r--knife/spec/integration/client_list_spec.rb49
-rw-r--r--knife/spec/integration/client_show_spec.rb37
-rw-r--r--knife/spec/integration/commands_spec.rb55
-rw-r--r--knife/spec/integration/common_options_spec.rb174
-rw-r--r--knife/spec/integration/config_list_spec.rb220
-rw-r--r--knife/spec/integration/config_show_spec.rb192
-rw-r--r--knife/spec/integration/config_use_spec.rb198
-rw-r--r--knife/spec/integration/cookbook_api_ipv6_spec.rb113
-rw-r--r--knife/spec/integration/cookbook_bulk_delete_spec.rb65
-rw-r--r--knife/spec/integration/cookbook_download_spec.rb72
-rw-r--r--knife/spec/integration/cookbook_list_spec.rb55
-rw-r--r--knife/spec/integration/cookbook_show_spec.rb149
-rw-r--r--knife/spec/integration/cookbook_upload_spec.rb128
-rw-r--r--knife/spec/integration/data_bag_create_spec.rb125
-rw-r--r--knife/spec/integration/data_bag_delete_spec.rb59
-rw-r--r--knife/spec/integration/data_bag_edit_spec.rb105
-rw-r--r--knife/spec/integration/data_bag_from_file_spec.rb116
-rw-r--r--knife/spec/integration/data_bag_list_spec.rb44
-rw-r--r--knife/spec/integration/data_bag_show_spec.rb95
-rw-r--r--knife/spec/integration/delete_spec.rb1018
-rw-r--r--knife/spec/integration/deps_spec.rb703
-rw-r--r--knife/spec/integration/diff_spec.rb605
-rw-r--r--knife/spec/integration/download_spec.rb1336
-rw-r--r--knife/spec/integration/environment_compare_spec.rb75
-rw-r--r--knife/spec/integration/environment_create_spec.rb41
-rw-r--r--knife/spec/integration/environment_delete_spec.rb37
-rw-r--r--knife/spec/integration/environment_from_file_spec.rb116
-rw-r--r--knife/spec/integration/environment_list_spec.rb42
-rw-r--r--knife/spec/integration/environment_show_spec.rb77
-rw-r--r--knife/spec/integration/list_spec.rb1060
-rw-r--r--knife/spec/integration/node_bulk_delete_spec.rb52
-rw-r--r--knife/spec/integration/node_create_spec.rb47
-rw-r--r--knife/spec/integration/node_delete_spec.rb48
-rw-r--r--knife/spec/integration/node_environment_set_spec.rb46
-rw-r--r--knife/spec/integration/node_from_file_spec.rb59
-rw-r--r--knife/spec/integration/node_list_spec.rb45
-rw-r--r--knife/spec/integration/node_run_list_add_spec.rb54
-rw-r--r--knife/spec/integration/node_run_list_remove_spec.rb36
-rw-r--r--knife/spec/integration/node_run_list_set_spec.rb41
-rw-r--r--knife/spec/integration/node_show_spec.rb36
-rw-r--r--knife/spec/integration/raw_spec.rb297
-rw-r--r--knife/spec/integration/redirection_spec.rb64
-rw-r--r--knife/spec/integration/role_bulk_delete_spec.rb52
-rw-r--r--knife/spec/integration/role_create_spec.rb41
-rw-r--r--knife/spec/integration/role_delete_spec.rb48
-rw-r--r--knife/spec/integration/role_from_file_spec.rb96
-rw-r--r--knife/spec/integration/role_list_spec.rb45
-rw-r--r--knife/spec/integration/role_show_spec.rb51
-rw-r--r--knife/spec/integration/search_node_spec.rb40
-rw-r--r--knife/spec/integration/serve_spec.rb (renamed from spec/integration/knife/serve_spec.rb)0
-rw-r--r--knife/spec/integration/show_spec.rb197
-rw-r--r--knife/spec/integration/upload_spec.rb1617
-rw-r--r--knife/spec/knife_spec_helper.rb241
-rw-r--r--knife/spec/support/chef_helpers.rb79
-rw-r--r--knife/spec/support/key_helpers.rb102
-rw-r--r--knife/spec/support/platform_helpers.rb251
-rw-r--r--knife/spec/support/platforms/prof/gc.rb51
-rw-r--r--knife/spec/support/platforms/prof/win32.rb45
-rw-r--r--knife/spec/support/platforms/win32/spec_service.rb57
-rw-r--r--knife/spec/support/recipe_dsl_helper.rb83
-rw-r--r--knife/spec/support/shared/context/config.rb18
-rw-r--r--knife/spec/support/shared/functional/knife.rb37
-rw-r--r--knife/spec/support/shared/integration/integration_helper.rb122
-rw-r--r--knife/spec/support/shared/integration/knife_support.rb192
-rw-r--r--knife/spec/support/shared/matchers/exit_with_code.rb32
-rw-r--r--knife/spec/support/shared/matchers/match_environment_variable.rb17
-rw-r--r--knife/spec/support/shared/unit/knife_shared.rb39
-rw-r--r--knife/spec/support/shared/unit/mock_shellout.rb49
-rw-r--r--knife/spec/tiny_server.rb190
-rw-r--r--knife/spec/unit/application/knife_spec.rb241
-rw-r--r--knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb152
-rw-r--r--knife/spec/unit/knife/bootstrap/client_builder_spec.rb207
-rw-r--r--knife/spec/unit/knife/bootstrap/train_connector_spec.rb244
-rw-r--r--knife/spec/unit/knife/bootstrap_spec.rb2220
-rw-r--r--knife/spec/unit/knife/client_bulk_delete_spec.rb166
-rw-r--r--knife/spec/unit/knife/client_create_spec.rb169
-rw-r--r--knife/spec/unit/knife/client_delete_spec.rb99
-rw-r--r--knife/spec/unit/knife/client_edit_spec.rb53
-rw-r--r--knife/spec/unit/knife/client_list_spec.rb34
-rw-r--r--knife/spec/unit/knife/client_reregister_spec.rb62
-rw-r--r--knife/spec/unit/knife/client_show_spec.rb52
-rw-r--r--knife/spec/unit/knife/configure_client_spec.rb81
-rw-r--r--knife/spec/unit/knife/configure_spec.rb190
-rw-r--r--knife/spec/unit/knife/cookbook_bulk_delete_spec.rb87
-rw-r--r--knife/spec/unit/knife/cookbook_delete_spec.rb239
-rw-r--r--knife/spec/unit/knife/cookbook_download_spec.rb255
-rw-r--r--knife/spec/unit/knife/cookbook_list_spec.rb88
-rw-r--r--knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb72
-rw-r--r--knife/spec/unit/knife/cookbook_metadata_spec.rb182
-rw-r--r--knife/spec/unit/knife/cookbook_show_spec.rb253
-rw-r--r--knife/spec/unit/knife/cookbook_upload_spec.rb364
-rw-r--r--knife/spec/unit/knife/core/bootstrap_context_spec.rb287
-rw-r--r--knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb187
-rw-r--r--knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb198
-rw-r--r--knife/spec/unit/knife/core/gem_glob_loader_spec.rb242
-rw-r--r--knife/spec/unit/knife/core/hashed_command_loader_spec.rb112
-rw-r--r--knife/spec/unit/knife/core/node_editor_spec.rb211
-rw-r--r--knife/spec/unit/knife/core/object_loader_spec.rb81
-rw-r--r--knife/spec/unit/knife/core/status_presenter_spec.rb54
-rw-r--r--knife/spec/unit/knife/core/subcommand_loader_spec.rb64
-rw-r--r--knife/spec/unit/knife/core/ui_spec.rb656
-rw-r--r--knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb238
-rw-r--r--knife/spec/unit/knife/data_bag_create_spec.rb175
-rw-r--r--knife/spec/unit/knife/data_bag_edit_spec.rb126
-rw-r--r--knife/spec/unit/knife/data_bag_from_file_spec.rb174
-rw-r--r--knife/spec/unit/knife/data_bag_secret_options_spec.rb173
-rw-r--r--knife/spec/unit/knife/data_bag_show_spec.rb139
-rw-r--r--knife/spec/unit/knife/environment_compare_spec.rb112
-rw-r--r--knife/spec/unit/knife/environment_create_spec.rb91
-rw-r--r--knife/spec/unit/knife/environment_delete_spec.rb71
-rw-r--r--knife/spec/unit/knife/environment_edit_spec.rb79
-rw-r--r--knife/spec/unit/knife/environment_from_file_spec.rb90
-rw-r--r--knife/spec/unit/knife/environment_list_spec.rb54
-rw-r--r--knife/spec/unit/knife/environment_show_spec.rb52
-rw-r--r--knife/spec/unit/knife/key_create_spec.rb223
-rw-r--r--knife/spec/unit/knife/key_delete_spec.rb133
-rw-r--r--knife/spec/unit/knife/key_edit_spec.rb264
-rw-r--r--knife/spec/unit/knife/key_helper.rb74
-rw-r--r--knife/spec/unit/knife/key_list_spec.rb216
-rw-r--r--knife/spec/unit/knife/key_show_spec.rb126
-rw-r--r--knife/spec/unit/knife/node_bulk_delete_spec.rb94
-rw-r--r--knife/spec/unit/knife/node_delete_spec.rb77
-rw-r--r--knife/spec/unit/knife/node_edit_spec.rb116
-rw-r--r--knife/spec/unit/knife/node_environment_set_spec.rb61
-rw-r--r--knife/spec/unit/knife/node_from_file_spec.rb59
-rw-r--r--knife/spec/unit/knife/node_list_spec.rb62
-rw-r--r--knife/spec/unit/knife/node_policy_set_spec.rb122
-rw-r--r--knife/spec/unit/knife/node_run_list_add_spec.rb145
-rw-r--r--knife/spec/unit/knife/node_run_list_remove_spec.rb106
-rw-r--r--knife/spec/unit/knife/node_run_list_set_spec.rb115
-rw-r--r--knife/spec/unit/knife/node_show_spec.rb65
-rw-r--r--knife/spec/unit/knife/org_create_spec.rb76
-rw-r--r--knife/spec/unit/knife/org_delete_spec.rb41
-rw-r--r--knife/spec/unit/knife/org_edit_spec.rb49
-rw-r--r--knife/spec/unit/knife/org_list_spec.rb58
-rw-r--r--knife/spec/unit/knife/org_show_spec.rb45
-rw-r--r--knife/spec/unit/knife/org_user_add_spec.rb39
-rw-r--r--knife/spec/unit/knife/raw_spec.rb43
-rw-r--r--knife/spec/unit/knife/role_bulk_delete_spec.rb80
-rw-r--r--knife/spec/unit/knife/role_create_spec.rb80
-rw-r--r--knife/spec/unit/knife/role_delete_spec.rb67
-rw-r--r--knife/spec/unit/knife/role_edit_spec.rb77
-rw-r--r--knife/spec/unit/knife/role_env_run_list_add_spec.rb217
-rw-r--r--knife/spec/unit/knife/role_env_run_list_clear_spec.rb94
-rw-r--r--knife/spec/unit/knife/role_env_run_list_remove_spec.rb102
-rw-r--r--knife/spec/unit/knife/role_env_run_list_replace_spec.rb105
-rw-r--r--knife/spec/unit/knife/role_env_run_list_set_spec.rb99
-rw-r--r--knife/spec/unit/knife/role_from_file_spec.rb69
-rw-r--r--knife/spec/unit/knife/role_list_spec.rb54
-rw-r--r--knife/spec/unit/knife/role_run_list_add_spec.rb179
-rw-r--r--knife/spec/unit/knife/role_run_list_clear_spec.rb84
-rw-r--r--knife/spec/unit/knife/role_run_list_remove_spec.rb92
-rw-r--r--knife/spec/unit/knife/role_run_list_replace_spec.rb98
-rw-r--r--knife/spec/unit/knife/role_run_list_set_spec.rb89
-rw-r--r--knife/spec/unit/knife/role_show_spec.rb59
-rw-r--r--knife/spec/unit/knife/ssh_spec.rb403
-rw-r--r--knife/spec/unit/knife/ssl_check_spec.rb256
-rw-r--r--knife/spec/unit/knife/ssl_fetch_spec.rb222
-rw-r--r--knife/spec/unit/knife/status_spec.rb112
-rw-r--r--knife/spec/unit/knife/supermarket_download_spec.rb152
-rw-r--r--knife/spec/unit/knife/supermarket_install_spec.rb203
-rw-r--r--knife/spec/unit/knife/supermarket_list_spec.rb70
-rw-r--r--knife/spec/unit/knife/supermarket_search_spec.rb85
-rw-r--r--knife/spec/unit/knife/supermarket_share_spec.rb208
-rw-r--r--knife/spec/unit/knife/supermarket_unshare_spec.rb78
-rw-r--r--knife/spec/unit/knife/tag_create_spec.rb23
-rw-r--r--knife/spec/unit/knife/tag_delete_spec.rb25
-rw-r--r--knife/spec/unit/knife/tag_list_spec.rb23
-rw-r--r--knife/spec/unit/knife/user_create_spec.rb256
-rw-r--r--knife/spec/unit/knife/user_delete_spec.rb171
-rw-r--r--knife/spec/unit/knife/user_edit_spec.rb54
-rw-r--r--knife/spec/unit/knife/user_list_spec.rb73
-rw-r--r--knife/spec/unit/knife/user_password_spec.rb64
-rw-r--r--knife/spec/unit/knife/user_reregister_spec.rb56
-rw-r--r--knife/spec/unit/knife/user_show_spec.rb91
-rw-r--r--knife/spec/unit/knife_spec.rb634
-rw-r--r--lib/chef/action_collection.rb32
-rw-r--r--lib/chef/application.rb2
-rw-r--r--lib/chef/application/base.rb17
-rw-r--r--lib/chef/application/knife.rb234
-rw-r--r--lib/chef/application/windows_service.rb338
-rw-r--r--lib/chef/application/windows_service_manager.rb205
-rw-r--r--lib/chef/applications.rb1
-rw-r--r--lib/chef/chef_fs/command_line.rb7
-rw-r--r--lib/chef/chef_fs/file_pattern.rb4
-rw-r--r--lib/chef/chef_fs/file_system.rb19
-rw-r--r--lib/chef/chef_fs/knife.rb160
-rw-r--r--lib/chef/chef_fs/parallelizer.rb102
-rw-r--r--lib/chef/chef_fs/parallelizer/flatten_enumerable.rb35
-rw-r--r--lib/chef/chef_fs/parallelizer/parallel_enumerable.rb278
-rw-r--r--lib/chef/client.rb10
-rw-r--r--lib/chef/compliance/default_attributes.rb21
-rw-r--r--lib/chef/compliance/fetcher/automate.rb30
-rw-r--r--lib/chef/compliance/fetcher/chef_server.rb4
-rw-r--r--lib/chef/compliance/reporter/automate.rb25
-rw-r--r--lib/chef/compliance/reporter/chef_server_automate.rb21
-rw-r--r--lib/chef/compliance/reporter/cli.rb77
-rw-r--r--lib/chef/compliance/reporter/compliance_enforcer.rb4
-rw-r--r--lib/chef/compliance/reporter/json_file.rb9
-rw-r--r--lib/chef/compliance/runner.rb177
-rw-r--r--lib/chef/cookbook/cookbook_version_loader.rb6
-rw-r--r--lib/chef/cookbook/gem_installer.rb6
-rw-r--r--lib/chef/cookbook/synchronizer.rb8
-rw-r--r--lib/chef/cookbook_loader.rb6
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb244
-rw-r--r--lib/chef/cookbook_uploader.rb1
-rw-r--r--lib/chef/cookbook_version.rb30
-rw-r--r--lib/chef/data_bag.rb3
-rw-r--r--lib/chef/data_bag_item.rb16
-rw-r--r--lib/chef/data_collector.rb1
-rw-r--r--lib/chef/data_collector/run_end_message.rb2
-rw-r--r--lib/chef/delayed_evaluator.rb4
-rw-r--r--lib/chef/deprecated.rb8
-rw-r--r--lib/chef/dsl/chef_vault.rb12
-rw-r--r--lib/chef/dsl/declare_resource.rb15
-rw-r--r--lib/chef/dsl/reboot_pending.rb5
-rw-r--r--lib/chef/encrypted_data_bag_item/assertions.rb2
-rw-r--r--lib/chef/event_dispatch/base.rb3
-rw-r--r--lib/chef/exceptions.rb3
-rw-r--r--lib/chef/file_access_control/windows.rb8
-rw-r--r--lib/chef/file_cache.rb8
-rw-r--r--lib/chef/formatters/doc.rb3
-rw-r--r--lib/chef/formatters/error_inspectors/resource_failure_inspector.rb42
-rw-r--r--lib/chef/group.rb75
-rw-r--r--lib/chef/handler.rb54
-rw-r--r--lib/chef/handler/json_file.rb2
-rw-r--r--lib/chef/handler/slow_report.rb66
-rw-r--r--lib/chef/http/ssl_policies.rb41
-rw-r--r--lib/chef/knife.rb665
-rw-r--r--lib/chef/knife/bootstrap.rb1142
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb162
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb212
-rw-r--r--lib/chef/knife/bootstrap/train_connector.rb336
-rw-r--r--lib/chef/knife/client_bulk_delete.rb104
-rw-r--r--lib/chef/knife/client_create.rb101
-rw-r--r--lib/chef/knife/client_delete.rb62
-rw-r--r--lib/chef/knife/client_edit.rb52
-rw-r--r--lib/chef/knife/client_list.rb41
-rw-r--r--lib/chef/knife/client_reregister.rb58
-rw-r--r--lib/chef/knife/client_show.rb48
-rw-r--r--lib/chef/knife/config_list.rb139
-rw-r--r--lib/chef/knife/configure.rb150
-rw-r--r--lib/chef/knife/cookbook_bulk_delete.rb71
-rw-r--r--lib/chef/knife/cookbook_delete.rb151
-rw-r--r--lib/chef/knife/cookbook_download.rb142
-rw-r--r--lib/chef/knife/cookbook_metadata.rb106
-rw-r--r--lib/chef/knife/cookbook_metadata_from_file.rb49
-rw-r--r--lib/chef/knife/cookbook_show.rb98
-rw-r--r--lib/chef/knife/cookbook_upload.rb292
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb264
-rw-r--r--lib/chef/knife/core/cookbook_scm_repo.rb159
-rw-r--r--lib/chef/knife/core/gem_glob_loader.rb138
-rw-r--r--lib/chef/knife/core/hashed_command_loader.rb100
-rw-r--r--lib/chef/knife/core/node_editor.rb130
-rw-r--r--lib/chef/knife/core/node_presenter.rb158
-rw-r--r--lib/chef/knife/core/object_loader.rb115
-rw-r--r--lib/chef/knife/core/status_presenter.rb172
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb203
-rw-r--r--lib/chef/knife/core/ui.rb338
-rw-r--r--lib/chef/knife/core/windows_bootstrap_context.rb406
-rw-r--r--lib/chef/knife/data_bag_create.rb81
-rw-r--r--lib/chef/knife/data_bag_delete.rb49
-rw-r--r--lib/chef/knife/data_bag_edit.rb74
-rw-r--r--lib/chef/knife/data_bag_from_file.rb113
-rw-r--r--lib/chef/knife/data_bag_list.rb42
-rw-r--r--lib/chef/knife/data_bag_secret_options.rb122
-rw-r--r--lib/chef/knife/data_bag_show.rb69
-rw-r--r--lib/chef/knife/delete.rb125
-rw-r--r--lib/chef/knife/deps.rb156
-rw-r--r--lib/chef/knife/diff.rb83
-rw-r--r--lib/chef/knife/download.rb84
-rw-r--r--lib/chef/knife/edit.rb88
-rw-r--r--lib/chef/knife/environment_compare.rb128
-rw-r--r--lib/chef/knife/environment_create.rb52
-rw-r--r--lib/chef/knife/environment_delete.rb44
-rw-r--r--lib/chef/knife/environment_edit.rb44
-rw-r--r--lib/chef/knife/environment_from_file.rb84
-rw-r--r--lib/chef/knife/environment_list.rb41
-rw-r--r--lib/chef/knife/environment_show.rb47
-rw-r--r--lib/chef/knife/exec.rb99
-rw-r--r--lib/chef/knife/key_create.rb112
-rw-r--r--lib/chef/knife/key_delete.rb55
-rw-r--r--lib/chef/knife/key_edit.rb118
-rw-r--r--lib/chef/knife/key_list.rb90
-rw-r--r--lib/chef/knife/key_show.rb53
-rw-r--r--lib/chef/knife/list.rb177
-rw-r--r--lib/chef/knife/node_bulk_delete.rb75
-rw-r--r--lib/chef/knife/node_create.rb47
-rw-r--r--lib/chef/knife/node_delete.rb46
-rw-r--r--lib/chef/knife/node_edit.rb70
-rw-r--r--lib/chef/knife/node_environment_set.rb53
-rw-r--r--lib/chef/knife/node_from_file.rb51
-rw-r--r--lib/chef/knife/node_list.rb44
-rw-r--r--lib/chef/knife/node_policy_set.rb79
-rw-r--r--lib/chef/knife/node_run_list_add.rb104
-rw-r--r--lib/chef/knife/node_run_list_remove.rb67
-rw-r--r--lib/chef/knife/node_run_list_set.rb66
-rw-r--r--lib/chef/knife/node_show.rb62
-rw-r--r--lib/chef/knife/raw.rb123
-rw-r--r--lib/chef/knife/role_bulk_delete.rb66
-rw-r--r--lib/chef/knife/role_create.rb53
-rw-r--r--lib/chef/knife/role_delete.rb46
-rw-r--r--lib/chef/knife/role_edit.rb45
-rw-r--r--lib/chef/knife/role_env_run_list_add.rb87
-rw-r--r--lib/chef/knife/role_env_run_list_clear.rb55
-rw-r--r--lib/chef/knife/role_env_run_list_remove.rb57
-rw-r--r--lib/chef/knife/role_env_run_list_replace.rb60
-rw-r--r--lib/chef/knife/role_env_run_list_set.rb70
-rw-r--r--lib/chef/knife/role_from_file.rb51
-rw-r--r--lib/chef/knife/role_list.rb42
-rw-r--r--lib/chef/knife/role_run_list_add.rb87
-rw-r--r--lib/chef/knife/role_run_list_clear.rb55
-rw-r--r--lib/chef/knife/role_run_list_remove.rb56
-rw-r--r--lib/chef/knife/role_run_list_replace.rb60
-rw-r--r--lib/chef/knife/role_run_list_set.rb69
-rw-r--r--lib/chef/knife/role_show.rb48
-rw-r--r--lib/chef/knife/search.rb193
-rw-r--r--lib/chef/knife/serve.rb65
-rw-r--r--lib/chef/knife/show.rb72
-rw-r--r--lib/chef/knife/ssh.rb643
-rw-r--r--lib/chef/knife/ssl_check.rb284
-rw-r--r--lib/chef/knife/ssl_fetch.rb161
-rw-r--r--lib/chef/knife/status.rb98
-rw-r--r--lib/chef/knife/supermarket_download.rb121
-rw-r--r--lib/chef/knife/supermarket_install.rb192
-rw-r--r--lib/chef/knife/supermarket_share.rb166
-rw-r--r--lib/chef/knife/supermarket_unshare.rb61
-rw-r--r--lib/chef/knife/tag_create.rb52
-rw-r--r--lib/chef/knife/tag_delete.rb60
-rw-r--r--lib/chef/knife/tag_list.rb47
-rw-r--r--lib/chef/knife/upload.rb86
-rw-r--r--lib/chef/knife/user_create.rb107
-rw-r--r--lib/chef/knife/user_delete.rb44
-rw-r--r--lib/chef/knife/user_edit.rb52
-rw-r--r--lib/chef/knife/user_list.rb42
-rw-r--r--lib/chef/knife/user_reregister.rb59
-rw-r--r--lib/chef/knife/user_show.rb48
-rw-r--r--lib/chef/knife/xargs.rb282
-rw-r--r--lib/chef/mixin/get_source_from_package.rb2
-rw-r--r--lib/chef/node.rb41
-rw-r--r--lib/chef/node/attribute.rb10
-rw-r--r--lib/chef/node/immutable_collections.rb13
-rw-r--r--lib/chef/node/mixin/deep_merge_cache.rb18
-rw-r--r--lib/chef/org.rb5
-rw-r--r--lib/chef/policy_builder/policyfile.rb2
-rw-r--r--lib/chef/property.rb18
-rw-r--r--lib/chef/provider.rb2
-rw-r--r--lib/chef/provider/cron.rb2
-rw-r--r--lib/chef/provider/directory.rb12
-rw-r--r--lib/chef/provider/execute.rb3
-rw-r--r--lib/chef/provider/file.rb4
-rw-r--r--lib/chef/provider/git.rb22
-rw-r--r--lib/chef/provider/group/groupadd.rb6
-rw-r--r--lib/chef/provider/group/groupmod.rb6
-rw-r--r--lib/chef/provider/group/pw.rb6
-rw-r--r--lib/chef/provider/ifconfig.rb4
-rw-r--r--lib/chef/provider/link.rb6
-rw-r--r--lib/chef/provider/mount.rb19
-rw-r--r--lib/chef/provider/mount/aix.rb6
-rw-r--r--lib/chef/provider/mount/mount.rb12
-rw-r--r--lib/chef/provider/mount/windows.rb2
-rw-r--r--lib/chef/provider/package.rb110
-rw-r--r--lib/chef/provider/package/apt.rb28
-rw-r--r--lib/chef/provider/package/deb.rb6
-rw-r--r--lib/chef/provider/package/dnf.rb51
-rw-r--r--lib/chef/provider/package/dnf/dnf_helper.py52
-rw-r--r--lib/chef/provider/package/dnf/python_helper.rb29
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb4
-rw-r--r--lib/chef/provider/package/portage.rb4
-rw-r--r--lib/chef/provider/package/rubygems.rb28
-rw-r--r--lib/chef/provider/package/windows.rb6
-rw-r--r--lib/chef/provider/package/yum.rb5
-rw-r--r--lib/chef/provider/package/yum/python_helper.rb25
-rw-r--r--lib/chef/provider/package/yum/simplejson/LICENSE.txt79
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.py318
-rw-r--r--lib/chef/provider/package/yum/simplejson/__init__.pycbin12059 -> 0 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.py354
-rw-r--r--lib/chef/provider/package/yum/simplejson/decoder.pycbin11088 -> 0 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.py440
-rw-r--r--lib/chef/provider/package/yum/simplejson/encoder.pycbin13588 -> 0 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.py65
-rw-r--r--lib/chef/provider/package/yum/simplejson/scanner.pycbin2405 -> 0 bytes
-rw-r--r--lib/chef/provider/package/yum/simplejson/tool.py37
-rw-r--r--lib/chef/provider/package/yum/yum_helper.py114
-rw-r--r--lib/chef/provider/registry_key.rb2
-rw-r--r--lib/chef/provider/route.rb4
-rw-r--r--lib/chef/provider/service.rb12
-rw-r--r--lib/chef/provider/service/aixinit.rb2
-rw-r--r--lib/chef/provider/service/debian.rb2
-rw-r--r--lib/chef/provider/service/freebsd.rb36
-rw-r--r--lib/chef/provider/service/macosx.rb14
-rw-r--r--lib/chef/provider/service/systemd.rb57
-rw-r--r--lib/chef/provider/service/upstart.rb15
-rw-r--r--lib/chef/provider/service/windows.rb24
-rw-r--r--lib/chef/provider/subversion.rb22
-rw-r--r--lib/chef/provider/support/zypper_repo.erb6
-rw-r--r--lib/chef/provider/systemd_unit.rb46
-rw-r--r--lib/chef/provider/template/content.rb10
-rw-r--r--lib/chef/provider/user.rb4
-rw-r--r--lib/chef/provider/user/dscl.rb2
-rw-r--r--lib/chef/provider/user/mac.rb37
-rw-r--r--lib/chef/provider/user/pw.rb2
-rw-r--r--lib/chef/provider/user/windows.rb2
-rw-r--r--lib/chef/provider/windows_script.rb2
-rw-r--r--lib/chef/provider/yum_repository.rb4
-rw-r--r--lib/chef/provider/zypper_repository.rb60
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource.rb43
-rw-r--r--lib/chef/resource/alternatives.rb12
-rw-r--r--lib/chef/resource/apt_package.rb2
-rw-r--r--lib/chef/resource/apt_preference.rb4
-rw-r--r--lib/chef/resource/apt_repository.rb12
-rw-r--r--lib/chef/resource/apt_update.rb9
-rw-r--r--lib/chef/resource/archive_file.rb5
-rw-r--r--lib/chef/resource/breakpoint.rb2
-rw-r--r--lib/chef/resource/build_essential.rb8
-rw-r--r--lib/chef/resource/chef_client_config.rb8
-rw-r--r--lib/chef/resource/chef_client_cron.rb10
-rw-r--r--lib/chef/resource/chef_client_launchd.rb12
-rw-r--r--lib/chef/resource/chef_client_scheduled_task.rb30
-rw-r--r--lib/chef/resource/chef_client_systemd_timer.rb8
-rw-r--r--lib/chef/resource/chef_gem.rb4
-rw-r--r--lib/chef/resource/chef_handler.rb10
-rw-r--r--lib/chef/resource/chef_sleep.rb2
-rw-r--r--lib/chef/resource/chef_vault_secret.rb11
-rw-r--r--lib/chef/resource/chocolatey_config.rb8
-rw-r--r--lib/chef/resource/chocolatey_feature.rb8
-rw-r--r--lib/chef/resource/chocolatey_source.rb14
-rw-r--r--lib/chef/resource/cron/_cron_shared.rb4
-rw-r--r--lib/chef/resource/cron/cron_d.rb11
-rw-r--r--lib/chef/resource/cron_access.rb6
-rw-r--r--lib/chef/resource/dmg_package.rb16
-rw-r--r--lib/chef/resource/dpkg_package.rb2
-rw-r--r--lib/chef/resource/dsc_resource.rb2
-rw-r--r--lib/chef/resource/execute.rb17
-rw-r--r--lib/chef/resource/file.rb4
-rw-r--r--lib/chef/resource/gem_package.rb4
-rw-r--r--lib/chef/resource/group.rb12
-rw-r--r--lib/chef/resource/homebrew_cask.rb33
-rw-r--r--lib/chef/resource/homebrew_package.rb2
-rw-r--r--lib/chef/resource/homebrew_tap.rb11
-rw-r--r--lib/chef/resource/homebrew_update.rb4
-rw-r--r--lib/chef/resource/hostname.rb96
-rw-r--r--lib/chef/resource/http_request.rb4
-rw-r--r--lib/chef/resource/inspec_waiver_file_entry.rb156
-rw-r--r--lib/chef/resource/kernel_module.rb23
-rw-r--r--lib/chef/resource/locale.rb7
-rw-r--r--lib/chef/resource/lwrp_base.rb19
-rw-r--r--lib/chef/resource/macos_userdefaults.rb24
-rw-r--r--lib/chef/resource/mdadm.rb64
-rw-r--r--lib/chef/resource/mount.rb3
-rw-r--r--lib/chef/resource/ohai_hint.rb8
-rw-r--r--lib/chef/resource/openbsd_package.rb17
-rw-r--r--lib/chef/resource/openssl_dhparam.rb3
-rw-r--r--lib/chef/resource/openssl_ec_private_key.rb8
-rw-r--r--lib/chef/resource/openssl_ec_public_key.rb4
-rw-r--r--lib/chef/resource/openssl_rsa_private_key.rb6
-rw-r--r--lib/chef/resource/openssl_rsa_public_key.rb4
-rw-r--r--lib/chef/resource/openssl_x509_certificate.rb9
-rw-r--r--lib/chef/resource/openssl_x509_crl.rb4
-rw-r--r--lib/chef/resource/openssl_x509_request.rb4
-rw-r--r--lib/chef/resource/osx_profile.rb20
-rw-r--r--lib/chef/resource/plist.rb16
-rw-r--r--lib/chef/resource/powershell_package_source.rb6
-rw-r--r--lib/chef/resource/powershell_script.rb2
-rw-r--r--lib/chef/resource/reboot.rb47
-rw-r--r--lib/chef/resource/remote_directory.rb6
-rw-r--r--lib/chef/resource/remote_file.rb6
-rw-r--r--lib/chef/resource/rhsm_errata.rb19
-rw-r--r--lib/chef/resource/rhsm_errata_level.rb17
-rw-r--r--lib/chef/resource/rhsm_register.rb18
-rw-r--r--lib/chef/resource/rhsm_repo.rb23
-rw-r--r--lib/chef/resource/rhsm_subscription.rb8
-rw-r--r--lib/chef/resource/ruby.rb6
-rw-r--r--lib/chef/resource/ruby_block.rb2
-rw-r--r--lib/chef/resource/scm/git.rb2
-rw-r--r--lib/chef/resource/ssh_known_hosts_entry.rb11
-rw-r--r--lib/chef/resource/sudo.rb20
-rw-r--r--lib/chef/resource/swap_file.rb8
-rw-r--r--lib/chef/resource/sysctl.rb8
-rw-r--r--lib/chef/resource/systemd_unit.rb4
-rw-r--r--lib/chef/resource/template.rb6
-rw-r--r--lib/chef/resource/timezone.rb4
-rw-r--r--lib/chef/resource/user/dscl_user.rb35
-rw-r--r--lib/chef/resource/user/mac_user.rb2
-rw-r--r--lib/chef/resource/user/windows_user.rb5
-rw-r--r--lib/chef/resource/user_ulimit.rb4
-rw-r--r--lib/chef/resource/windows_ad_join.rb8
-rw-r--r--lib/chef/resource/windows_audit_policy.rb4
-rw-r--r--lib/chef/resource/windows_auto_run.rb7
-rw-r--r--lib/chef/resource/windows_certificate.rb346
-rw-r--r--lib/chef/resource/windows_defender.rb163
-rw-r--r--lib/chef/resource/windows_defender_exclusion.rb125
-rw-r--r--lib/chef/resource/windows_dfs_folder.rb8
-rw-r--r--lib/chef/resource/windows_dfs_namespace.rb8
-rw-r--r--lib/chef/resource/windows_dfs_server.rb4
-rw-r--r--lib/chef/resource/windows_dns_record.rb8
-rw-r--r--lib/chef/resource/windows_dns_zone.rb8
-rw-r--r--lib/chef/resource/windows_env.rb11
-rw-r--r--lib/chef/resource/windows_feature.rb12
-rw-r--r--lib/chef/resource/windows_feature_dism.rb12
-rw-r--r--lib/chef/resource/windows_feature_powershell.rb6
-rw-r--r--lib/chef/resource/windows_firewall_profile.rb8
-rw-r--r--lib/chef/resource/windows_firewall_rule.rb29
-rw-r--r--lib/chef/resource/windows_font.rb8
-rw-r--r--lib/chef/resource/windows_pagefile.rb173
-rw-r--r--lib/chef/resource/windows_path.rb8
-rw-r--r--lib/chef/resource/windows_printer.rb141
-rw-r--r--lib/chef/resource/windows_printer_port.rb115
-rw-r--r--lib/chef/resource/windows_security_policy.rb96
-rw-r--r--lib/chef/resource/windows_share.rb35
-rw-r--r--lib/chef/resource/windows_shortcut.rb10
-rw-r--r--lib/chef/resource/windows_task.rb26
-rw-r--r--lib/chef/resource/windows_uac.rb8
-rw-r--r--lib/chef/resource/windows_user_privilege.rb8
-rw-r--r--lib/chef/resource/windows_workgroup.rb7
-rw-r--r--lib/chef/resource/yum_package.rb20
-rw-r--r--lib/chef/resource/yum_repository.rb5
-rw-r--r--lib/chef/resource/zypper_package.rb8
-rw-r--r--lib/chef/resource/zypper_repository.rb36
-rw-r--r--lib/chef/resource_builder.rb10
-rw-r--r--lib/chef/resource_collection/resource_set.rb2
-rw-r--r--lib/chef/resource_inspector.rb6
-rw-r--r--lib/chef/resource_reporter.rb1
-rw-r--r--lib/chef/resources.rb6
-rw-r--r--lib/chef/run_lock.rb2
-rw-r--r--lib/chef/runner.rb2
-rw-r--r--lib/chef/shell.rb33
-rw-r--r--lib/chef/shell/ext.rb6
-rw-r--r--lib/chef/user.rb1
-rw-r--r--lib/chef/user_v1.rb7
-rw-r--r--lib/chef/util/dsc/configuration_generator.rb3
-rw-r--r--lib/chef/util/dsc/local_configuration_manager.rb2
-rw-r--r--lib/chef/version.rb2
-rw-r--r--lib/chef/version_string.rb2
-rw-r--r--lib/chef/win32/api.rb11
-rw-r--r--lib/chef/win32/registry.rb6
-rw-r--r--omnibus/Gemfile10
-rw-r--r--omnibus/Gemfile.lock239
-rw-r--r--omnibus/config/software/more-ruby-cleanup.rb9
-rw-r--r--omnibus/kitchen.yml2
-rw-r--r--omnibus/omnibus-test.ps126
-rw-r--r--omnibus/omnibus-test.sh38
-rwxr-xr-xomnibus/package-scripts/chef/postinst30
-rw-r--r--omnibus/resources/chef/msi/localization-en-us.wxl.erb4
-rw-r--r--omnibus/resources/chef/msi/source.wxs.erb40
-rw-r--r--omnibus_overrides.rb21
-rw-r--r--post-bundle-install.rb29
-rwxr-xr-xscripts/bk_tests/bk_container_prep.sh21
-rwxr-xr-xscripts/bk_tests/bk_linux_exec.sh41
-rw-r--r--scripts/bk_tests/bk_run_choco.ps19
-rw-r--r--scripts/bk_tests/bk_win_functional.ps143
-rw-r--r--scripts/bk_tests/bk_win_prep.ps120
-rw-r--r--scripts/ci/verify-plan.ps137
-rw-r--r--spec/data/cookbooks/openldap/libraries/openldap.rb2
-rw-r--r--spec/data/lwrp/resources/bar.rb2
-rw-r--r--spec/data/lwrp/resources/buck_passer.rb1
-rw-r--r--spec/data/lwrp/resources/buck_passer_2.rb1
-rw-r--r--spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb1
-rw-r--r--spec/data/lwrp/resources/foo.rb2
-rw-r--r--spec/data/lwrp/resources/inline_compiler.rb1
-rw-r--r--spec/data/lwrp/resources/monkey_name_printer.rb1
-rw-r--r--spec/data/lwrp/resources/paint_drying_watcher.rb1
-rw-r--r--spec/data/lwrp/resources/thumb_twiddler.rb1
-rw-r--r--spec/data/lwrp/resources_with_default_attributes/nodeattr.rb2
-rw-r--r--spec/data/lwrp_const_scoping/resources/conflict.rb1
-rw-r--r--spec/data/lwrp_override/resources/foo.rb1
-rw-r--r--spec/data/rubygems.org/latest_specs.4.8.gzbin0 -> 86 bytes
-rw-r--r--spec/data/rubygems.org/nonexistent_gembin0 -> 4 bytes
-rw-r--r--spec/data/rubygems.org/nonexistent_gem-info1
-rw-r--r--spec/data/rubygems.org/sexp_processorbin0 -> 2737 bytes
-rw-r--r--spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rzbin0 -> 519 bytes
-rw-r--r--spec/data/rubygems.org/sexp_processor-info49
-rw-r--r--spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/dependency1/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/dependency2/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb2
-rw-r--r--spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb1
-rw-r--r--spec/data/run_context/cookbooks/test/resources/resource.rb2
-rw-r--r--spec/data/ssl/binary/chef-rspec-der.certbin0 -> 1174 bytes
-rw-r--r--spec/data/ssl/binary/chef-rspec-der.keybin0 -> 1191 bytes
-rw-r--r--spec/data/trusted_certs_empty/README.md1
-rw-r--r--spec/functional/dsl/registry_helper_spec.rb2
-rw-r--r--spec/functional/knife/configure_spec.rb33
-rw-r--r--spec/functional/knife/cookbook_delete_spec.rb156
-rw-r--r--spec/functional/knife/exec_spec.rb55
-rw-r--r--spec/functional/knife/rehash_spec.rb39
-rw-r--r--spec/functional/knife/ssh_spec.rb352
-rwxr-xr-xspec/functional/resource/aixinit_service_spec.rb14
-rw-r--r--spec/functional/resource/apt_package_spec.rb2
-rw-r--r--spec/functional/resource/chocolatey_package_spec.rb13
-rw-r--r--spec/functional/resource/cron_spec.rb2
-rw-r--r--spec/functional/resource/dnf_package_spec.rb1288
-rw-r--r--spec/functional/resource/dsc_script_spec.rb2
-rw-r--r--spec/functional/resource/group_spec.rb2
-rw-r--r--spec/functional/resource/link_spec.rb2
-rw-r--r--spec/functional/resource/ohai_spec.rb12
-rw-r--r--spec/functional/resource/registry_spec.rb16
-rw-r--r--spec/functional/resource/remote_file_spec.rb2
-rw-r--r--spec/functional/resource/user/dscl_spec.rb188
-rw-r--r--spec/functional/resource/user/mac_user_spec.rb4
-rw-r--r--spec/functional/resource/windows_certificate_spec.rb637
-rw-r--r--spec/functional/resource/windows_env_spec.rb4
-rw-r--r--spec/functional/resource/windows_hostname_spec.rb91
-rw-r--r--spec/functional/resource/windows_pagefile_spec.rb98
-rw-r--r--spec/functional/resource/windows_service_spec.rb105
-rw-r--r--spec/functional/resource/yum_package_spec.rb923
-rw-r--r--spec/functional/version_spec.rb2
-rw-r--r--spec/functional/win32/registry_spec.rb2
-rw-r--r--spec/functional/win32/service_manager_spec.rb220
-rw-r--r--spec/integration/client/client_spec.rb30
-rw-r--r--spec/integration/client/exit_code_spec.rb2
-rw-r--r--spec/integration/client/ipv6_spec.rb2
-rw-r--r--spec/integration/compliance/compliance_spec.rb5
-rw-r--r--spec/integration/knife/chef_fs_data_store_spec.rb557
-rw-r--r--spec/integration/knife/chef_repo_path_spec.rb962
-rw-r--r--spec/integration/knife/chef_repository_file_system_spec.rb200
-rw-r--r--spec/integration/knife/chefignore_spec.rb301
-rw-r--r--spec/integration/knife/client_bulk_delete_spec.rb131
-rw-r--r--spec/integration/knife/client_create_spec.rb70
-rw-r--r--spec/integration/knife/client_delete_spec.rb64
-rw-r--r--spec/integration/knife/client_key_create_spec.rb66
-rw-r--r--spec/integration/knife/client_key_delete_spec.rb43
-rw-r--r--spec/integration/knife/client_key_list_spec.rb61
-rw-r--r--spec/integration/knife/client_key_show_spec.rb45
-rw-r--r--spec/integration/knife/client_list_spec.rb49
-rw-r--r--spec/integration/knife/client_show_spec.rb37
-rw-r--r--spec/integration/knife/common_options_spec.rb174
-rw-r--r--spec/integration/knife/config_list_spec.rb220
-rw-r--r--spec/integration/knife/config_show_spec.rb192
-rw-r--r--spec/integration/knife/config_use_spec.rb198
-rw-r--r--spec/integration/knife/cookbook_api_ipv6_spec.rb113
-rw-r--r--spec/integration/knife/cookbook_bulk_delete_spec.rb65
-rw-r--r--spec/integration/knife/cookbook_download_spec.rb72
-rw-r--r--spec/integration/knife/cookbook_list_spec.rb55
-rw-r--r--spec/integration/knife/cookbook_show_spec.rb149
-rw-r--r--spec/integration/knife/cookbook_upload_spec.rb128
-rw-r--r--spec/integration/knife/data_bag_create_spec.rb125
-rw-r--r--spec/integration/knife/data_bag_delete_spec.rb59
-rw-r--r--spec/integration/knife/data_bag_edit_spec.rb105
-rw-r--r--spec/integration/knife/data_bag_from_file_spec.rb116
-rw-r--r--spec/integration/knife/data_bag_list_spec.rb44
-rw-r--r--spec/integration/knife/data_bag_show_spec.rb95
-rw-r--r--spec/integration/knife/delete_spec.rb1018
-rw-r--r--spec/integration/knife/deps_spec.rb703
-rw-r--r--spec/integration/knife/diff_spec.rb605
-rw-r--r--spec/integration/knife/download_spec.rb1336
-rw-r--r--spec/integration/knife/environment_compare_spec.rb75
-rw-r--r--spec/integration/knife/environment_create_spec.rb41
-rw-r--r--spec/integration/knife/environment_delete_spec.rb37
-rw-r--r--spec/integration/knife/environment_from_file_spec.rb116
-rw-r--r--spec/integration/knife/environment_list_spec.rb42
-rw-r--r--spec/integration/knife/environment_show_spec.rb77
-rw-r--r--spec/integration/knife/list_spec.rb1060
-rw-r--r--spec/integration/knife/node_bulk_delete_spec.rb52
-rw-r--r--spec/integration/knife/node_create_spec.rb47
-rw-r--r--spec/integration/knife/node_delete_spec.rb48
-rw-r--r--spec/integration/knife/node_environment_set_spec.rb46
-rw-r--r--spec/integration/knife/node_from_file_spec.rb59
-rw-r--r--spec/integration/knife/node_list_spec.rb45
-rw-r--r--spec/integration/knife/node_run_list_add_spec.rb54
-rw-r--r--spec/integration/knife/node_run_list_remove_spec.rb36
-rw-r--r--spec/integration/knife/node_run_list_set_spec.rb41
-rw-r--r--spec/integration/knife/node_show_spec.rb36
-rw-r--r--spec/integration/knife/raw_spec.rb297
-rw-r--r--spec/integration/knife/redirection_spec.rb64
-rw-r--r--spec/integration/knife/role_bulk_delete_spec.rb52
-rw-r--r--spec/integration/knife/role_create_spec.rb41
-rw-r--r--spec/integration/knife/role_delete_spec.rb48
-rw-r--r--spec/integration/knife/role_from_file_spec.rb96
-rw-r--r--spec/integration/knife/role_list_spec.rb45
-rw-r--r--spec/integration/knife/role_show_spec.rb51
-rw-r--r--spec/integration/knife/search_node_spec.rb40
-rw-r--r--spec/integration/knife/show_spec.rb197
-rw-r--r--spec/integration/knife/upload_spec.rb1617
-rw-r--r--spec/integration/ohai/ohai_spec.rb13
-rw-r--r--spec/integration/recipes/accumulator_spec.rb14
-rw-r--r--spec/integration/recipes/lwrp_inline_resources_spec.rb6
-rw-r--r--spec/integration/recipes/lwrp_spec.rb4
-rw-r--r--spec/integration/recipes/notifies_spec.rb16
-rw-r--r--spec/integration/recipes/notifying_block_spec.rb3
-rw-r--r--spec/integration/recipes/recipe_dsl_spec.rb18
-rw-r--r--spec/integration/recipes/resource_action_spec.rb14
-rw-r--r--spec/integration/recipes/unified_mode_spec.rb72
-rw-r--r--spec/integration/recipes/use_partial_spec.rb5
-rw-r--r--spec/scripts/ssl-serve.rb47
-rw-r--r--spec/spec_helper.rb29
-rw-r--r--spec/support/chef_helpers.rb18
-rw-r--r--spec/support/lib/chef/resource/with_state.rb1
-rw-r--r--spec/support/lib/chef/resource/zen_follower.rb1
-rw-r--r--spec/support/lib/chef/resource/zen_master.rb1
-rw-r--r--spec/support/matchers/leak.rb16
-rw-r--r--spec/support/platform_helpers.rb13
-rw-r--r--spec/support/shared/functional/execute_resource.rb2
-rw-r--r--spec/support/shared/functional/win32_service.rb57
-rw-r--r--spec/support/shared/integration/integration_helper.rb1
-rw-r--r--spec/support/shared/unit/provider/file.rb16
-rw-r--r--spec/support/shared/unit/script_resource.rb4
-rw-r--r--spec/unit/application/knife_spec.rb241
-rw-r--r--spec/unit/application/solo_spec.rb4
-rw-r--r--spec/unit/chef_fs/diff_spec.rb2
-rw-r--r--spec/unit/chef_fs/file_system/repository/directory_spec.rb2
-rw-r--r--spec/unit/chef_fs/file_system_spec.rb2
-rw-r--r--spec/unit/chef_fs/parallelizer_spec.rb479
-rw-r--r--spec/unit/compliance/fetcher/automate_spec.rb18
-rw-r--r--spec/unit/compliance/reporter/automate_spec.rb28
-rw-r--r--spec/unit/compliance/reporter/chef_server_automate_spec.rb20
-rw-r--r--spec/unit/compliance/reporter/compliance_enforcer_spec.rb1
-rw-r--r--spec/unit/compliance/runner_spec.rb156
-rw-r--r--spec/unit/cookbook_site_streaming_uploader_spec.rb198
-rw-r--r--spec/unit/cookbook_version_spec.rb52
-rw-r--r--spec/unit/data_bag_item_spec.rb11
-rw-r--r--spec/unit/data_bag_spec.rb2
-rw-r--r--spec/unit/data_collector_spec.rb48
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb8
-rw-r--r--spec/unit/dsl/registry_helper_spec.rb2
-rw-r--r--spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb4
-rw-r--r--spec/unit/handler_spec.rb10
-rw-r--r--spec/unit/http/ssl_policies_spec.rb184
-rw-r--r--spec/unit/knife/bootstrap/chef_vault_handler_spec.rb152
-rw-r--r--spec/unit/knife/bootstrap/client_builder_spec.rb207
-rw-r--r--spec/unit/knife/bootstrap/train_connector_spec.rb244
-rw-r--r--spec/unit/knife/bootstrap_spec.rb2193
-rw-r--r--spec/unit/knife/client_bulk_delete_spec.rb166
-rw-r--r--spec/unit/knife/client_create_spec.rb169
-rw-r--r--spec/unit/knife/client_delete_spec.rb99
-rw-r--r--spec/unit/knife/client_edit_spec.rb53
-rw-r--r--spec/unit/knife/client_list_spec.rb34
-rw-r--r--spec/unit/knife/client_reregister_spec.rb62
-rw-r--r--spec/unit/knife/client_show_spec.rb52
-rw-r--r--spec/unit/knife/configure_client_spec.rb81
-rw-r--r--spec/unit/knife/configure_spec.rb190
-rw-r--r--spec/unit/knife/cookbook_bulk_delete_spec.rb87
-rw-r--r--spec/unit/knife/cookbook_delete_spec.rb239
-rw-r--r--spec/unit/knife/cookbook_download_spec.rb255
-rw-r--r--spec/unit/knife/cookbook_list_spec.rb88
-rw-r--r--spec/unit/knife/cookbook_metadata_from_file_spec.rb72
-rw-r--r--spec/unit/knife/cookbook_metadata_spec.rb182
-rw-r--r--spec/unit/knife/cookbook_show_spec.rb253
-rw-r--r--spec/unit/knife/cookbook_upload_spec.rb364
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb287
-rw-r--r--spec/unit/knife/core/cookbook_scm_repo_spec.rb187
-rw-r--r--spec/unit/knife/core/gem_glob_loader_spec.rb209
-rw-r--r--spec/unit/knife/core/hashed_command_loader_spec.rb112
-rw-r--r--spec/unit/knife/core/node_editor_spec.rb211
-rw-r--r--spec/unit/knife/core/object_loader_spec.rb81
-rw-r--r--spec/unit/knife/core/subcommand_loader_spec.rb64
-rw-r--r--spec/unit/knife/core/ui_spec.rb656
-rw-r--r--spec/unit/knife/core/windows_bootstrap_context_spec.rb238
-rw-r--r--spec/unit/knife/data_bag_create_spec.rb175
-rw-r--r--spec/unit/knife/data_bag_edit_spec.rb126
-rw-r--r--spec/unit/knife/data_bag_from_file_spec.rb174
-rw-r--r--spec/unit/knife/data_bag_secret_options_spec.rb173
-rw-r--r--spec/unit/knife/data_bag_show_spec.rb139
-rw-r--r--spec/unit/knife/environment_compare_spec.rb112
-rw-r--r--spec/unit/knife/environment_create_spec.rb91
-rw-r--r--spec/unit/knife/environment_delete_spec.rb71
-rw-r--r--spec/unit/knife/environment_edit_spec.rb79
-rw-r--r--spec/unit/knife/environment_from_file_spec.rb90
-rw-r--r--spec/unit/knife/environment_list_spec.rb54
-rw-r--r--spec/unit/knife/environment_show_spec.rb52
-rw-r--r--spec/unit/knife/key_create_spec.rb223
-rw-r--r--spec/unit/knife/key_delete_spec.rb133
-rw-r--r--spec/unit/knife/key_edit_spec.rb264
-rw-r--r--spec/unit/knife/key_helper.rb74
-rw-r--r--spec/unit/knife/key_list_spec.rb216
-rw-r--r--spec/unit/knife/key_show_spec.rb126
-rw-r--r--spec/unit/knife/node_bulk_delete_spec.rb94
-rw-r--r--spec/unit/knife/node_delete_spec.rb77
-rw-r--r--spec/unit/knife/node_edit_spec.rb116
-rw-r--r--spec/unit/knife/node_environment_set_spec.rb61
-rw-r--r--spec/unit/knife/node_from_file_spec.rb59
-rw-r--r--spec/unit/knife/node_list_spec.rb62
-rw-r--r--spec/unit/knife/node_policy_set_spec.rb122
-rw-r--r--spec/unit/knife/node_run_list_add_spec.rb145
-rw-r--r--spec/unit/knife/node_run_list_remove_spec.rb106
-rw-r--r--spec/unit/knife/node_run_list_set_spec.rb115
-rw-r--r--spec/unit/knife/node_show_spec.rb65
-rw-r--r--spec/unit/knife/raw_spec.rb43
-rw-r--r--spec/unit/knife/role_bulk_delete_spec.rb80
-rw-r--r--spec/unit/knife/role_create_spec.rb80
-rw-r--r--spec/unit/knife/role_delete_spec.rb67
-rw-r--r--spec/unit/knife/role_edit_spec.rb77
-rw-r--r--spec/unit/knife/role_env_run_list_add_spec.rb217
-rw-r--r--spec/unit/knife/role_env_run_list_clear_spec.rb94
-rw-r--r--spec/unit/knife/role_env_run_list_remove_spec.rb102
-rw-r--r--spec/unit/knife/role_env_run_list_replace_spec.rb105
-rw-r--r--spec/unit/knife/role_env_run_list_set_spec.rb99
-rw-r--r--spec/unit/knife/role_from_file_spec.rb69
-rw-r--r--spec/unit/knife/role_list_spec.rb54
-rw-r--r--spec/unit/knife/role_run_list_add_spec.rb179
-rw-r--r--spec/unit/knife/role_run_list_clear_spec.rb84
-rw-r--r--spec/unit/knife/role_run_list_remove_spec.rb92
-rw-r--r--spec/unit/knife/role_run_list_replace_spec.rb98
-rw-r--r--spec/unit/knife/role_run_list_set_spec.rb89
-rw-r--r--spec/unit/knife/role_show_spec.rb59
-rw-r--r--spec/unit/knife/ssh_spec.rb403
-rw-r--r--spec/unit/knife/ssl_check_spec.rb256
-rw-r--r--spec/unit/knife/ssl_fetch_spec.rb222
-rw-r--r--spec/unit/knife/status_spec.rb112
-rw-r--r--spec/unit/knife/supermarket_download_spec.rb152
-rw-r--r--spec/unit/knife/supermarket_install_spec.rb202
-rw-r--r--spec/unit/knife/supermarket_list_spec.rb70
-rw-r--r--spec/unit/knife/supermarket_search_spec.rb85
-rw-r--r--spec/unit/knife/supermarket_share_spec.rb209
-rw-r--r--spec/unit/knife/supermarket_unshare_spec.rb78
-rw-r--r--spec/unit/knife/tag_create_spec.rb23
-rw-r--r--spec/unit/knife/tag_delete_spec.rb25
-rw-r--r--spec/unit/knife/tag_list_spec.rb23
-rw-r--r--spec/unit/knife/user_create_spec.rb184
-rw-r--r--spec/unit/knife/user_delete_spec.rb46
-rw-r--r--spec/unit/knife/user_edit_spec.rb48
-rw-r--r--spec/unit/knife/user_list_spec.rb36
-rw-r--r--spec/unit/knife/user_reregister_spec.rb56
-rw-r--r--spec/unit/knife/user_show_spec.rb46
-rw-r--r--spec/unit/knife_spec.rb634
-rw-r--r--spec/unit/lwrp_spec.rb2
-rw-r--r--spec/unit/mixin/openssl_helper_spec.rb7
-rw-r--r--spec/unit/mixin/params_validate_spec.rb7
-rw-r--r--spec/unit/node/attribute_spec.rb2
-rw-r--r--spec/unit/node_spec.rb78
-rw-r--r--spec/unit/org_group_spec.rb45
-rw-r--r--spec/unit/property_spec.rb45
-rw-r--r--spec/unit/provider/cron_spec.rb2
-rw-r--r--spec/unit/provider/group/gpasswd_spec.rb4
-rw-r--r--spec/unit/provider/group/groupmod_spec.rb4
-rw-r--r--spec/unit/provider/group/pw_spec.rb4
-rw-r--r--spec/unit/provider/group_spec.rb2
-rw-r--r--spec/unit/provider/link_spec.rb2
-rw-r--r--spec/unit/provider/mount/mount_spec.rb52
-rw-r--r--spec/unit/provider/package/apt_spec.rb102
-rw-r--r--spec/unit/provider/package/deb_spec.rb6
-rw-r--r--spec/unit/provider/package/dnf/python_helper_spec.rb9
-rw-r--r--spec/unit/provider/package/freebsd/pkgng_spec.rb2
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb70
-rw-r--r--spec/unit/provider/package/yum/python_helper_spec.rb1
-rw-r--r--spec/unit/provider/service/arch_service_spec.rb1
-rw-r--r--spec/unit/provider/service/debian_service_spec.rb1
-rw-r--r--spec/unit/provider/service/macosx_spec.rb10
-rw-r--r--spec/unit/provider/service/systemd_service_spec.rb161
-rw-r--r--spec/unit/provider/service/upstart_service_spec.rb29
-rw-r--r--spec/unit/provider/service/windows_spec.rb4
-rw-r--r--spec/unit/provider/subversion_spec.rb4
-rw-r--r--spec/unit/provider/systemd_unit_spec.rb141
-rw-r--r--spec/unit/provider/user/dscl_spec.rb699
-rw-r--r--spec/unit/provider/zypper_repository_spec.rb17
-rw-r--r--spec/unit/provider_spec.rb8
-rw-r--r--spec/unit/resource/chef_client_cron_spec.rb16
-rw-r--r--spec/unit/resource/inspec_waiver_file_entry_spec.rb80
-rw-r--r--spec/unit/resource/powershell_script_spec.rb4
-rw-r--r--spec/unit/resource/user/windows_user_spec.rb36
-rw-r--r--spec/unit/resource/windows_certificate_spec.rb12
-rw-r--r--spec/unit/resource/windows_defender_exclusion_spec.rb62
-rw-r--r--spec/unit/resource/windows_defender_spec.rb71
-rw-r--r--spec/unit/resource/windows_firewall_rule_spec.rb19
-rw-r--r--spec/unit/resource/windows_pagefile_spec.rb13
-rw-r--r--spec/unit/resource/windows_task_spec.rb2
-rw-r--r--spec/unit/resource/zypper_repository_spec.rb2
-rw-r--r--spec/unit/resource_inspector_spec.rb9
-rw-r--r--spec/unit/resource_spec.rb46
-rw-r--r--spec/unit/user_spec.rb2
-rw-r--r--spec/unit/user_v1_spec.rb10
-rw-r--r--spec/unit/windows_service_spec.rb118
-rwxr-xr-xtasks/bin/run_external_test2
-rwxr-xr-xtasks/docs.rb44
-rw-r--r--tasks/rspec.rb15
-rw-r--r--tasks/spellcheck.rb7
1571 files changed, 62014 insertions, 53723 deletions
diff --git a/.expeditor/config.yml b/.expeditor/config.yml
index 69fe5b8f72..8df29c65a3 100644
--- a/.expeditor/config.yml
+++ b/.expeditor/config.yml
@@ -1,8 +1,5 @@
# Documentation available at https://expeditor.chef.io/docs/getting-started/
---
-# the name we use for this project when interacting with expeditor chatbot
-project:
- alias: chef-16
# The name of the product keys for this product (from mixlib-install)
product_key:
@@ -147,6 +144,21 @@ subscriptions:
- workload: ruby_gem_published:win32-taskscheduler-*
actions:
- bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:win32-certstore-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:win32-event-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:win32-mutex-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:win32-eventlog-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:win32-api-*
+ actions:
+ - bash:.expeditor/update_dep.sh
- workload: ruby_gem_published:ffi-yajl-*
actions:
- bash:.expeditor/update_dep.sh
@@ -162,3 +174,62 @@ subscriptions:
- workload: ruby_gem_published:ffi-libarchive-*
actions:
- bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:plist-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:ffi-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:net-ssh-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:tty-prompt-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:tty-screen-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:tty-table-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:pastel-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:erubis-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:bcrypt_pbkdf-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:ed25519-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:addressable-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:proxifier-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:syslog-logger-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:uuidtools-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:iniparse-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:net-sftp-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ - workload: ruby_gem_published:fauxhai-ng-*
+ actions:
+ - bash:.expeditor/update_dep.sh
+ # NOTE: The branch of Ohai here needs to be updated when setting up a stable branch of chef/chef
+ - workload: chef/ohai:master_completed:pull_request_merged:chef/ohai:master:*
+ actions:
+ - bash:.expeditor/update_bundler_dep.sh
+ # NOTE: When the stable branch of chef/chef is being cut you probably want to remove this subscription
+ - workload: chef/chefstyle:master_completed:pull_request_merged:chef/chefstyle:master:*
+ actions:
+ - bash:.expeditor/update_bundler_dep.sh
diff --git a/.expeditor/habitat-test.pipeline.yml b/.expeditor/habitat-test.pipeline.yml
index 7d2cc8f653..859d4eb1f1 100644
--- a/.expeditor/habitat-test.pipeline.yml
+++ b/.expeditor/habitat-test.pipeline.yml
@@ -11,7 +11,7 @@ steps:
- label: ":linux: Validate Linux"
commands:
- - sudo ./scripts/ci/install-hab.sh x86_64-linux
+ - sudo ./.expeditor/scripts/install-hab.sh x86_64-linux
- 'echo "--- :hammer_and_wrench: Installing $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUX"'
- sudo hab pkg install $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUX
- sudo ./habitat/tests/test.sh $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUX
@@ -23,7 +23,7 @@ steps:
- label: ":linux: Validate Linux (kernel2)"
commands:
- - sudo ./scripts/ci/install-hab.sh x86_64-linux-kernel2
+ - sudo ./.expeditor/scripts/install-hab.sh x86_64-linux-kernel2
- 'echo "--- :hammer_and_wrench: Installing $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUXKERNEL2"'
- sudo hab pkg install $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUXKERNEL2
- sudo ./habitat/tests/test.sh $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64LINUXKERNEL2
@@ -35,7 +35,7 @@ steps:
- label: ":windows: Validate Habitat Builds of Chef Infra"
commands:
- - powershell -File ./scripts/ci/ensure-minimum-viable-hab.ps1
+ - powershell -File ./.expeditor/scripts/ensure-minimum-viable-hab.ps1
- 'Write-Host "--- :hammer_and_wrench: Installing $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64WINDOWS"'
- hab pkg install $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64WINDOWS
- powershell -File ./habitat/tests/test.ps1 -PackageIdentifier $EXPEDITOR_PKG_IDENTS_CHEFINFRACLIENTX86_64WINDOWS
diff --git a/.expeditor/release.omnibus.yml b/.expeditor/release.omnibus.yml
index 9b7f397a33..a9d8aec620 100644
--- a/.expeditor/release.omnibus.yml
+++ b/.expeditor/release.omnibus.yml
@@ -5,6 +5,8 @@ test-path: omnibus/omnibus-test.sh
test-path-windows: omnibus/omnibus-test.ps1
fips-platforms:
- el-*-x86_64
+ - el-*-ppc64*
+ - ubuntu-*-x86_64
- windows-*
builder-to-testers-map:
aix-7.1-powerpc:
@@ -15,14 +17,13 @@ builder-to-testers-map:
- debian-10-x86_64
debian-10-aarch64:
- debian-10-aarch64
- el-6-i686:
- - el-6-i686
el-6-x86_64:
- el-6-x86_64
el-7-aarch64:
- el-7-aarch64
- - el-8-aarch64
- amazon-2-aarch64
+ el-8-aarch64:
+ - el-8-aarch64
el-7-ppc64:
- el-7-ppc64
el-7-ppc64le:
@@ -32,16 +33,19 @@ builder-to-testers-map:
- el-8-s390x
el-7-x86_64:
- el-7-x86_64
- - el-8-x86_64
- amazon-2-x86_64
+ el-8-x86_64:
+ - el-8-x86_64
freebsd-11-amd64:
- freebsd-11-amd64
- freebsd-12-amd64
- mac_os_x-10.13-x86_64:
- - mac_os_x-10.13-x86_64
+# - freebsd-13-amd64
+ mac_os_x-10.14-x86_64:
- mac_os_x-10.14-x86_64
- mac_os_x-10.15-x86_64
- - mac_os_x-11.0-x86_64
+ - mac_os_x-11-x86_64
+ mac_os_x-11-arm64:
+ - mac_os_x-11-arm64
sles-12-s390x:
- sles-12-s390x
- sles-15-s390x
@@ -50,15 +54,14 @@ builder-to-testers-map:
- sles-15-x86_64
sles-15-aarch64:
- sles-15-aarch64
-# solaris2-5.11-i386:
-# - solaris2-5.11-i386
-# solaris2-5.11-sparc:
-# - solaris2-5.11-sparc
+ solaris2-5.11-i386:
+ - solaris2-5.11-i386
+ solaris2-5.11-sparc:
+ - solaris2-5.11-sparc
ubuntu-18.04-aarch64:
- ubuntu-18.04-aarch64
- ubuntu-20.04-aarch64
- ubuntu-16.04-x86_64:
- - ubuntu-16.04-x86_64
+ ubuntu-18.04-x86_64:
- ubuntu-18.04-x86_64
- ubuntu-20.04-x86_64
windows-2012r2-i386:
diff --git a/.expeditor/scripts/bk_container_prep.sh b/.expeditor/scripts/bk_container_prep.sh
new file mode 100755
index 0000000000..e065f20579
--- /dev/null
+++ b/.expeditor/scripts/bk_container_prep.sh
@@ -0,0 +1,27 @@
+# This script gets a container ready to run our various tests in BuildKite
+
+echo "--- Container Config..."
+
+source /etc/os-release
+echo $PRETTY_NAME
+
+echo "ruby version:"
+ruby -v
+echo "bundler version:"
+bundle -v
+
+echo "--- Preparing Container..."
+
+export FORCE_FFI_YAJL="ext"
+export CHEF_LICENSE="accept-no-persist"
+export BUNDLE_GEMFILE="/workdir/Gemfile"
+
+# make sure we have the network tools in place for various network specs
+if [ -f /etc/debian_version ]; then
+ touch /etc/network/interfaces
+fi
+
+# remove default bundler config if there is one
+rm -f .bundle/config
+
+echo "+++ Run tests"
diff --git a/.expeditor/scripts/bk_linux_exec.sh b/.expeditor/scripts/bk_linux_exec.sh
new file mode 100755
index 0000000000..fbfd376581
--- /dev/null
+++ b/.expeditor/scripts/bk_linux_exec.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# Enable IPv6 in docker
+echo "--- Enabling ipv6 on docker"
+sudo systemctl stop docker
+dockerd_config="/etc/docker/daemon.json"
+sudo echo "$(jq '. + {"ipv6": true, "fixed-cidr-v6": "2001:2019:6002::/80", "ip-forward": false}' $dockerd_config)" > $dockerd_config
+sudo systemctl start docker
+
+# Install C and C++
+echo "--- Installing package deps"
+sudo yum install -y gcc gcc-c++ openssl-devel readline-devel zlib-devel
+
+# Install ASDF
+echo "--- Installing asdf to ${HOME}/.asdf"
+git clone https://github.com/asdf-vm/asdf.git "${HOME}/.asdf"
+cd "${HOME}/.asdf"; git checkout "$(git describe --abbrev=0 --tags)"; cd -
+. "${HOME}/.asdf/asdf.sh"
+
+# Install Ruby
+ruby_version=$(sed -n '/"ruby"/{s/.*version: "//;s/"//;p;}' omnibus_overrides.rb)
+echo "--- Installing Ruby $ruby_version"
+asdf plugin add ruby
+asdf install ruby $ruby_version
+asdf global ruby $ruby_version
+
+# Set Environment Variables
+export BUNDLE_GEMFILE=$PWD/kitchen-tests/Gemfile
+export FORCE_FFI_YAJL=ext
+export CHEF_LICENSE="accept-silent"
+
+# Update Gems
+echo "--- Installing Gems"
+echo 'gem: --no-document' >> ~/.gemrc
+sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
+bundle install --jobs=3 --retry=3 --path=../vendor/bundle
+
+echo "--- Config information"
+
+echo "!!!! RUBY VERSION !!!!"
+ruby --version
+echo "!!!! BUNDLER LOCATION !!!!"
+which bundle
+echo "!!!! BUNDLER VERSION !!!!"
+bundle -v
+echo "!!!! DOCKER VERSION !!!!"
+docker version
+echo "!!!! DOCKER STATUS !!!!"
+sudo service docker status
+
+echo "+++ Running tests"
diff --git a/.expeditor/scripts/bk_win_functional.ps1 b/.expeditor/scripts/bk_win_functional.ps1
new file mode 100644
index 0000000000..05f8e57248
--- /dev/null
+++ b/.expeditor/scripts/bk_win_functional.ps1
@@ -0,0 +1,33 @@
+Write-Output "--- system details"
+$Properties = 'Caption', 'CSName', 'Version', 'BuildType', 'OSArchitecture'
+Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table -AutoSize
+
+# chocolatey functional tests fail so delete the chocolatey binary to avoid triggering them
+Remove-Item -Path C:\ProgramData\chocolatey\bin\choco.exe -ErrorAction SilentlyContinue
+
+$ErrorActionPreference = 'Stop'
+
+Write-Output "--- Enable Ruby 2.7"
+
+Write-Output "Add Uru to Environment PATH"
+$env:PATH = "C:\Program Files (x86)\Uru;" + $env:PATH
+[Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine)
+
+Write-Output "Register Installed Ruby Version 2.7 With Uru"
+Start-Process "C:\Program Files (x86)\Uru\uru_rt.exe" -ArgumentList 'admin add C:\ruby27\bin' -Wait
+uru 271
+if (-not $?) { throw "Can't Activate Ruby. Did Uru Registration Succeed?" }
+ruby -v
+if (-not $?) { throw "Can't run Ruby. Is it installed?" }
+
+Write-Output "--- configure winrm"
+winrm quickconfig -q
+
+Write-Output "--- bundle install"
+bundle config set --local without 'omnibus_package'
+bundle install --jobs=3 --retry=3
+if (-not $?) { throw "Unable to install gem dependencies" }
+
+Write-Output "+++ bundle exec rake spec:functional"
+bundle exec rake spec:functional
+if (-not $?) { throw "Chef functional specs failing." }
diff --git a/scripts/bk_tests/bk_win_integration.ps1 b/.expeditor/scripts/bk_win_integration.ps1
index 6b0debc790..6b0debc790 100644
--- a/scripts/bk_tests/bk_win_integration.ps1
+++ b/.expeditor/scripts/bk_win_integration.ps1
diff --git a/.expeditor/scripts/bk_win_prep.ps1 b/.expeditor/scripts/bk_win_prep.ps1
new file mode 100644
index 0000000000..37796da468
--- /dev/null
+++ b/.expeditor/scripts/bk_win_prep.ps1
@@ -0,0 +1,15 @@
+echo "--- system details"
+$Properties = 'Caption', 'CSName', 'Version', 'BuildType', 'OSArchitecture'
+Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table -AutoSize
+
+echo "ruby version:"
+ruby -v
+if (-not $?) { throw "Can't run Ruby. Is it installed?" }
+
+echo "bundler version: "
+bundle --version
+if (-not $?) { throw "Can't run Bundler. Is it installed?" }
+
+echo "--- bundle install"
+bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
+if (-not $?) { throw "Unable to install gem dependencies" } \ No newline at end of file
diff --git a/scripts/bk_tests/bk_win_unit.ps1 b/.expeditor/scripts/bk_win_unit.ps1
index f1f28ade05..f1f28ade05 100644
--- a/scripts/bk_tests/bk_win_unit.ps1
+++ b/.expeditor/scripts/bk_win_unit.ps1
diff --git a/scripts/ci/ensure-minimum-viable-hab.ps1 b/.expeditor/scripts/ensure-minimum-viable-hab.ps1
index 10bfeb0fa8..10bfeb0fa8 100644
--- a/scripts/ci/ensure-minimum-viable-hab.ps1
+++ b/.expeditor/scripts/ensure-minimum-viable-hab.ps1
diff --git a/scripts/ci/install-hab.sh b/.expeditor/scripts/install-hab.sh
index 75e910bfab..75e910bfab 100755
--- a/scripts/ci/install-hab.sh
+++ b/.expeditor/scripts/install-hab.sh
diff --git a/.expeditor/scripts/verify-plan.ps1 b/.expeditor/scripts/verify-plan.ps1
new file mode 100644
index 0000000000..614d472964
--- /dev/null
+++ b/.expeditor/scripts/verify-plan.ps1
@@ -0,0 +1,37 @@
+#!/usr/bin/env powershell
+
+#Requires -Version 5
+
+param(
+ # The name of the plan that is to be built.
+ [string]$Plan
+)
+
+$env:HAB_ORIGIN = 'ci'
+$Plan = 'chef-infra-client'
+
+Write-Host "--- :8ball: :windows: Verifying $Plan"
+
+powershell -File "./.expeditor/scripts/ensure-minimum-viable-hab.ps1"
+if (-not $?) { throw "Could not ensure the minimum hab version required is installed." }
+
+Write-Host "--- :key: Generating fake origin key"
+hab origin key generate $env:HAB_ORIGIN
+
+$project_root = "$(git rev-parse --show-toplevel)"
+Set-Location $project_root
+
+Write-Host "--- :construction: Building $Plan"
+$env:DO_CHECK=$true; hab pkg build .
+if (-not $?) { throw "unable to build"}
+
+. results/last_build.ps1
+if (-not $?) { throw "unable to determine details about this build"}
+
+Write-Host "--- :hammer_and_wrench: Installing $pkg_ident"
+hab pkg install results/$pkg_artifact
+if (-not $?) { throw "unable to install this build"}
+
+Write-Host "--- :mag_right: Testing $Plan"
+powershell -File "./habitat/tests/test.ps1" -PackageIdentifier $pkg_ident
+if (-not $?) { throw "package didn't pass the test suite" }
diff --git a/scripts/ci/verify-plan.sh b/.expeditor/scripts/verify-plan.sh
index b207334267..b207334267 100755
--- a/scripts/ci/verify-plan.sh
+++ b/.expeditor/scripts/verify-plan.sh
diff --git a/.expeditor/update_bundler_dep.sh b/.expeditor/update_bundler_dep.sh
new file mode 100755
index 0000000000..71575a6c76
--- /dev/null
+++ b/.expeditor/update_bundler_dep.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+############################################################################
+# What is this script?
+#
+# Chef uses a workflow tool called Expeditor to manage version bumps, changelogs
+# and releases. When a dependency of chef is released, expeditor is triggered
+# against this repository to run this script. It bumps our gem lock files and opens
+# a PR. That way humans can do hard work and bots can open gem bump PRs.
+############################################################################
+
+set -evx
+
+branch="expeditor/${EXPEDITOR_REPO}_${EXPEDITOR_LATEST_COMMIT}"
+git checkout -b "$branch"
+
+bundle lock --update
+
+git add .
+
+# give a friendly message for the commit and make sure it's noted for any future audit of our codebase that no
+# DCO sign-off is needed for this sort of PR since it contains no intellectual property
+git commit --message "Bump $EXPEDITOR_REPO to $EXPEDITOR_LATEST_COMMIT" --message "This pull request was triggered automatically via Expeditor when $DEPNAME $EXPEDITOR_LATEST_COMMIT was merged." --message "This change falls under the obvious fix policy so no Developer Certificate of Origin (DCO) sign-off is required."
+
+open_pull_request "$EXPEDITOR_BRANCH"
+
+# Get back to master and cleanup the leftovers - any changed files left over at the end of this script will get committed to master.
+git checkout -
+git branch -D "$branch"
diff --git a/.expeditor/update_dep.sh b/.expeditor/update_dep.sh
index baf950c408..55258c66f8 100644..100755
--- a/.expeditor/update_dep.sh
+++ b/.expeditor/update_dep.sh
@@ -11,11 +11,14 @@
set -evx
+REPONAME=$(echo $EXPEDITOR_REPO | cut -d '/' -f 2)
+DEPNAME="${EXPEDITOR_GEM_NAME:-${REPONAME:?Could not find gem name}}"
+
function new_gem_included() {
- git diff | grep -E '^\+' | grep "${EXPEDITOR_GEM_NAME} (${EXPEDITOR_VERSION})"
+ git diff | grep -E '^\+' | grep "${DEPNAME} (${EXPEDITOR_VERSION})"
}
-branch="expeditor/${EXPEDITOR_GEM_NAME}_${EXPEDITOR_VERSION}"
+branch="expeditor/${EXPEDITOR_BRANCH}_${DEPNAME}_${EXPEDITOR_VERSION}"
git checkout -b "$branch"
tries=12
@@ -23,10 +26,10 @@ for (( i=1; i<=$tries; i+=1 )); do
bundle lock --update
new_gem_included && break || sleep 20
if [ $i -eq $tries ]; then
- echo "Searching for '${EXPEDITOR_GEM_NAME} (${EXPEDITOR_VERSION})' ${i} times and did not find it"
+ echo "Searching for '${DEPNAME} (${EXPEDITOR_VERSION})' ${i} times and did not find it"
exit 1
else
- echo "Searched ${i} times for '${EXPEDITOR_GEM_NAME} (${EXPEDITOR_VERSION})'"
+ echo "Searched ${i} times for '${DEPNAME} (${EXPEDITOR_VERSION})'"
fi
done
@@ -34,7 +37,7 @@ git add .
# give a friendly message for the commit and make sure it's noted for any future audit of our codebase that no
# DCO sign-off is needed for this sort of PR since it contains no intellectual property
-git commit --message "Bump $EXPEDITOR_GEM_NAME to $EXPEDITOR_VERSION" --message "This pull request was triggered automatically via Expeditor when $EXPEDITOR_GEM_NAME $EXPEDITOR_VERSION was promoted to Rubygems." --message "This change falls under the obvious fix policy so no Developer Certificate of Origin (DCO) sign-off is required."
+git commit --message "Bump $DEPNAME to $EXPEDITOR_VERSION" --message "This pull request was triggered automatically via Expeditor when $DEPNAME $EXPEDITOR_VERSION was promoted to Rubygems." --message "This change falls under the obvious fix policy so no Developer Certificate of Origin (DCO) sign-off is required."
open_pull_request "$EXPEDITOR_BRANCH"
diff --git a/.expeditor/update_version.sh b/.expeditor/update_version.sh
index 2a75c4b2f9..95840cadfc 100755
--- a/.expeditor/update_version.sh
+++ b/.expeditor/update_version.sh
@@ -16,6 +16,7 @@ VERSION=$(cat VERSION)
sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"${VERSION}\"/" chef-config/lib/chef-config/version.rb
sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"${VERSION}\"/" chef-bin/lib/chef-bin/version.rb
sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"${VERSION}\"/" chef-utils/lib/chef-utils/version.rb
+sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"${VERSION}\"/" knife/lib/chef/knife/version.rb
sed -i -r "s/VersionString\.new\(\".+\"\)/VersionString.new(\"${VERSION}\")/" lib/chef/version.rb
# Update the version inside Gemfile.lock
diff --git a/.expeditor/verify.pipeline.yml b/.expeditor/verify.pipeline.yml
index 5c58574cb3..b1ec9b73f2 100644
--- a/.expeditor/verify.pipeline.yml
+++ b/.expeditor/verify.pipeline.yml
@@ -7,194 +7,220 @@ expeditor:
retry:
automatic:
limit: 1
- timeout_in_minutes: 30
+ timeout_in_minutes: 45
steps:
#########################################################################
- # Tests Ruby 2.7
+ # Tests Ruby 2.6
#########################################################################
-- label: "Integration Ubuntu 18.04 :ruby: 2.7"
+- label: "chef-utils Unit :ruby: 2.6"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - cd chef-utils
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
+ - bundle exec rake spec
+ expeditor:
+ executor:
+ docker:
+ image: rubydistros/ubuntu-18.04:2.6
+
+- label: "chef-config Unit :ruby: 2.6"
+ commands:
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - cd chef-config
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
+ - bundle exec rake spec
+ expeditor:
+ executor:
+ docker:
+ image: rubydistros/ubuntu-18.04:2.6
+
+#########################################################################
+ # Tests Ruby 3.0
+#########################################################################
+
+- label: "Integration Ubuntu 18.04 :ruby: 3.0"
+ commands:
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:integration
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
privileged: true
-- label: "Functional Ubuntu 18.04 :ruby: 2.7"
+- label: "Functional Ubuntu 18.04 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- apt-get update -y
- apt-get install -y cron locales # needed for functional tests to pass
- - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:functional
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
privileged: true
-- label: "Unit Ubuntu 18.04 :ruby: 2.7"
+- label: "Unit Ubuntu 18.04 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:unit
- bundle exec rake component_specs
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
-- label: "Integration Ubuntu 20.04 :ruby: 2.7"
+- label: "Integration Ubuntu 20.04 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:integration
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-20.04:2.7
+ image: rubydistros/ubuntu-20.04:3.0
privileged: true
-- label: "Functional Ubuntu 20.04 :ruby: 2.7"
+- label: "Functional Ubuntu 20.04 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- apt-get update -y
- apt-get install -y cron locales # needed for functional tests to pass
- - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:functional
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-20.04:2.7
+ image: rubydistros/ubuntu-20.04:3.0
privileged: true
-- label: "Unit Ubuntu 20.04 :ruby: 2.7"
+- label: "Unit Ubuntu 20.04 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:unit
- bundle exec rake component_specs
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-20.04:2.7
+ image: rubydistros/ubuntu-20.04:3.0
-- label: "Integration CentOS 7 :ruby: 2.7"
+- label: "Integration CentOS 7 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:integration
expeditor:
executor:
docker:
- image: rubydistros/centos-7:2.7
+ image: rubydistros/centos-7:3.0
privileged: true
-- label: "Functional CentOS 7 :ruby: 2.7"
+- label: "Functional CentOS 7 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - yum install -y crontabs e2fsprogs util-linux
- - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - yum install -y crontabs e2fsprogs
+ - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:functional
expeditor:
executor:
docker:
- image: rubydistros/centos-7:2.7
+ image: rubydistros/centos-7:3.0
privileged: true
-- label: "Unit CentOS 7 :ruby: 2.7"
+- label: "Unit CentOS 7 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:unit
- bundle exec rake component_specs
expeditor:
executor:
docker:
- image: rubydistros/centos-7:2.7
+ image: rubydistros/centos-7:3.0
-- label: "Integration openSUSE 15 :ruby: 2.7"
+- label: "Integration openSUSE 15 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- zypper install -y cron insserv-compat
- cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:integration
expeditor:
executor:
docker:
- image: rubydistros/opensuse-15:2.7
+ image: rubydistros/opensuse-15:3.0
privileged: true
-- label: "Functional openSUSE 15 :ruby: 2.7"
+- label: "Functional openSUSE 15 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- zypper install -y cronie insserv-compat
- - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:functional
expeditor:
executor:
docker:
- image: rubydistros/opensuse-15:2.7
+ image: rubydistros/opensuse-15:3.0
privileged: true
-- label: "Unit openSUSE 15 :ruby: 2.7"
+- label: "Unit openSUSE 15 :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- zypper install -y cron insserv-compat
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:unit
- bundle exec rake component_specs
expeditor:
executor:
docker:
- image: rubydistros/opensuse-15:2.7
+ image: rubydistros/opensuse-15:3.0
-- label: "Integration Fedora :ruby: 2.7"
+- label: "Integration Fedora :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:integration
expeditor:
executor:
docker:
- image: rubydistros/fedora-latest:2.7
+ image: rubydistros/fedora-latest:3.0
privileged: true
-- label: "Functional Fedora :ruby: 2.7"
+- label: "Functional Fedora :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - yum install -y crontabs e2fsprogs util-linux
- - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - dnf install -y crontabs e2fsprogs
+ - cd /workdir; bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:functional
expeditor:
executor:
docker:
- image: rubydistros/fedora-latest:2.7
+ image: rubydistros/fedora-latest:3.0
privileged: true
environment:
- FORCE_FFI_YAJL=ext
- CHEF_LICENSE=accept-no-persist
-- label: "Unit Fedora :ruby: 2.7"
+- label: "Unit Fedora :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
+ - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec rake spec:unit
- bundle exec rake component_specs
expeditor:
executor:
docker:
- image: rubydistros/fedora-latest:2.7
+ image: rubydistros/fedora-latest:3.0
-- label: "Functional Windows :ruby: 2.7"
+- label: "Functional Windows :ruby: 3.0"
commands:
- - scripts/bk_tests/bk_win_functional.ps1
+ - .expeditor/scripts/bk_win_functional.ps1
expeditor:
executor:
windows:
@@ -202,151 +228,82 @@ steps:
single-use: true
shell: ["powershell", "-Command"]
-- label: "Integration Windows :ruby: 2.7"
+- label: "Integration Windows :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_win_integration.ps1
+ - /workdir/.expeditor/scripts/bk_win_integration.ps1
expeditor:
executor:
docker:
host_os: windows
- image: rubydistros/windows-2019:2.7
+ image: rubydistros/windows-2019:3.0
environment:
- FORCE_FFI_YAJL=ext
- CHEF_LICENSE=accept-no-persist
shell: ["powershell", "-Command"]
-- label: "Chocolatey Windows :ruby: 2.7"
- commands:
- - /workdir/scripts/bk_tests/bk_run_choco.ps1
- expeditor:
- executor:
- docker:
- host_os: windows
- image: rubydistros/windows-2019:2.7
- shell: ["powershell", "-Command"]
-
-- label: "Unit Windows :ruby: 2.7"
+- label: "Unit Windows :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_win_unit.ps1
+ - /workdir/.expeditor/scripts/bk_win_unit.ps1
expeditor:
executor:
docker:
host_os: windows
- image: rubydistros/windows-2019:2.7
+ image: rubydistros/windows-2019:3.0
environment:
- FORCE_FFI_YAJL=ext
- CHEF_LICENSE=accept-no-persist
shell: ["powershell", "-Command"]
#########################################################################
-# Tests Ruby 2.6
-#########################################################################
-
-- label: "Chefstyle :ruby: 2.6"
- commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package ruby_prof
- - bundle exec rake style
- expeditor:
- executor:
- docker:
- image: rubydistros/ubuntu-18.04:2.6
-
-- label: "Integration :ruby: 2.6"
- commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- - bundle exec rake spec:integration
- expeditor:
- executor:
- docker:
- image: rubydistros/ubuntu-18.04:2.6
- privileged: true
-
-- label: "Functional :ruby: 2.6"
- commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - apt-get update -y
- - apt-get install -y cron locales net-tools # needed for functional tests to pass
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- - bundle exec rake spec:functional
- expeditor:
- executor:
- docker:
- image: rubydistros/ubuntu-18.04:2.6
- privileged: true
-
-- label: "Unit :ruby: 2.6"
- commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- - bundle exec rake spec:unit
- - bundle exec rake component_specs
- expeditor:
- executor:
- docker:
- image: rubydistros/ubuntu-18.04:2.6
-
-#########################################################################
# EXTERNAL GEM TESTING
#########################################################################
-- label: "chef-sugar gem :ruby: 2.7"
- commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
- - bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- - bundle exec tasks/bin/run_external_test chef/chef-sugar master rake
- expeditor:
- executor:
- docker:
- image: rubydistros/ubuntu-18.04:2.7
-
-- label: "chef-zero gem :ruby: 2.7"
+- label: "chef-zero gem :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec tasks/bin/run_external_test chef/chef-zero master rake pedant
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
environment:
- PEDANT_OPTS=--skip-oc_id
- CHEF_FS=true
-- label: "cheffish gem :ruby: 2.7"
+- label: "cheffish gem :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec tasks/bin/run_external_test chef/cheffish master rake spec
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.6
+ image: rubydistros/ubuntu-18.04:3.0
-- label: "chefspec gem :ruby: 2.7"
+- label: "chefspec gem :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec tasks/bin/run_external_test chefspec/chefspec master rake
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
-- label: "knife-windows gem :ruby: 2.7"
+- label: "knife-windows gem :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
- bundle exec tasks/bin/run_external_test chef/knife-windows master rake spec
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
+ image: rubydistros/ubuntu-18.04:3.0
-- label: "berkshelf gem :ruby: 2.7"
+- label: "berkshelf gem :ruby: 3.0"
commands:
- - /workdir/scripts/bk_tests/bk_container_prep.sh
+ - /workdir/.expeditor/scripts/bk_container_prep.sh
- apt-get update -y
- apt-get install -y graphviz
- bundle install --jobs=3 --retry=3 --path=vendor/bundle --without omnibus_package
@@ -354,251 +311,12 @@ steps:
expeditor:
executor:
docker:
- image: rubydistros/ubuntu-18.04:2.7
-
-#########################################################################
- # START TEST KITCHEN ONLY
-#########################################################################
-
-- label: "Kitchen: Amazon Linux 201X"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-amazonlinux
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Amazon Linux 2"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-amazonlinux-2
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Ubuntu 16.04"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-ubuntu-1604
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- UBUNTU: "16.04"
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Ubuntu 18.04"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-ubuntu-1804
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Ubuntu 20.04"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-ubuntu-2004
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Ubuntu 20.10"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-ubuntu-2010
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Debian 9"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-debian-9
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Debian 10"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-debian-10
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: CentOS 6"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-centos-6
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: CentOS 7"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-centos-7
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: CentOS 8"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-centos-8
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Oracle Linux 7"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-oraclelinux-7
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Oracle Linux 8"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-oraclelinux-8
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: Fedora latest"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-fedora-latest
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Kitchen: openSUSE Leap: 15"
- commands:
- - scripts/bk_tests/bk_linux_exec.sh
- - cd kitchen-tests
- - /opt/omnibus-toolchain/bin/bundle exec kitchen test end-to-end-opensuse-leap-15
- artifact_paths:
- - $PWD/.kitchen/logs/kitchen.log
- env:
- KITCHEN_YAML: kitchen.yml
- expeditor:
- executor:
- linux:
- privileged: true
- single-use: true
-
-- label: "Spellcheck"
- commands:
- - npm install -g cspell
- - rake -f tasks/spellcheck.rb spellcheck
- soft_fail: true
- expeditor:
- executor:
- docker:
+ image: rubydistros/ubuntu-18.04:3.0
- label: ":habicat: Linux plan"
commands:
- - sudo ./scripts/ci/install-hab.sh 'x86_64-linux'
- - sudo ./scripts/ci/verify-plan.sh
+ - sudo ./.expeditor/scripts/install-hab.sh 'x86_64-linux'
+ - sudo ./.expeditor/scripts/verify-plan.sh
timeout_in_minutes: 60
expeditor:
executor:
@@ -608,8 +326,8 @@ steps:
- label: ":habicat: Linux plan (kernel2)"
commands:
- - sudo ./scripts/ci/install-hab.sh 'x86_64-linux-kernel2'
- - sudo ./scripts/ci/verify-plan.sh
+ - sudo ./.expeditor/scripts/install-hab.sh 'x86_64-linux-kernel2'
+ - sudo ./.expeditor/scripts/verify-plan.sh
timeout_in_minutes: 60
expeditor:
executor:
@@ -619,7 +337,7 @@ steps:
- label: ":habicat: Windows plan"
commands:
- - ./scripts/ci/verify-plan.ps1
+ - ./.expeditor/scripts/verify-plan.ps1
timeout_in_minutes: 60
expeditor:
executor:
diff --git a/.github/workflows/func_spec.yml b/.github/workflows/func_spec.yml
new file mode 100644
index 0000000000..5913fc31f4
--- /dev/null
+++ b/.github/workflows/func_spec.yml
@@ -0,0 +1,25 @@
+---
+name: func_spec
+
+"on":
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ choco:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-2019, windows-2016]
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
+ ruby: [2.7, '3.0']
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ bundler-cache: true
+ - run: bundle exec rspec spec/functional/resource/chocolatey_package_spec.rb
diff --git a/.github/workflows/kitchen.yml b/.github/workflows/kitchen.yml
new file mode 100644
index 0000000000..be95d960fa
--- /dev/null
+++ b/.github/workflows/kitchen.yml
@@ -0,0 +1,126 @@
+---
+name: kitchen
+
+"on":
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ windows:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-2019, windows-2016]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: 'Install Chef/Ohai from Omnitruck'
+ id: install_chef
+ run: |
+ . { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -project chef -channel current
+ $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
+ chef-client -v
+ ohai -v
+ rake --version
+ bundle -v
+ - name: 'Upgrade Chef/Ohai via Appbundler'
+ id: upgrade
+ run: |
+ $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
+ $env:OHAI_VERSION = ( Select-String -Path .\Gemfile.lock -Pattern '(?<=ohai \()\d.*(?=\))' | ForEach-Object { $_.Matches[0].Value } )
+ gem install appbundler appbundle-updater --no-doc
+ appbundle-updater chef chef $env:GITHUB_SHA --tarball --github chef/chef
+ Write-Output "Installed Chef / Ohai release:"
+ chef-client -v
+ ohai -v
+ - name: 'Run end_to_end::default recipe'
+ id: run
+ run: |
+ cd kitchen-tests
+ $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
+ bundle config set without 'omnibus_package'
+ bundle install --jobs=3 --retry=3 --path=vendor/bundle
+ gem install berkshelf --no-doc
+ # berks emits a ruby warning when it loads net/http due to a previously
+ # defined constant. Even though it is just a warning, powershell immediately
+ # exits 1. I'm not sure why but this just suppresses the warnings.
+ $env:RUBYOPT="-W0"
+ berks vendor cookbooks
+ # restore the default warning level
+ $env:RUBYOPT="-W1"
+ chef-client -z -o end_to_end --chef-license accept-no-persist
+
+ macos:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-10.15] # macos-11.0 is not public for now
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: 'Install Chef/Ohai from Omnitruck'
+ id: install_chef
+ run: |
+ brew install coreutils
+ curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -c current
+ /opt/chef/bin/chef-client -v
+ /opt/chef/bin/ohai -v
+ /opt/chef/embedded/bin/rake --version
+ /opt/chef/embedded/bin/bundle -v
+ - name: 'Upgrade Chef/Ohai via Appbundler'
+ id: upgrade
+ run: |
+ OHAI_VERSION=$(sed -n '/ohai .[0-9]/{s/.*(//;s/)//;p;}' Gemfile.lock)
+ sudo /opt/chef/embedded/bin/gem install appbundler appbundle-updater --no-doc
+ sudo /opt/chef/embedded/bin/appbundle-updater chef chef $GITHUB_SHA --tarball --github chef/chef
+ echo "Installed Chef / Ohai release:"
+ /opt/chef/bin/chef-client -v
+ /opt/chef/bin/ohai -v
+ - name: 'Run end_to_end::default recipe'
+ id: run
+ run: |
+ cd kitchen-tests
+ sudo /opt/chef/embedded/bin/bundle config set without 'omnibus_package'
+ sudo /opt/chef/embedded/bin/bundle install --jobs=3 --retry=3 --path=vendor/bundle
+ sudo /opt/chef/embedded/bin/gem install berkshelf --no-doc
+ sudo /opt/chef/embedded/bin/berks vendor cookbooks
+ sudo /opt/chef/bin/chef-client -z -o end_to_end --chef-license accept-no-persist
+
+ linux:
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - 'amazonlinux-2'
+ - 'centos-6'
+ - 'centos-7'
+ - 'centos-8'
+ - 'debian-9'
+ - 'debian-10'
+ - 'debian-11'
+ - 'fedora-latest'
+ - 'opensuse-leap-15'
+ - 'oraclelinux-7'
+ - 'oraclelinux-8'
+ - 'ubuntu-1804'
+ - 'ubuntu-2004'
+ - 'ubuntu-2104'
+ runs-on: ubuntu-latest
+ env:
+ FORCE_FFI_YAJL: ext
+ CHEF_LICENSE: accept-no-persist
+ steps:
+ - name: Check out code
+ uses: actions/checkout@master
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 2.7
+ bundler-cache: true
+ working-directory: kitchen-tests
+ - name: Run Test Kitchen
+ working-directory: kitchen-tests
+ run: |
+ bundle exec kitchen test end-to-end-${{ matrix.os }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000000..9ea4b32227
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,36 @@
+---
+name: lint
+
+"on":
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ chefstyle:
+ runs-on: ubuntu-latest
+ env:
+ BUNDLE_WITHOUT: ruby_shadow:omnibus_package
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 2.7
+ bundler-cache: true
+ - uses: r7kamura/rubocop-problem-matchers-action@v1 # this shows the failures in the PR
+ - run: bundle exec chefstyle -c .rubocop.yml
+
+ spellcheck:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: carlosperate/download-file-action@v1.0.3
+ id: download-custom-dictionary
+ with:
+ file-url: 'https://raw.githubusercontent.com/chef/chef_dictionary/master/chef.txt'
+ file-name: 'chef_dictionary.txt'
+ - uses: zwaldowski/cspell-action@v1
+ with:
+ config: cspell.json
+ paths: "**/*"
diff --git a/.github/workflows/unit_specs.yml b/.github/workflows/unit_specs.yml
new file mode 100644
index 0000000000..fd0be0991f
--- /dev/null
+++ b/.github/workflows/unit_specs.yml
@@ -0,0 +1,26 @@
+---
+name: unit_specs
+
+"on":
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ unit:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-10.15] # macos-11.0 is not public for now
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
+ ruby: ['3.0']
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby }}
+ bundler-cache: true
+ - run: bundle exec rake spec:unit
+ - run: bundle exec rake component_specs
diff --git a/.gitignore b/.gitignore
index fdf31a58e9..0bb2f93da8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,6 +54,7 @@ spec/data/nodes
spec/data/chef_guid
/config/
+
vendor/
kitchen-tests/vendor
@@ -78,6 +79,14 @@ chef-utils/.bundle
chef-utils/Gemfile.lock
chef-utils/pkg
+# knife
+knife/.bundle
+knife/Gemfile.lock
+knife/pkg
+knife/spec/data/test-dir
+knife/spec/data/nodes
+knife/spec/data/chef_guid
+
# auto generated docs from the docs.rb rake task
docs_site
@@ -93,3 +102,6 @@ mkmf.log
# our custom dictionary pulled from https://github.com/chef/chef_dictionary/
chef_dictionary.txt
+
+# ignore any gems we build locally
+*.gem
diff --git a/.rubocop.yml b/.rubocop.yml
index 0270949244..b6e3e47b30 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -2,6 +2,7 @@ AllCops:
TargetRubyVersion: 2.6
Exclude:
- "spec/data/**/*"
+ - "knife/spec/data/**/*"
- "vendor/**/*"
- "pkg/**/*"
- "chef-config/pkg/**/*"
@@ -12,24 +13,35 @@ Lint/UselessAssignment:
Enabled: false
Lint/DeprecatedClassMethods:
Enabled: false
-Lint/ParenthesesAsGroupedExpression:
- Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Lint/AssignmentInCondition:
Enabled: false
Lint/AmbiguousBlockAssociation:
Enabled: false
-Lint/RedundantSplatExpansion:
- Enabled: false
Lint/ShadowingOuterLocalVariable:
Enabled: false
-Lint/EmptyWhen:
- Enabled: false
Lint/IneffectiveAccessModifier:
Enabled: false
-Lint/ShadowedException:
- Enabled: false
+Lint/InterpolationCheck:
+ Enabled: true
+ Exclude:
+ - 'spec/unit/property_spec.rb'
+ - 'spec/functional/shell_spec.rb'
+Lint/DeprecatedConstants:
+ Enabled: true
+ Exclude:
+ - lib/chef/node/attribute.rb # false alarms
+
+
+# This cop shouldn't alert on the helper / specs itself
+Chef/Ruby/LegacyPowershellOutMethods:
+ Exclude:
+ - 'lib/chef/mixin/powershell_out.rb'
+ - 'spec/functional/mixin/powershell_out_spec.rb'
+ - 'spec/unit/mixin/powershell_out_spec.rb'
+ - 'lib/chef/resource/windows_feature_powershell.rb' # https://github.com/chef/chef/issues/10927
+ - 'lib/chef/provider/package/powershell.rb' # https://github.com/chef/chef/issues/10926
# set additional paths
Chef/Ruby/UnlessDefinedRequire:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 190b6a98dc..f801162a69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,29 +1,428 @@
<!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ -->
-<!-- latest_release 16.7.68 -->
-## [v16.7.68](https://github.com/chef/chef/tree/v16.7.68) (2020-12-03)
+This changelog lists individual merged pull requests to Chef Infra Client and geared towards developers. For a list of significant changes per release see the [Chef Infra Client Release Notes](https://docs.chef.io/release_notes_client/).
+
+<!-- latest_release 17.3.0 -->
+## [v17.3.0](https://github.com/chef/chef/tree/v17.3.0) (2021-06-15)
#### Merged Pull Requests
-- only test dsc_script on 64 bit and document that it will fail on 32 bit clients [#10708](https://github.com/chef/chef/pull/10708) ([mwrock](https://github.com/mwrock))
+- Add windows_defender and windows_defender_exclusion resources [#11702](https://github.com/chef/chef/pull/11702) ([tas50](https://github.com/tas50))
<!-- latest_release -->
-<!-- release_rollup since=16.7.61 -->
+<!-- release_rollup since=17.2.29 -->
### Changes not yet released to stable
#### Merged Pull Requests
-- only test dsc_script on 64 bit and document that it will fail on 32 bit clients [#10708](https://github.com/chef/chef/pull/10708) ([mwrock](https://github.com/mwrock)) <!-- 16.7.68 -->
-- Add new Compliance Phase replicating the functionality previously in the audit cookbook [#10547](https://github.com/chef/chef/pull/10547) ([phiggins](https://github.com/phiggins)) <!-- 16.7.67 -->
-- Allow remote_file consider certificates stored under /etc/chef/trusted_certs [#10704](https://github.com/chef/chef/pull/10704) ([kapilchouhan99](https://github.com/kapilchouhan99)) <!-- 16.7.66 -->
-- Pin our InSpec version and use Chefstyle from rubygems for now [#10702](https://github.com/chef/chef/pull/10702) ([tas50](https://github.com/tas50)) <!-- 16.7.65 -->
-- Update changelog with missing updates from previous prs [#10703](https://github.com/chef/chef/pull/10703) ([nkierpiec](https://github.com/nkierpiec)) <!-- 16.7.64 -->
-- Add back macOS builds to the omnibus pipeline [#10701](https://github.com/chef/chef/pull/10701) ([tas50](https://github.com/tas50)) <!-- 16.7.63 -->
-- Enable git cache to speed up builds [#10689](https://github.com/chef/chef/pull/10689) ([tas50](https://github.com/tas50)) <!-- 16.7.63 -->
-- replace usages of Cmdlet class with powershell_exec [#10683](https://github.com/chef/chef/pull/10683) ([mwrock](https://github.com/mwrock)) <!-- 16.7.63 -->
-- Update the Docker file to use the RHEL 7 package [#10700](https://github.com/chef/chef/pull/10700) ([tas50](https://github.com/tas50)) <!-- 16.7.63 -->
-- Use URI::DEFAULT_PARSER.make_regexp instead of URI.regexp [#10674](https://github.com/chef/chef/pull/10674) ([tas50](https://github.com/tas50)) <!-- 16.7.63 -->
-- Resolve new spacing offenses in RuboCop 1.4 [#10691](https://github.com/chef/chef/pull/10691) ([tas50](https://github.com/tas50)) <!-- 16.7.62 -->
+- Add windows_defender and windows_defender_exclusion resources [#11702](https://github.com/chef/chef/pull/11702) ([tas50](https://github.com/tas50)) <!-- 17.3.0 -->
+- Add the x25519 gem to knife [#11706](https://github.com/chef/chef/pull/11706) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 17.2.38 -->
+- windows_printer: Install drivers, allow skipping port creation, and load state properly [#11665](https://github.com/chef/chef/pull/11665) ([tas50](https://github.com/tas50)) <!-- 17.2.37 -->
+- Handle source_line being nil gracefully [#11691](https://github.com/chef/chef/pull/11691) ([fuegas](https://github.com/fuegas)) <!-- 17.2.36 -->
+- Enable slow resource reporting in our kitchen tests [#11698](https://github.com/chef/chef/pull/11698) ([tas50](https://github.com/tas50)) <!-- 17.2.35 -->
+- Minor improvements for our self documented resources [#11697](https://github.com/chef/chef/pull/11697) ([tas50](https://github.com/tas50)) <!-- 17.2.34 -->
+- Add macos_ruby? helper and wire to the macos? helper [#11693](https://github.com/chef/chef/pull/11693) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 17.2.33 -->
+- Bump inspec-core-bin to 4.37.25 [#11686](https://github.com/chef/chef/pull/11686) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot])) <!-- 17.2.32 -->
+- Add 17.2 release notes [#11669](https://github.com/chef/chef/pull/11669) ([tas50](https://github.com/tas50)) <!-- 17.2.30 -->
+- Add testing of installing knife into the client [#11682](https://github.com/chef/chef/pull/11682) ([tas50](https://github.com/tas50)) <!-- 17.2.31 -->
<!-- release_rollup -->
<!-- latest_stable_release -->
+## [v17.2.29](https://github.com/chef/chef/tree/v17.2.29) (2021-06-09)
+
+#### Merged Pull Requests
+- Fix markdown code blocks [#11567](https://github.com/chef/chef/pull/11567) ([tas50](https://github.com/tas50))
+- More minor docs fixes [#11568](https://github.com/chef/chef/pull/11568) ([tas50](https://github.com/tas50))
+- Make sure we have description fields in actions and fix periods [#11573](https://github.com/chef/chef/pull/11573) ([tas50](https://github.com/tas50))
+- Add additional action descriptions for docs [#11575](https://github.com/chef/chef/pull/11575) ([tas50](https://github.com/tas50))
+- Remove extraneous double mixin for the require. [#11581](https://github.com/chef/chef/pull/11581) ([Dylan-M](https://github.com/Dylan-M))
+- Update validation on the ResetLockoutCount to limit it to LockoutDuration rather than limiting it to 30 minutes [#11583](https://github.com/chef/chef/pull/11583) ([chef-davin](https://github.com/chef-davin))
+- Enables kernel2 habitat package builds/promotions [#11588](https://github.com/chef/chef/pull/11588) ([collinmcneese](https://github.com/collinmcneese))
+- Updated the hostname resource to remove WMI support and use PowerShell… [#11584](https://github.com/chef/chef/pull/11584) ([johnmccrae](https://github.com/johnmccrae))
+- Fix failing Test Kitchen tests in GitHub actions [#11589](https://github.com/chef/chef/pull/11589) ([tas50](https://github.com/tas50))
+- Remove comment no longer relevant [#11595](https://github.com/chef/chef/pull/11595) ([deivid-rodriguez](https://github.com/deivid-rodriguez))
+- Add introduced fields to the hostname resource [#11608](https://github.com/chef/chef/pull/11608) ([tas50](https://github.com/tas50))
+- Adds promotion of kernel2 package to current channel in expeditor config [#11605](https://github.com/chef/chef/pull/11605) ([collinmcneese](https://github.com/collinmcneese))
+- pass homebrew_path, owner props to homebrew_tap if installing cask [#11607](https://github.com/chef/chef/pull/11607) ([mattlqx](https://github.com/mattlqx))
+- Additional dist constants used in chef-cli and InSpec [#11594](https://github.com/chef/chef/pull/11594) ([ramereth](https://github.com/ramereth))
+- Do not set sensitive true for SSL certificate [#11578](https://github.com/chef/chef/pull/11578) ([jasonwbarnett](https://github.com/jasonwbarnett))
+- Properly generate the docs markdown notes sections [#11601](https://github.com/chef/chef/pull/11601) ([tas50](https://github.com/tas50))
+- Make sure we can run dep update on 2+ branches [#11610](https://github.com/chef/chef/pull/11610) ([tas50](https://github.com/tas50))
+- Update InSpec to 4.37.17 and bump omnibus [#11611](https://github.com/chef/chef/pull/11611) ([tas50](https://github.com/tas50))
+- Bump ffi to 1.15.1 [#11612](https://github.com/chef/chef/pull/11612) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- inspec_waiver_file_entry: Autoload yaml and use dist file [#11552](https://github.com/chef/chef/pull/11552) ([tas50](https://github.com/tas50))
+- Bump the knife ffi dep [#11618](https://github.com/chef/chef/pull/11618) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump inspec-core-bin to 4.37.20 [#11632](https://github.com/chef/chef/pull/11632) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump chef/chefstyle to 082bbaf73d76000724f8d8ae3ba7f89c9123ad3f [#11635](https://github.com/chef/chef/pull/11635) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add a slow resources report to chef-client [#11642](https://github.com/chef/chef/pull/11642) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump all deps to the latest [#11643](https://github.com/chef/chef/pull/11643) ([tas50](https://github.com/tas50))
+- Make sure we load ohai in knife configure correctly [#11647](https://github.com/chef/chef/pull/11647) ([tas50](https://github.com/tas50))
+- Support recipes that and in .yaml as well as .yml [#11629](https://github.com/chef/chef/pull/11629) ([marcparadise](https://github.com/marcparadise))
+- Fix Chef::Handler specs and slow report behavior [#11648](https://github.com/chef/chef/pull/11648) ([lamont-granquist](https://github.com/lamont-granquist))
+- Fix incorrect require_relative causing failures in `knife org user add` [#11649](https://github.com/chef/chef/pull/11649) ([marcparadise](https://github.com/marcparadise))
+- Add tests to verify knife command load &amp; execution [#11653](https://github.com/chef/chef/pull/11653) ([marcparadise](https://github.com/marcparadise))
+- Bump inspec-core-bin to 4.37.23 [#11655](https://github.com/chef/chef/pull/11655) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Updated the Windows Pagefile resource to use PowerShell over WMI, added a corresponding test file [#11636](https://github.com/chef/chef/pull/11636) ([johnmccrae](https://github.com/johnmccrae))
+- windows_printer: use powershell_exec to delete the printer instead of slower/double logging powershell_script [#11663](https://github.com/chef/chef/pull/11663) ([tas50](https://github.com/tas50))
+- Cleanup windows_printer_port and allow updating the port [#11662](https://github.com/chef/chef/pull/11662) ([tas50](https://github.com/tas50))
+- Cleanup the zypper_repository resource + support multiple GPG Keys [#11660](https://github.com/chef/chef/pull/11660) ([tas50](https://github.com/tas50))
+- Add ed25519 gem back to the omnibus install [#11664](https://github.com/chef/chef/pull/11664) ([tas50](https://github.com/tas50))
+- windows_firewall_rule: allow for multiple remote addresses [#11657](https://github.com/chef/chef/pull/11657) ([johnmccrae](https://github.com/johnmccrae))
+- Improve the automatically generated docs [#11670](https://github.com/chef/chef/pull/11670) ([tas50](https://github.com/tas50))
+- Update omnibus-software to the latest [#11673](https://github.com/chef/chef/pull/11673) ([tas50](https://github.com/tas50))
+- Support attribute block/allow list in data collector [#11674](https://github.com/chef/chef/pull/11674) ([marcparadise](https://github.com/marcparadise))
+<!-- latest_stable_release -->
+
+## [v17.1.35](https://github.com/chef/chef/tree/v17.1.35) (2021-05-11)
+
+#### Merged Pull Requests
+- Fix building knife and make it require Ruby 2.7 [#11463](https://github.com/chef/chef/pull/11463) ([tas50](https://github.com/tas50))
+- Fix location and resource_name of unified_mode deprecation message [#11469](https://github.com/chef/chef/pull/11469) ([lamont-granquist](https://github.com/lamont-granquist))
+- Make deprecation warning message more explicit [#11470](https://github.com/chef/chef/pull/11470) ([lamont-granquist](https://github.com/lamont-granquist))
+- Loosen the chef deps in knife and remove the Gemfile.lock [#11471](https://github.com/chef/chef/pull/11471) ([tas50](https://github.com/tas50))
+- use int64 on x64 architecture for LPARAM and LONG_PTR types [#11472](https://github.com/chef/chef/pull/11472) ([mwrock](https://github.com/mwrock))
+- Remove Chef Sugar from CI testing matrix [#11475](https://github.com/chef/chef/pull/11475) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump inspec-core-bin to 4.36.4 [#11474](https://github.com/chef/chef/pull/11474) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update URLs in our Compliance Phase errors [#11480](https://github.com/chef/chef/pull/11480) ([tas50](https://github.com/tas50))
+- Yum func spec modernization [#11483](https://github.com/chef/chef/pull/11483) ([lamont-granquist](https://github.com/lamont-granquist))
+- Make CLI one of the default reporters for Compliance Phase [#11481](https://github.com/chef/chef/pull/11481) ([tas50](https://github.com/tas50))
+- move knife spec tests into the knife gem dir [#11275](https://github.com/chef/chef/pull/11275) ([marcparadise](https://github.com/marcparadise))
+- Bump omnibus from `79c80e0` to `1d97cd9` in /omnibus [#11478](https://github.com/chef/chef/pull/11478) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Fix yum provider blocking and flushing [#11486](https://github.com/chef/chef/pull/11486) ([lamont-granquist](https://github.com/lamont-granquist))
+- Update omnibus to unbreak builds [#11489](https://github.com/chef/chef/pull/11489) ([tas50](https://github.com/tas50))
+- Remove RHEL5 support from yum_package provider [#11492](https://github.com/chef/chef/pull/11492) ([lamont-granquist](https://github.com/lamont-granquist))
+- Remove the ruby-prof gem from omnibus packages [#11491](https://github.com/chef/chef/pull/11491) ([tas50](https://github.com/tas50))
+- Remove profile-ruby tests [#11493](https://github.com/chef/chef/pull/11493) ([tas50](https://github.com/tas50))
+- Bump chef/chefstyle to 7f3a6e65b45e62446291840315f32afa95b80959 [#11498](https://github.com/chef/chef/pull/11498) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Enable deprecation warnings in our specs [#11499](https://github.com/chef/chef/pull/11499) ([tas50](https://github.com/tas50))
+- Resolve File.exists? in directory provider [#11502](https://github.com/chef/chef/pull/11502) ([tas50](https://github.com/tas50))
+- Bump chef/ohai to 947a97d47daa1dce6aa7b91f2057b15451805b25 [#11503](https://github.com/chef/chef/pull/11503) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Fix systemd service state detection [#11497](https://github.com/chef/chef/pull/11497) ([ramereth](https://github.com/ramereth))
+- Remove knife deps from the chef gemspec [#11488](https://github.com/chef/chef/pull/11488) ([tas50](https://github.com/tas50))
+- Add back windows deps to omnibus [#11509](https://github.com/chef/chef/pull/11509) ([tas50](https://github.com/tas50))
+- Bump chef/ohai to 17.1 [#11511](https://github.com/chef/chef/pull/11511) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Fix bug causing systemd units to always re-enable and re-start [#11512](https://github.com/chef/chef/pull/11512) ([gene1wood](https://github.com/gene1wood))
+- Fix edit_resource usage in unified_mode [#11519](https://github.com/chef/chef/pull/11519) ([lamont-granquist](https://github.com/lamont-granquist))
+- Suppress unified mode deprecation warning for deprecated resources [#11520](https://github.com/chef/chef/pull/11520) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump inspec-core-bin to 4.37.0 [#11525](https://github.com/chef/chef/pull/11525) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- windows_security_policy: Add AuditPolicyChange and LockoutDuration capabilities [#11533](https://github.com/chef/chef/pull/11533) ([johnmccrae](https://github.com/johnmccrae))
+- DNF provider update [#11535](https://github.com/chef/chef/pull/11535) ([lamont-granquist](https://github.com/lamont-granquist))
+- Update Hostname resource to account for newer versions of PowerShell being standard [#11537](https://github.com/chef/chef/pull/11537) ([johnmccrae](https://github.com/johnmccrae))
+- Use buffered i/o for yum and disable repos in testing [#11538](https://github.com/chef/chef/pull/11538) ([lamont-granquist](https://github.com/lamont-granquist))
+- Add introduced to the new hostname properties [#11539](https://github.com/chef/chef/pull/11539) ([tas50](https://github.com/tas50))
+- Fix a typo setting up cookbook_name value in templates [#11540](https://github.com/chef/chef/pull/11540) ([tas50](https://github.com/tas50))
+- Run Linux dokken tests in GitHub Actions [#11515](https://github.com/chef/chef/pull/11515) ([tas50](https://github.com/tas50))
+- Test more platforms in Dokken / GH Actions [#11546](https://github.com/chef/chef/pull/11546) ([tas50](https://github.com/tas50))
+- Fix 2 typos in code and slim our cspell exceptions down [#11541](https://github.com/chef/chef/pull/11541) ([tas50](https://github.com/tas50))
+- Treat 32bit-on-64bit the same as 32bit [#11547](https://github.com/chef/chef/pull/11547) ([lamont-granquist](https://github.com/lamont-granquist))
+- Build RHEL 8 packages on RHEL 8 boxes [#11544](https://github.com/chef/chef/pull/11544) ([tas50](https://github.com/tas50))
+- Silence `bundle install` warning when installing gems for cookbooks [#11551](https://github.com/chef/chef/pull/11551) ([nvwls](https://github.com/nvwls))
+- Remove comment no longer relevant [#11550](https://github.com/chef/chef/pull/11550) ([deivid-rodriguez](https://github.com/deivid-rodriguez))
+- Creating the `inspec_waiver_file_entry` resource for managing and formatting a waiver file [#10098](https://github.com/chef/chef/pull/10098) ([chef-davin](https://github.com/chef-davin))
+- Bump chef/chefstyle to 9e9864d3839e1a78703e6662b0dbe7a04af05fd1 [#11527](https://github.com/chef/chef/pull/11527) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- mount: Fix idempotentency for loopback mounts [#11376](https://github.com/chef/chef/pull/11376) ([msys-sgarg](https://github.com/msys-sgarg))
+- Revert &quot;Kept a check in pattern matching for a mount of type loop&quot; [#11557](https://github.com/chef/chef/pull/11557) ([tas50](https://github.com/tas50))
+- Strip the __env_path variable in the which helper [#11561](https://github.com/chef/chef/pull/11561) ([tas50](https://github.com/tas50))
+- Revert windows hostname changes for now [#11564](https://github.com/chef/chef/pull/11564) ([tas50](https://github.com/tas50))
+- Fix logrotate in tests [#11565](https://github.com/chef/chef/pull/11565) ([tas50](https://github.com/tas50))
+
+## [v17.0.242](https://github.com/chef/chef/tree/v17.0.242) (2021-04-28)
+
+#### Merged Pull Requests
+- knife bootstrap: Windows Trusted cert path slashes fix [#10740](https://github.com/chef/chef/pull/10740) ([axelrtgs](https://github.com/axelrtgs))
+- Improve our automated resource documentation generation [#10739](https://github.com/chef/chef/pull/10739) ([tas50](https://github.com/tas50))
+- Add audit cookbook&#39;s chef_node_attribute_enabled to Compliance Phase. [#10735](https://github.com/chef/chef/pull/10735) ([phiggins](https://github.com/phiggins))
+- locale: Update the locale-gen timeout to 1800s [#10743](https://github.com/chef/chef/pull/10743) ([tas50](https://github.com/tas50))
+- Update train to 3.4.4 [#10745](https://github.com/chef/chef/pull/10745) ([tas50](https://github.com/tas50))
+- Remove old test script. [#10746](https://github.com/chef/chef/pull/10746) ([phiggins](https://github.com/phiggins))
+- Cleanup bootstrap&#39;s trusted_certs_dir tests. [#10754](https://github.com/chef/chef/pull/10754) ([phiggins](https://github.com/phiggins))
+- Fix failures in ssl handler [#10751](https://github.com/chef/chef/pull/10751) ([phiggins](https://github.com/phiggins))
+- Update links to Compliance Phase documentation in log messages. [#10755](https://github.com/chef/chef/pull/10755) ([phiggins](https://github.com/phiggins))
+- Bump Chef Infra to 17 [#10760](https://github.com/chef/chef/pull/10760) ([tas50](https://github.com/tas50))
+- Chef 17: Assume Rubygems 1.8 in the rubygems provider / specs [#10379](https://github.com/chef/chef/pull/10379) ([tas50](https://github.com/tas50))
+- Remove EOL RHEL 6 32bit builds [#10721](https://github.com/chef/chef/pull/10721) ([tas50](https://github.com/tas50))
+- Fixed cron_d resource ignoring sensitive property in Chef 17 [#10767](https://github.com/chef/chef/pull/10767) ([axl89](https://github.com/axl89))
+- Pull in the new FFI we need for M1 Macs [#10772](https://github.com/chef/chef/pull/10772) ([tas50](https://github.com/tas50))
+- Remove support for Ubuntu 16.04 [#10765](https://github.com/chef/chef/pull/10765) ([tas50](https://github.com/tas50))
+- Misc minor perf bumps [#10773](https://github.com/chef/chef/pull/10773) ([tas50](https://github.com/tas50))
+- Resolve Lint/ParenthesesAsGroupedExpression warnings [#10779](https://github.com/chef/chef/pull/10779) ([tas50](https://github.com/tas50))
+- Cleanup some more disabled style cops [#10780](https://github.com/chef/chef/pull/10780) ([tas50](https://github.com/tas50))
+- Refactor the code for windows_security_policy resource [#10699](https://github.com/chef/chef/pull/10699) ([chef-davin](https://github.com/chef-davin))
+- Fix knife status json output for EC2 instance with no public IP. [#10781](https://github.com/chef/chef/pull/10781) ([phiggins](https://github.com/phiggins))
+- Cleanup knife status [#10782](https://github.com/chef/chef/pull/10782) ([phiggins](https://github.com/phiggins))
+- Stub http requests in rubygems tests. [#10761](https://github.com/chef/chef/pull/10761) ([phiggins](https://github.com/phiggins))
+- Fix dnf_package version and arch property support and idempotency [#9847](https://github.com/chef/chef/pull/9847) ([lamont-granquist](https://github.com/lamont-granquist))
+- Stop updating bundler in CI [#10786](https://github.com/chef/chef/pull/10786) ([tas50](https://github.com/tas50))
+- Removed unused rubygems from the omnibus overrides file [#10788](https://github.com/chef/chef/pull/10788) ([tas50](https://github.com/tas50))
+- Don&#39;t install util-linux into the containers in CI [#10787](https://github.com/chef/chef/pull/10787) ([tas50](https://github.com/tas50))
+- Ensure we can still install RHEL 7 GCC on RHEL 6 in testing [#10723](https://github.com/chef/chef/pull/10723) ([tas50](https://github.com/tas50))
+- Remove Test Kitchen tests for Amazon Linux 201X [#10789](https://github.com/chef/chef/pull/10789) ([tas50](https://github.com/tas50))
+- Replace Ubuntu 20.10 testing with 21.04 [#10790](https://github.com/chef/chef/pull/10790) ([tas50](https://github.com/tas50))
+- Add Test Kitchen testing for Debian 11 [#10791](https://github.com/chef/chef/pull/10791) ([tas50](https://github.com/tas50))
+- Fix escaping in doc string. [#10793](https://github.com/chef/chef/pull/10793) ([phiggins](https://github.com/phiggins))
+- Consolidate Windows cert tests to improve CI runtime [#10794](https://github.com/chef/chef/pull/10794) ([phiggins](https://github.com/phiggins))
+- Coerce uid to integer in Windows user resource. [#10803](https://github.com/chef/chef/pull/10803) ([phiggins](https://github.com/phiggins))
+- Remove the runtime dep on bundler [#10801](https://github.com/chef/chef/pull/10801) ([tas50](https://github.com/tas50))
+- Update bcrypt_pbkdf to support Ruby 3 [#10804](https://github.com/chef/chef/pull/10804) ([tas50](https://github.com/tas50))
+- Remove the evals in the omnibus gemfile for Dependabot [#10805](https://github.com/chef/chef/pull/10805) ([tas50](https://github.com/tas50))
+- Bump test-kitchen from 2.8.0 to 2.9.0 in /omnibus [#10807](https://github.com/chef/chef/pull/10807) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump omnibus-software from `457df26` to `869ef4e` in /omnibus [#10808](https://github.com/chef/chef/pull/10808) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump omnibus from `d13ae16` to `44f1303` in /omnibus [#10806](https://github.com/chef/chef/pull/10806) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Add gemspec metadata [#10809](https://github.com/chef/chef/pull/10809) ([tas50](https://github.com/tas50))
+- Pin ffi for now to prevent i386 windows failures [#10810](https://github.com/chef/chef/pull/10810) ([tas50](https://github.com/tas50))
+- Fix homebrew_cask for the new syntax [#10822](https://github.com/chef/chef/pull/10822) ([tas50](https://github.com/tas50))
+- Update Nokogiri to 1.11.0 [#10829](https://github.com/chef/chef/pull/10829) ([tas50](https://github.com/tas50))
+- Add a new `reposdir` property in the `yum_repository` resource [#10830](https://github.com/chef/chef/pull/10830) ([tas50](https://github.com/tas50))
+- Simplify the code in the hostname resource [#10832](https://github.com/chef/chef/pull/10832) ([tas50](https://github.com/tas50))
+- Add 16.9 release notes [#10835](https://github.com/chef/chef/pull/10835) ([tas50](https://github.com/tas50))
+- load_current_resource for systemd service more efficiently [#10776](https://github.com/chef/chef/pull/10776) ([joshuamiller01](https://github.com/joshuamiller01))
+- Add Chef Infra Client 15.15 release notes [#10847](https://github.com/chef/chef/pull/10847) ([tas50](https://github.com/tas50))
+- Update chef-zero to pull in webrick [#10853](https://github.com/chef/chef/pull/10853) ([tas50](https://github.com/tas50))
+- adapt to FreeBSD pkgng sysexit changes [#10813](https://github.com/chef/chef/pull/10813) ([mrtazz](https://github.com/mrtazz))
+- Compliance phase: change the audit cb checker to use the recipes list on the node. [#10864](https://github.com/chef/chef/pull/10864) ([lamont-granquist](https://github.com/lamont-granquist))
+- Remove the kitchen-azurerm dep for kitchent tests [#10865](https://github.com/chef/chef/pull/10865) ([tas50](https://github.com/tas50))
+- Fix misspelling in spec/functional/resource/aixinit_service_spec.rb. [#10868](https://github.com/chef/chef/pull/10868) ([coldiron](https://github.com/coldiron))
+- Update the alternatives and chef_client_launchd descriptions [#10873](https://github.com/chef/chef/pull/10873) ([tas50](https://github.com/tas50))
+- Fix yard warnings in the chef_vault dsl [#10870](https://github.com/chef/chef/pull/10870) ([tas50](https://github.com/tas50))
+- Fix ohai resource spec [#10874](https://github.com/chef/chef/pull/10874) ([lamont-granquist](https://github.com/lamont-granquist))
+- Pin rspec until we can resolve failures with 3.10 [#10879](https://github.com/chef/chef/pull/10879) ([tas50](https://github.com/tas50))
+- Updated uuidtools version [#10881](https://github.com/chef/chef/pull/10881) ([kapilchouhan99](https://github.com/kapilchouhan99))
+- Compliance Phase: even better audit cookbook detection [#10882](https://github.com/chef/chef/pull/10882) ([lamont-granquist](https://github.com/lamont-granquist))
+- Add support for client.d files in chef-shell [#10880](https://github.com/chef/chef/pull/10880) ([jaymzh](https://github.com/jaymzh))
+- Manually install necessary Ruby for verify pipeline [#10869](https://github.com/chef/chef/pull/10869) ([christopher-snapp](https://github.com/christopher-snapp))
+- Remove Ruby 2.6 tests [#10884](https://github.com/chef/chef/pull/10884) ([tas50](https://github.com/tas50))
+- Test chef-utils and chef-config on Ruby 2.6 still [#10885](https://github.com/chef/chef/pull/10885) ([tas50](https://github.com/tas50))
+- Resolve chefstyle failure [#10886](https://github.com/chef/chef/pull/10886) ([tas50](https://github.com/tas50))
+- Add support for lazy attributes [#10861](https://github.com/chef/chef/pull/10861) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump omnibus-software from `869ef4e` to `023e6bf` in /omnibus [#10889](https://github.com/chef/chef/pull/10889) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump test-kitchen from 2.9.0 to 2.10.0 in /omnibus [#10893](https://github.com/chef/chef/pull/10893) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Add backup functionality to windows_task [#10894](https://github.com/chef/chef/pull/10894) ([kimbernator](https://github.com/kimbernator))
+- Bump omnibus-software from `023e6bf` to `1cff56e` in /omnibus [#10903](https://github.com/chef/chef/pull/10903) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump inspec-core-bin to 4.25.1 [#10907](https://github.com/chef/chef/pull/10907) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add release notes for 16.9.29 [#10910](https://github.com/chef/chef/pull/10910) ([tas50](https://github.com/tas50))
+- Don&#39;t ship dev gems in the shipping artifact [#10913](https://github.com/chef/chef/pull/10913) ([lamont-granquist](https://github.com/lamont-granquist))
+- fix typo in release notes [#10924](https://github.com/chef/chef/pull/10924) ([IanMadd](https://github.com/IanMadd))
+- load_current_resource for systemd_unit more efficiently [#10925](https://github.com/chef/chef/pull/10925) ([joshuamiller01](https://github.com/joshuamiller01))
+- Pull in the latest Ohai [#10929](https://github.com/chef/chef/pull/10929) ([tas50](https://github.com/tas50))
+- Update Ohai to 17.0.10 [#10931](https://github.com/chef/chef/pull/10931) ([tas50](https://github.com/tas50))
+- Replace deprecated File.exists? with File.exist? in more places [#10934](https://github.com/chef/chef/pull/10934) ([tas50](https://github.com/tas50))
+- Fix an interpolation mistake in an error message + turn on the cop [#10935](https://github.com/chef/chef/pull/10935) ([tas50](https://github.com/tas50))
+- Enable Deprecated Constants Cop [#10936](https://github.com/chef/chef/pull/10936) ([tas50](https://github.com/tas50))
+- Bump train-core to 3.4.8 [#10940](https://github.com/chef/chef/pull/10940) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- handles su - USER session to perform bootstrap [#10410](https://github.com/chef/chef/pull/10410) ([vsingh-msys](https://github.com/vsingh-msys))
+- Bump omnibus from `44f1303` to `65c5931` in /omnibus [#10944](https://github.com/chef/chef/pull/10944) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump train-core to 3.4.9 [#10945](https://github.com/chef/chef/pull/10945) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update systemd_unit.rb to make Cookstyle compliant [#10937](https://github.com/chef/chef/pull/10937) ([cpressland](https://github.com/cpressland))
+- Bump inspec-core-bin to 4.26.4 [#10946](https://github.com/chef/chef/pull/10946) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add 16.9.32 release notes [#10949](https://github.com/chef/chef/pull/10949) ([tas50](https://github.com/tas50))
+- Fix DNF version comparison bug [#10951](https://github.com/chef/chef/pull/10951) ([lamont-granquist](https://github.com/lamont-granquist))
+- Update Ohai to 17.0.11 and Chefstyle to 1.6.2 [#10956](https://github.com/chef/chef/pull/10956) ([tas50](https://github.com/tas50))
+- Bump Ohai to 17.0.12 for Alma Linux support [#10957](https://github.com/chef/chef/pull/10957) ([tas50](https://github.com/tas50))
+- fix specs for spec 3.10 [#10959](https://github.com/chef/chef/pull/10959) ([lamont-granquist](https://github.com/lamont-granquist))
+- Drop some compliance log messages down to debug output [#10965](https://github.com/chef/chef/pull/10965) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump omnibus-software from `197c895` to `c523ead` in /omnibus [#10981](https://github.com/chef/chef/pull/10981) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Handle sysv compat mode when checking enabled status for systemd service [#10976](https://github.com/chef/chef/pull/10976) ([joshuamiller01](https://github.com/joshuamiller01))
+- Compliance cli report [#10939](https://github.com/chef/chef/pull/10939) ([aknarts](https://github.com/aknarts))
+- Chef 17: Remove windows service manager capabilities [#10928](https://github.com/chef/chef/pull/10928) ([tas50](https://github.com/tas50))
+- Add support for resource action descriptions [#10952](https://github.com/chef/chef/pull/10952) ([marcparadise](https://github.com/marcparadise))
+- Improve chef-utils helper descriptions [#10984](https://github.com/chef/chef/pull/10984) ([tas50](https://github.com/tas50))
+- windows_certificate: Fix the `user_store` property to actually install certificates to the user store [#10977](https://github.com/chef/chef/pull/10977) ([tas50](https://github.com/tas50))
+- Improve resource automation [#10988](https://github.com/chef/chef/pull/10988) ([tas50](https://github.com/tas50))
+- DNF/YUM package: fix abrt errors [#10991](https://github.com/chef/chef/pull/10991) ([lamont-granquist](https://github.com/lamont-granquist))
+- Improve the auto generation of documentation [#10992](https://github.com/chef/chef/pull/10992) ([tas50](https://github.com/tas50))
+- Update Ohai to 17.0.17 [#10994](https://github.com/chef/chef/pull/10994) ([tas50](https://github.com/tas50))
+- Update Ohai to 17.0.18 for alibabalinux support [#10995](https://github.com/chef/chef/pull/10995) ([tas50](https://github.com/tas50))
+- Fix downgrades in apt_package [#10993](https://github.com/chef/chef/pull/10993) ([jaymzh](https://github.com/jaymzh))
+- Extend the reboot_pending? helper to all debian-ish platforms [#10989](https://github.com/chef/chef/pull/10989) ([tas50](https://github.com/tas50))
+- Bump ffi-libarchive to 1.0.17 [#11000](https://github.com/chef/chef/pull/11000) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump mixlib-archive to 1.1.4 [#11002](https://github.com/chef/chef/pull/11002) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add alibaba? helper and pull in Ohai with alibaba support [#11004](https://github.com/chef/chef/pull/11004) ([tas50](https://github.com/tas50))
+- Add more descriptions for docs generation [#11003](https://github.com/chef/chef/pull/11003) ([tas50](https://github.com/tas50))
+- Bump omnibus-software from `1fa2052` to `a018c22` in /omnibus [#11009](https://github.com/chef/chef/pull/11009) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Use openssl 1.1.1i for OSX builds [#11006](https://github.com/chef/chef/pull/11006) ([marcparadise](https://github.com/marcparadise))
+- Bump omnibus-software from `a018c22` to `ef9714f` in /omnibus [#11023](https://github.com/chef/chef/pull/11023) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump berkshelf from 7.1.0 to 7.2.0 in /omnibus [#11027](https://github.com/chef/chef/pull/11027) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Fix automate compliance fetcher for profiles with at signs [#11032](https://github.com/chef/chef/pull/11032) ([lamont-granquist](https://github.com/lamont-granquist))
+- mount: Fix for network mounts which use the root level as the device [#11031](https://github.com/chef/chef/pull/11031) ([ramereth](https://github.com/ramereth))
+- Stop producing Habitat kernel2 packages [#11037](https://github.com/chef/chef/pull/11037) ([tas50](https://github.com/tas50))
+- Fix typo in powershell_script.rb [#11040](https://github.com/chef/chef/pull/11040) ([floh96](https://github.com/floh96))
+- Don&#39;t make upstart service to service on any debian platform families [#11039](https://github.com/chef/chef/pull/11039) ([tas50](https://github.com/tas50))
+- Bump mixlib-shellout to 3.2.5 [#11041](https://github.com/chef/chef/pull/11041) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Remove an Upstart check for Ubuntu 8.04-9.04 [#11038](https://github.com/chef/chef/pull/11038) ([tas50](https://github.com/tas50))
+- Fix hab promotes + compile with -O3 for performance [#11045](https://github.com/chef/chef/pull/11045) ([tas50](https://github.com/tas50))
+- Bump omnibus-software from `ef9714f` to `dd2a33e` in /omnibus [#11050](https://github.com/chef/chef/pull/11050) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Remove macOS 10.13 from the build matrix [#10680](https://github.com/chef/chef/pull/10680) ([tas50](https://github.com/tas50))
+- Fully remove user resource support for macOS &lt; 10.14 [#10688](https://github.com/chef/chef/pull/10688) ([tas50](https://github.com/tas50))
+- Update to latest omnibus-software dep for openssl signing fix [#11048](https://github.com/chef/chef/pull/11048) ([marcparadise](https://github.com/marcparadise))
+- Bump omnibus from `f939485` to `c882886` in /omnibus [#11057](https://github.com/chef/chef/pull/11057) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump mixlib-archive to 1.1.6 [#11059](https://github.com/chef/chef/pull/11059) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `dd2a33e` to `1dd8635` in /omnibus [#11062](https://github.com/chef/chef/pull/11062) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump mixlib-archive to 1.1.7 [#11066](https://github.com/chef/chef/pull/11066) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus to 8.1.1 for fastmsi fix [#11071](https://github.com/chef/chef/pull/11071) ([tas50](https://github.com/tas50))
+- Update name of macos builder in Buildkite [#11063](https://github.com/chef/chef/pull/11063) ([tas50](https://github.com/tas50))
+- bump openssl-1.0.2y [#11065](https://github.com/chef/chef/pull/11065) ([dheerajd-msys](https://github.com/dheerajd-msys))
+- 16.10.17 notes [#11080](https://github.com/chef/chef/pull/11080) ([tas50](https://github.com/tas50))
+- Update Ohai to 17.0.21 [#11085](https://github.com/chef/chef/pull/11085) ([tas50](https://github.com/tas50))
+- Improve automatic docs generation [#11047](https://github.com/chef/chef/pull/11047) ([tas50](https://github.com/tas50))
+- Integrated knife-opc into Chef [#10187](https://github.com/chef/chef/pull/10187) ([snehaldwivedi](https://github.com/snehaldwivedi))
+- Re-lazy the template variable default [#11089](https://github.com/chef/chef/pull/11089) ([lamont-granquist](https://github.com/lamont-granquist))
+- Further improve docs generation [#11088](https://github.com/chef/chef/pull/11088) ([tas50](https://github.com/tas50))
+- Bump train-core to 3.5.2 [#11091](https://github.com/chef/chef/pull/11091) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update Ohai to 17.0.22 [#11092](https://github.com/chef/chef/pull/11092) ([tas50](https://github.com/tas50))
+- Update resolver cookbook usage in test-kitchen tests [#11086](https://github.com/chef/chef/pull/11086) ([ramereth](https://github.com/ramereth))
+- Bump omnibus-software from `9390767` to `fb0fa04` in /omnibus [#11098](https://github.com/chef/chef/pull/11098) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump inspec-core-bin to 4.26.13 [#11099](https://github.com/chef/chef/pull/11099) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add a compliance_mode node attribute [#11111](https://github.com/chef/chef/pull/11111) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump omnibus-software from `fb0fa04` to `a1e9c90` in /omnibus [#11116](https://github.com/chef/chef/pull/11116) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Extend node[&quot;audit&quot;][&quot;compliance_phase&quot;] to assert phase on or off [#11115](https://github.com/chef/chef/pull/11115) ([lamont-granquist](https://github.com/lamont-granquist))
+- Dup default property values [#11095](https://github.com/chef/chef/pull/11095) ([lamont-granquist](https://github.com/lamont-granquist))
+- Use .tr instead of .gsub where we can [#11120](https://github.com/chef/chef/pull/11120) ([tas50](https://github.com/tas50))
+- Bump all deps to current and fix a spelling mistake [#11122](https://github.com/chef/chef/pull/11122) ([tas50](https://github.com/tas50))
+- Use shell redirection in chef_client_cron when append_log_file is true [#11124](https://github.com/chef/chef/pull/11124) ([ramereth](https://github.com/ramereth))
+- Update our bcrypt_pbkdf dep to allow the final 1.1.0 release [#11131](https://github.com/chef/chef/pull/11131) ([tas50](https://github.com/tas50))
+- Removes install-as-service option which is not supported [#11137](https://github.com/chef/chef/pull/11137) ([marcparadise](https://github.com/marcparadise))
+- Move idempotency logs to debug [#11149](https://github.com/chef/chef/pull/11149) ([jaymzh](https://github.com/jaymzh))
+- Build place for documenting internal/dev/unsupported commands [#11148](https://github.com/chef/chef/pull/11148) ([jaymzh](https://github.com/jaymzh))
+- Resolve Test Kitchen failures on Oracle 8 [#11140](https://github.com/chef/chef/pull/11140) ([tas50](https://github.com/tas50))
+- Update FFI to 1.15.0 [#11151](https://github.com/chef/chef/pull/11151) ([tas50](https://github.com/tas50))
+- Pin win32-certstore to 0.5.x until we resolve failures [#11153](https://github.com/chef/chef/pull/11153) ([tas50](https://github.com/tas50))
+- Add effortless? helper to chef-utils [#11150](https://github.com/chef/chef/pull/11150) ([tas50](https://github.com/tas50))
+- Use full path for launchctl calls [#11154](https://github.com/chef/chef/pull/11154) ([krackajak](https://github.com/krackajak))
+- Bump Ohai and chefstyle tot the latest [#11156](https://github.com/chef/chef/pull/11156) ([tas50](https://github.com/tas50))
+- Remove installing htop in test [#11157](https://github.com/chef/chef/pull/11157) ([tas50](https://github.com/tas50))
+- Bump omnibus-software from `a7ed951` to `daeb384` in /omnibus [#11167](https://github.com/chef/chef/pull/11167) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Make the /etc/chef directory as part of the install [#11158](https://github.com/chef/chef/pull/11158) ([tas50](https://github.com/tas50))
+- only run file verifiers when the contents changed [#11171](https://github.com/chef/chef/pull/11171) ([joshuamiller01](https://github.com/joshuamiller01))
+- Create client.rb in postinst with 640 perms not 644 [#11168](https://github.com/chef/chef/pull/11168) ([tas50](https://github.com/tas50))
+- M1 Mac arm builds [#11138](https://github.com/chef/chef/pull/11138) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump omnibus from `4a3c044` to `dd57896` in /omnibus [#11177](https://github.com/chef/chef/pull/11177) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump mixlib-authentication to 3.0.10 and ohai to 17.0.29 [#11183](https://github.com/chef/chef/pull/11183) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump chef/ohai to 771a034fdc9c9860d8c8d5d289615056ea74e8f6 [#11185](https://github.com/chef/chef/pull/11185) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump train-core to 3.5.4 [#11186](https://github.com/chef/chef/pull/11186) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Fix users_manage usage in kitchen-tests [#11181](https://github.com/chef/chef/pull/11181) ([ramereth](https://github.com/ramereth))
+- Bump omnibus-software from `daeb384` to `f903311` in /omnibus [#11191](https://github.com/chef/chef/pull/11191) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Disable compliance phase by default [#11195](https://github.com/chef/chef/pull/11195) ([tas50](https://github.com/tas50))
+- Fix compliance phase specs [#11198](https://github.com/chef/chef/pull/11198) ([tas50](https://github.com/tas50))
+- Add release notes for Chef Infra Client 16.11 [#11200](https://github.com/chef/chef/pull/11200) ([tas50](https://github.com/tas50))
+- Add login option to execute resource [#11201](https://github.com/chef/chef/pull/11201) ([lamont-granquist](https://github.com/lamont-granquist))
+- Fix Azure to trigger correctly on PRs [#11202](https://github.com/chef/chef/pull/11202) ([tas50](https://github.com/tas50))
+- Ruby 3.0 fixes and post-bundle-install hook [#10922](https://github.com/chef/chef/pull/10922) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump ohai from 16.10.6 to 16.10.7 in /omnibus [#11205](https://github.com/chef/chef/pull/11205) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Fix the ruby-3 omnibus builds/tests [#11209](https://github.com/chef/chef/pull/11209) ([lamont-granquist](https://github.com/lamont-granquist))
+- Drop off a sample client.rb config on *nix [#11173](https://github.com/chef/chef/pull/11173) ([tas50](https://github.com/tas50))
+- Fix ruby-prof loading issues [#11222](https://github.com/chef/chef/pull/11222) ([lamont-granquist](https://github.com/lamont-granquist))
+- Update openssl to 1.1.1j and libarchive to 3.5.1 [#11223](https://github.com/chef/chef/pull/11223) ([tas50](https://github.com/tas50))
+- Bump train-core to 3.5.5 [#11225](https://github.com/chef/chef/pull/11225) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `1769ef2` to `ed85910` in /omnibus [#11228](https://github.com/chef/chef/pull/11228) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump omnibus-software from `ed85910` to `f6aa2ed` in /omnibus [#11230](https://github.com/chef/chef/pull/11230) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Use method_missing for data bag item delegation [#11234](https://github.com/chef/chef/pull/11234) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump test-kitchen from 2.11.1 to 2.11.2 in /omnibus [#11238](https://github.com/chef/chef/pull/11238) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump inspec-core-bin to 4.29.3 [#11246](https://github.com/chef/chef/pull/11246) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Fix windows_user password idempotency [#11233](https://github.com/chef/chef/pull/11233) ([jaymzh](https://github.com/jaymzh))
+- bump powershell shim to 0.3.2 with powershell 7.1.3 deps [#11262](https://github.com/chef/chef/pull/11262) ([mwrock](https://github.com/mwrock))
+- Fix idempotency issues with network mounts [#11261](https://github.com/chef/chef/pull/11261) ([ramereth](https://github.com/ramereth))
+- Bump omnibus-software from `caf6ae0` to `a0e7438` in /omnibus [#11270](https://github.com/chef/chef/pull/11270) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Convert knife to its own gem [#11180](https://github.com/chef/chef/pull/11180) ([marcparadise](https://github.com/marcparadise))
+- Remove more redundant begins detected by rubocop [#11220](https://github.com/chef/chef/pull/11220) ([tas50](https://github.com/tas50))
+- Bump cheffish to 16.0.26 [#11276](https://github.com/chef/chef/pull/11276) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump chef/ohai to 2fc35fbcbcfe7e2f3dad8f425d8307a6f216d011 [#11283](https://github.com/chef/chef/pull/11283) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Skip the cron tests on macOS 11+ [#11291](https://github.com/chef/chef/pull/11291) ([tas50](https://github.com/tas50))
+- Report compliance runner not enabled if the node is not available [#11290](https://github.com/chef/chef/pull/11290) ([marcparadise](https://github.com/marcparadise))
+- Update openSSL on macOS to 1.1.1k [#11295](https://github.com/chef/chef/pull/11295) ([tas50](https://github.com/tas50))
+- Fix the cron skip on macOS logic [#11297](https://github.com/chef/chef/pull/11297) ([tas50](https://github.com/tas50))
+- Added new property fqdn and made sure hosts entry includes the same [#11273](https://github.com/chef/chef/pull/11273) ([msys-sgarg](https://github.com/msys-sgarg))
+- Bump chef/ohai to 426f5852142dd9b0fa36057c838db56bc36ed8f1 [#11304](https://github.com/chef/chef/pull/11304) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `76b31d1` to `f745eed` in /omnibus [#11302](https://github.com/chef/chef/pull/11302) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump omnibus-software from `f745eed` to `3d331d8` in /omnibus [#11310](https://github.com/chef/chef/pull/11310) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump chef/ohai to 4aa4f09fdddbe5ecf212ce16c06cd1d105650298 [#11312](https://github.com/chef/chef/pull/11312) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `3d331d8` to `ef7b496` in /omnibus [#11318](https://github.com/chef/chef/pull/11318) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump train-core to 3.6.0 [#11319](https://github.com/chef/chef/pull/11319) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `ef7b496` to `56f6321` in /omnibus [#11323](https://github.com/chef/chef/pull/11323) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump fauxhai-ng to 9.0.0 and InSpec to 4.31 [#11325](https://github.com/chef/chef/pull/11325) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- fix powershell exec segfaults on DSC_Resource [#11330](https://github.com/chef/chef/pull/11330) ([mwrock](https://github.com/mwrock))
+- Fix failing Fauxhai related specs [#11329](https://github.com/chef/chef/pull/11329) ([tas50](https://github.com/tas50))
+- Add centos_stream_platform? helper [#11296](https://github.com/chef/chef/pull/11296) ([ramereth](https://github.com/ramereth))
+- Ruby-3.0 builds [#11336](https://github.com/chef/chef/pull/11336) ([lamont-granquist](https://github.com/lamont-granquist))
+- Produce FIPS builds on Ubuntu as well [#11339](https://github.com/chef/chef/pull/11339) ([tas50](https://github.com/tas50))
+- Knife user create to work with only username and options --email and … [#11224](https://github.com/chef/chef/pull/11224) ([msys-sgarg](https://github.com/msys-sgarg))
+- Use dist constants in rubygems provider for is_omnibus? method [#11326](https://github.com/chef/chef/pull/11326) ([ramereth](https://github.com/ramereth))
+- Bump inspec to 4.31.1 and ohai to 17.0.34 [#11353](https://github.com/chef/chef/pull/11353) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Allow FIPS 140-2 OpenSSL FOM to build on PPC EL platforms [#11358](https://github.com/chef/chef/pull/11358) ([btm](https://github.com/btm))
+- Use new_resource for load_current_value blocks [#11368](https://github.com/chef/chef/pull/11368) ([jasonwbarnett](https://github.com/jasonwbarnett))
+- Move some testing to GitHub Actions [#11364](https://github.com/chef/chef/pull/11364) ([tas50](https://github.com/tas50))
+- Test running spellcheck in Github Actions [#11373](https://github.com/chef/chef/pull/11373) ([tas50](https://github.com/tas50))
+- Add problem matchers for our Chefstyle run in GH [#11374](https://github.com/chef/chef/pull/11374) ([tas50](https://github.com/tas50))
+- Bump chef/ohai to db08809916c84e74ec914bab3d313ec9dfc10195 [#11379](https://github.com/chef/chef/pull/11379) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump tty-prompt to 0.23.1 [#11389](https://github.com/chef/chef/pull/11389) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump chef/chefstyle to 2c32688dc259423c328ccc2cf6b9f3b1bb3cb0a5 [#11391](https://github.com/chef/chef/pull/11391) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump chef/ohai to 44f23f01752a95b3b93b710f9510806b781f8635 [#11392](https://github.com/chef/chef/pull/11392) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus-software from `0dcaeb1` to `810a6c4` in /omnibus [#11396](https://github.com/chef/chef/pull/11396) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Update tests to handle local omnibus packages from Buildkite artifacts api [#11382](https://github.com/chef/chef/pull/11382) ([nkierpiec](https://github.com/nkierpiec))
+- Bump chef/ohai to 2a0da607d2d80c74adb9798c3bae1d2702ba05fd [#11399](https://github.com/chef/chef/pull/11399) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update the download URL for windows msi [#11400](https://github.com/chef/chef/pull/11400) ([marcparadise](https://github.com/marcparadise))
+- Move most compliance validation to pre-run [#11377](https://github.com/chef/chef/pull/11377) ([marcparadise](https://github.com/marcparadise))
+- Revert &quot;Bump omnibus-software from `0dcaeb1` to `810a6c4` in /omnibus&quot; [#11403](https://github.com/chef/chef/pull/11403) ([nikhil2611](https://github.com/nikhil2611))
+- Bump inspec-core-bin to 4.33.1 [#11413](https://github.com/chef/chef/pull/11413) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Bump omnibus from `dd57896` to `79c80e0` in /omnibus [#11410](https://github.com/chef/chef/pull/11410) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Bump omnibus-software from `0dcaeb1` to `3ac1dbe` in /omnibus [#11408](https://github.com/chef/chef/pull/11408) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
+- Replace the ChefFS parallelizer with parallel_map helper [#11397](https://github.com/chef/chef/pull/11397) ([lamont-granquist](https://github.com/lamont-granquist))
+- Lock cheffish to 17 [#11420](https://github.com/chef/chef/pull/11420) ([lamont-granquist](https://github.com/lamont-granquist))
+- Produce packages for FreeBSD 13 [#11424](https://github.com/chef/chef/pull/11424) ([tas50](https://github.com/tas50))
+- fix uninitialized Win32 constant [#11430](https://github.com/chef/chef/pull/11430) ([mwrock](https://github.com/mwrock))
+- Compliance Phase preflight validation updates [#11404](https://github.com/chef/chef/pull/11404) ([marcparadise](https://github.com/marcparadise))
+- Disable FreeBSD builds for now [#11435](https://github.com/chef/chef/pull/11435) ([tas50](https://github.com/tas50))
+- Add macos unit testing with GitHub actions [#11422](https://github.com/chef/chef/pull/11422) ([tas50](https://github.com/tas50))
+- Bump chef/ohai to cfeba80b1f2c8d0cb520b2c80e459325c5cf41a9 [#11436](https://github.com/chef/chef/pull/11436) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update the habitat plan for Linux to Ruby 3 [#11437](https://github.com/chef/chef/pull/11437) ([tas50](https://github.com/tas50))
+- Add Test Kitchen tests in GitHub Actions for Windows [#11438](https://github.com/chef/chef/pull/11438) ([tas50](https://github.com/tas50))
+- Move macOS Test Kitchen tests to GitHub Actions [#11439](https://github.com/chef/chef/pull/11439) ([tas50](https://github.com/tas50))
+- Make json-file compliance report path visible [#11442](https://github.com/chef/chef/pull/11442) ([marcparadise](https://github.com/marcparadise))
+- windows_certificate: properly add/remove pfx and private keys, change… [#11405](https://github.com/chef/chef/pull/11405) ([johnmccrae](https://github.com/johnmccrae))
+- Add start/end of compliance phase logging [#11443](https://github.com/chef/chef/pull/11443) ([marcparadise](https://github.com/marcparadise))
+- Disable Dnf tests for fedora [#11444](https://github.com/chef/chef/pull/11444) ([marcparadise](https://github.com/marcparadise))
+- Add back the windows deps [#11449](https://github.com/chef/chef/pull/11449) ([tas50](https://github.com/tas50))
+- Cache all our gems during spec runs [#11450](https://github.com/chef/chef/pull/11450) ([tas50](https://github.com/tas50))
+- Improve the auto generated docs [#11448](https://github.com/chef/chef/pull/11448) ([tas50](https://github.com/tas50))
+- updating Gemfile to support environment variables [#11452](https://github.com/chef/chef/pull/11452) ([jayashrig158](https://github.com/jayashrig158))
+- Update omnibus gemfile.lock [#11454](https://github.com/chef/chef/pull/11454) ([tas50](https://github.com/tas50))
+- Bump chef/ohai to e2dd316c2380bd2a06c8bf928357169e089a401e [#11455](https://github.com/chef/chef/pull/11455) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Add warning for resources not running in unified_mode [#11453](https://github.com/chef/chef/pull/11453) ([lamont-granquist](https://github.com/lamont-granquist))
+- Bump ohai to 17.0.42 [#11459](https://github.com/chef/chef/pull/11459) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot]))
+- Update the Effortless package for Windows to Ruby 3 [#11456](https://github.com/chef/chef/pull/11456) ([tas50](https://github.com/tas50))
+- Disable the ohai time test so we can ship 17.0 [#11460](https://github.com/chef/chef/pull/11460) ([tas50](https://github.com/tas50))
+
+## [v16.8.14](https://github.com/chef/chef/tree/v16.8.14) (2020-12-12)
+
+#### Merged Pull Requests
+- Bump libarchive to 3.5.0 [#10730](https://github.com/chef/chef/pull/10730) ([tas50](https://github.com/tas50))
+- Update openSSL to 1.0.2x [#10732](https://github.com/chef/chef/pull/10732) ([tas50](https://github.com/tas50))
+- Update libiconv to 1.16 [#10731](https://github.com/chef/chef/pull/10731) ([tas50](https://github.com/tas50))
+- Raise error and retry with PTY on sudo password prompt [#10728](https://github.com/chef/chef/pull/10728) ([rveznaver](https://github.com/rveznaver))
+- Fix broken code in compliance runner&#39;s send_report. [#10733](https://github.com/chef/chef/pull/10733) ([phiggins](https://github.com/phiggins))
+
+## [v16.8.9](https://github.com/chef/chef/tree/v16.8.9) (2020-12-11)
+
+#### Merged Pull Requests
+- Resolve new spacing offenses in RuboCop 1.4 [#10691](https://github.com/chef/chef/pull/10691) ([tas50](https://github.com/tas50))
+- Use URI::DEFAULT_PARSER.make_regexp instead of URI.regexp [#10674](https://github.com/chef/chef/pull/10674) ([tas50](https://github.com/tas50))
+- Update the Docker file to use the RHEL 7 package [#10700](https://github.com/chef/chef/pull/10700) ([tas50](https://github.com/tas50))
+- replace usages of Cmdlet class with powershell_exec [#10683](https://github.com/chef/chef/pull/10683) ([mwrock](https://github.com/mwrock))
+- Enable git cache to speed up builds [#10689](https://github.com/chef/chef/pull/10689) ([tas50](https://github.com/tas50))
+- Add back macOS builds to the omnibus pipeline [#10701](https://github.com/chef/chef/pull/10701) ([tas50](https://github.com/tas50))
+- Update changelog with missing updates from previous prs [#10703](https://github.com/chef/chef/pull/10703) ([nkierpiec](https://github.com/nkierpiec))
+- Pin our InSpec version and use Chefstyle from rubygems for now [#10702](https://github.com/chef/chef/pull/10702) ([tas50](https://github.com/tas50))
+- Allow remote_file consider certificates stored under /etc/chef/trusted_certs [#10704](https://github.com/chef/chef/pull/10704) ([kapilchouhan99](https://github.com/kapilchouhan99))
+- Add new Compliance Phase replicating the functionality previously in the audit cookbook [#10547](https://github.com/chef/chef/pull/10547) ([phiggins](https://github.com/phiggins))
+- only test dsc_script on 64 bit and document that it will fail on 32 bit clients [#10708](https://github.com/chef/chef/pull/10708) ([mwrock](https://github.com/mwrock))
+- Switch back to chefstyle from github [#10709](https://github.com/chef/chef/pull/10709) ([tas50](https://github.com/tas50))
+- only run dsc_script functional tests on 64 bit ruby [#10710](https://github.com/chef/chef/pull/10710) ([mwrock](https://github.com/mwrock))
+- Update license-acceptance gem to 2.1.13 [#10714](https://github.com/chef/chef/pull/10714) ([tas50](https://github.com/tas50))
+- Update Train to 3.4.1 [#10716](https://github.com/chef/chef/pull/10716) ([tas50](https://github.com/tas50))
+- only run systemd unit tests on linux [#10718](https://github.com/chef/chef/pull/10718) ([mwrock](https://github.com/mwrock))
+- Fix for deprecation warning in knife ssh [#10717](https://github.com/chef/chef/pull/10717) ([kapilchouhan99](https://github.com/kapilchouhan99))
+- windows_certificate: Add exportable option to pfx certificate [#10711](https://github.com/chef/chef/pull/10711) ([dheerajd-msys](https://github.com/dheerajd-msys))
+- Update all deps to the latest [#10720](https://github.com/chef/chef/pull/10720) ([tas50](https://github.com/tas50))
+- Fix failing tests on Solaris / Enable Solaris builds again [#10719](https://github.com/chef/chef/pull/10719) ([mwrock](https://github.com/mwrock))
+- hostname: Avoid erroring out when hostname is not set on mac [#10724](https://github.com/chef/chef/pull/10724) ([lamont-granquist](https://github.com/lamont-granquist))
+- Update to InSpec 4.24 [#10726](https://github.com/chef/chef/pull/10726) ([tas50](https://github.com/tas50))
+
## [v16.7.61](https://github.com/chef/chef/tree/v16.7.61) (2020-11-26)
#### Merged Pull Requests
@@ -127,7 +526,6 @@
- Skip appx packaging on Windows [#10650](https://github.com/chef/chef/pull/10650) ([tas50](https://github.com/tas50))
- Bump omnibus / omnibus-software to the latest [#10690](https://github.com/chef/chef/pull/10690) ([tas50](https://github.com/tas50))
- Resolve NameError running mac_user resource [#10692](https://github.com/chef/chef/pull/10692) ([tas50](https://github.com/tas50))
-<!-- latest_stable_release -->
## [v16.6.14](https://github.com/chef/chef/tree/v16.6.14) (2020-10-14)
diff --git a/CHEF_MVPS.md b/CHEF_MVPS.md
index e623e4a948..98e0351c76 100644
--- a/CHEF_MVPS.md
+++ b/CHEF_MVPS.md
@@ -13,6 +13,8 @@ The Lifetime Community Chef Award is a recognition of years of investment by a m
- [Mandi Walls](https://github.com/lnxchk)
- [2019](https://blog.chef.io/congratulations-to-our-2019-awesome-community-chefs/)
- [Nathen Harvey](https://github.com/NathenHarvey)
+- [2018](https://blog.chef.io/2018-awesome-community-chefs/)
+ - [Joshua Timberman](https://github.com/jtimberman)
## Awesome Community Chefs
diff --git a/Dockerfile b/Dockerfile
index 56a6f61940..a468af5d43 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,9 +18,9 @@ FROM busybox
LABEL maintainer="Chef Software, Inc. <docker@chef.io>"
ARG CHANNEL=stable
-ARG VERSION=16.7.61
+ARG VERSION=17.2.29
-RUN wget "http://packages.chef.io/files/${CHANNEL}/chef/${VERSION}/el/7/chef-${VERSION}-1.el7.x86_64.rpm" -O /tmp/chef-client.rpm && \
+RUN wget "http://packages.chef.io/files/${CHANNEL}/chef/${VERSION}/el/6/chef-${VERSION}-1.el6.x86_64.rpm" -O /tmp/chef-client.rpm && \
rpm2cpio /tmp/chef-client.rpm | cpio -idmv && \
rm -rf /tmp/chef-client.rpm
diff --git a/Gemfile b/Gemfile
index f92dc7be14..4872842a5a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,10 +1,5 @@
source "https://rubygems.org"
-# Note we do not use the gemspec DSL which restricts to the
-# gemspec for the current platform and filters out other platforms
-# during a bundle lock operation. We actually want dependencies from
-# both of our gemspecs. Also note this this mimics gemspec behavior
-# of bundler versions prior to 1.12.0 (https://github.com/bundler/bundler/commit/193a14fe5e0d56294c7b370a0e59f93b2c216eed)
gem "chef", path: "."
gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master"
@@ -20,32 +15,26 @@ else
gem "chef-bin" # rubocop:disable Bundler/DuplicatedGem
end
-gem "cheffish", ">= 14"
-
-gem "chef-telemetry", ">=1.0.8" # 1.0.8 removes the http dep
+gem "cheffish", ">= 17"
group(:omnibus_package) do
gem "appbundler"
gem "rb-readline"
- gem "inspec-core-bin", "~> 4.23" # need to provide the binaries for inspec
+ gem "inspec-core-bin", "~> 4.24" # need to provide the binaries for inspec
gem "chef-vault"
end
group(:omnibus_package, :pry) do
gem "pry"
- gem "pry-byebug"
+ # byebug does not install on freebsd on ruby 3.0
+ # gem "pry-byebug"
gem "pry-stack_explorer"
end
-# Everything except AIX
-group(:ruby_prof) do
- # ruby-prof 1.3.0 does not compile on our centos6 builders/kitchen testers
- gem "ruby-prof", "< 1.3.0"
-end
-
# Everything except AIX and Windows
group(:ruby_shadow) do
- gem "ruby-shadow", platforms: :ruby
+ # if ruby-shadow does a release that supports ruby-3.0 this can be removed
+ gem "ruby-shadow", git: "https://github.com/chef/ruby-shadow", branch: "lcg/ruby-3.0", platforms: :ruby
end
group(:development, :test) do
@@ -57,9 +46,7 @@ end
group(:chefstyle) do
# for testing new chefstyle rules
- # disabled until we resolve the conflict in regexp_parser deps between rubocop and inspec
- # gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "master"
- gem "chefstyle", "= 1.5.2"
+ gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "master"
end
instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"]
diff --git a/Gemfile.lock b/Gemfile.lock
index c8e7bd2d2a..af38784f78 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,40 +1,51 @@
GIT
+ remote: https://github.com/chef/chefstyle.git
+ revision: b257fe452318540c7016066beb845e0706917cb4
+ branch: master
+ specs:
+ chefstyle (2.0.4)
+ rubocop (= 1.15.0)
+
+GIT
remote: https://github.com/chef/ohai.git
- revision: 876839a6324a635f15db4118366c938bdd3d675b
+ revision: 934109b9eb3d46da996eb4780719e0ebe11ccafc
branch: master
specs:
- ohai (16.8.0)
- chef-config (>= 12.8, < 17)
- chef-utils (>= 16.0, < 17)
+ ohai (17.1.0)
+ chef-config (>= 14.12, < 18)
+ chef-utils (>= 16.0, < 18)
ffi (~> 1.9)
ffi-yajl (~> 2.2)
ipaddress
mixlib-cli (>= 1.7.0)
mixlib-config (>= 2.0, < 4.0)
mixlib-log (>= 2.0.1, < 4.0)
- mixlib-shellout (>= 2.0, < 4.0)
+ mixlib-shellout (~> 3.2, >= 3.2.5)
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
+GIT
+ remote: https://github.com/chef/ruby-shadow
+ revision: 3b8ea40b0e943b5de721d956741308ce805a5c3c
+ branch: lcg/ruby-3.0
+ specs:
+ ruby-shadow (2.5.0)
+
PATH
remote: .
specs:
- chef (16.7.68)
+ chef (17.3.0)
addressable
- bcrypt_pbkdf (= 1.1.0.rc1)
- bundler (>= 1.10)
- chef-config (= 16.7.68)
- chef-utils (= 16.7.68)
+ chef-config (= 17.3.0)
+ chef-utils (= 17.3.0)
chef-vault
chef-zero (>= 14.0.11)
diff-lcs (>= 1.2.4, < 1.4.0)
- ed25519 (~> 1.2)
erubis (~> 2.7)
- ffi (>= 1.9.25)
+ ffi (>= 1.5.0)
ffi-libarchive (~> 1.0, >= 1.0.3)
ffi-yajl (~> 2.2)
- highline (>= 1.6.9, < 3)
iniparse (~> 1.4)
inspec-core (~> 4.23)
license-acceptance (>= 1.0.5, < 3)
@@ -44,34 +55,24 @@ PATH
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
- net-ssh (>= 4.2, < 7)
- net-ssh-multi (~> 1.2, >= 1.2.1)
- ohai (~> 16.0)
- pastel
+ ohai (~> 17.0)
plist (~> 3.2)
proxifier (~> 1.0)
syslog-logger (~> 1.6)
train-core (~> 3.2, >= 3.2.28)
train-winrm (>= 0.2.5)
- tty-prompt (~> 0.21)
- tty-screen (~> 0.6)
- tty-table (~> 0.11)
- uuidtools (~> 2.1.5)
- chef (16.7.68-universal-mingw32)
+ uuidtools (>= 2.1.5, < 3.0)
+ chef (17.3.0-universal-mingw32)
addressable
- bcrypt_pbkdf (= 1.1.0.rc1)
- bundler (>= 1.10)
- chef-config (= 16.7.68)
- chef-utils (= 16.7.68)
+ chef-config (= 17.3.0)
+ chef-utils (= 17.3.0)
chef-vault
chef-zero (>= 14.0.11)
diff-lcs (>= 1.2.4, < 1.4.0)
- ed25519 (~> 1.2)
erubis (~> 2.7)
- ffi (>= 1.9.25)
+ ffi (>= 1.5.0)
ffi-libarchive (~> 1.0, >= 1.0.3)
ffi-yajl (~> 2.2)
- highline (>= 1.6.9, < 3)
iniparse (~> 1.4)
inspec-core (~> 4.23)
iso8601 (>= 0.12.1, < 0.14)
@@ -82,21 +83,15 @@ PATH
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
- net-ssh (>= 4.2, < 7)
- net-ssh-multi (~> 1.2, >= 1.2.1)
- ohai (~> 16.0)
- pastel
+ ohai (~> 17.0)
plist (~> 3.2)
proxifier (~> 1.0)
syslog-logger (~> 1.6)
train-core (~> 3.2, >= 3.2.28)
train-winrm (>= 0.2.5)
- tty-prompt (~> 0.21)
- tty-screen (~> 0.6)
- tty-table (~> 0.11)
- uuidtools (~> 2.1.5)
+ uuidtools (>= 2.1.5, < 3.0)
win32-api (~> 1.5.3)
- win32-certstore (~> 0.3)
+ win32-certstore (~> 0.6.2)
win32-event (~> 0.6.1)
win32-eventlog (= 0.6.3)
win32-mmap (~> 0.4.1)
@@ -109,15 +104,15 @@ PATH
PATH
remote: chef-bin
specs:
- chef-bin (16.7.68)
- chef (= 16.7.68)
+ chef-bin (17.3.0)
+ chef (= 17.3.0)
PATH
remote: chef-config
specs:
- chef-config (16.7.68)
+ chef-config (17.3.0)
addressable
- chef-utils (= 16.7.68)
+ chef-utils (= 17.3.0)
fuzzyurl
mixlib-config (>= 2.2.12, < 4.0)
mixlib-shellout (>= 2.0, < 4.0)
@@ -126,7 +121,8 @@ PATH
PATH
remote: chef-utils
specs:
- chef-utils (16.7.68)
+ chef-utils (17.3.0)
+ concurrent-ruby
GEM
remote: https://rubygems.org/
@@ -136,101 +132,100 @@ GEM
appbundler (0.13.2)
mixlib-cli (>= 1.4, < 3.0)
mixlib-shellout (>= 2.0, < 4.0)
- ast (2.4.1)
- bcrypt_pbkdf (1.1.0.rc1)
- bcrypt_pbkdf (1.1.0.rc1-x64-mingw32)
- bcrypt_pbkdf (1.1.0.rc1-x86-mingw32)
- binding_of_caller (0.8.0)
+ ast (2.4.2)
+ binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
builder (3.2.4)
- byebug (11.1.3)
- chef-telemetry (1.0.14)
+ chef-telemetry (1.0.29)
chef-config
concurrent-ruby (~> 1.0)
- ffi-yajl (~> 2.2)
chef-vault (4.1.0)
- chef-zero (15.0.3)
+ chef-zero (15.0.4)
ffi-yajl (~> 2.2)
hashie (>= 2.0, < 5.0)
mixlib-log (>= 2.0, < 4.0)
rack (~> 2.0, >= 2.0.6)
uuidtools (~> 2.1)
- cheffish (16.0.12)
+ webrick
+ cheffish (17.0.0)
+ chef-utils (>= 17.0)
chef-zero (>= 14.0)
net-ssh
- chefstyle (1.5.2)
- rubocop (= 1.3.1)
coderay (1.1.3)
- concurrent-ruby (1.1.7)
- crack (0.4.4)
- debug_inspector (0.0.3)
+ concurrent-ruby (1.1.9)
+ crack (0.4.5)
+ rexml
+ debug_inspector (1.1.0)
diff-lcs (1.3)
- ecma-re-validator (0.2.1)
- regexp_parser (~> 1.2)
- ed25519 (1.2.4)
erubi (1.10.0)
erubis (2.7.0)
- faraday (1.0.1)
+ faraday (1.4.2)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
multipart-post (>= 1.2, < 3)
- fauxhai-ng (8.6.0)
+ ruby2_keywords (>= 0.0.4)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.1.0)
+ faraday_middleware (1.0.0)
+ faraday (~> 1.0)
+ fauxhai-ng (9.0.0)
net-ssh
- ffi (1.13.1)
- ffi (1.13.1-x64-mingw32)
- ffi (1.13.1-x86-mingw32)
- ffi-libarchive (1.0.4)
+ ffi (1.15.1)
+ ffi (1.15.1-x64-mingw32)
+ ffi (1.15.1-x86-mingw32)
+ ffi-libarchive (1.0.17)
ffi (~> 1.0)
ffi-win32-extensions (1.0.4)
ffi
- ffi-yajl (2.3.4)
- libyajl2 (~> 1.2)
+ ffi-yajl (2.4.0)
+ libyajl2 (>= 1.2)
fuzzyurl (0.9.0)
gssapi (1.3.1)
ffi (>= 1.0.1)
gyoku (1.3.1)
builder (>= 2.1.2)
- hana (1.3.6)
hashdiff (1.0.1)
- hashie (3.6.0)
- highline (2.0.3)
+ hashie (4.1.0)
httpclient (2.8.3)
iniparse (1.5.0)
- inspec-core (4.23.15)
+ inspec-core (4.37.25)
addressable (~> 2.4)
- chef-telemetry (~> 1.0)
- faraday (>= 0.9.0, < 1.1)
- hashie (~> 3.4)
- json_schemer (>= 0.2.1, < 0.2.12)
+ chef-telemetry (~> 1.0, >= 1.0.8)
+ faraday (>= 0.9.0, < 1.5)
+ faraday_middleware (~> 1.0)
+ hashie (>= 3.4, < 5.0)
license-acceptance (>= 0.2.13, < 3.0)
method_source (>= 0.8, < 2.0)
mixlib-log (~> 3.0)
multipart-post (~> 2.0)
parallel (~> 1.9)
- parslet (~> 1.5)
+ parslet (>= 1.5, < 2.0)
pry (~> 0.13)
- rspec (~> 3.9)
+ rspec (>= 3.9, < 3.11)
rspec-its (~> 1.2)
- rubyzip (~> 1.2, >= 1.2.2)
+ rubyzip (>= 1.2.2, < 3.0)
semverse (~> 3.0)
sslshake (~> 1.2)
thor (>= 0.20, < 2.0)
- tomlrb (~> 1.2.0)
+ tomlrb (>= 1.2, < 2.1)
train-core (~> 3.0)
tty-prompt (~> 0.17)
tty-table (~> 0.10)
- inspec-core-bin (4.23.15)
- inspec-core (= 4.23.15)
+ inspec-core-bin (4.37.25)
+ inspec-core (= 4.37.25)
ipaddress (0.8.3)
iso8601 (0.13.0)
- json (2.3.1)
- json_schemer (0.2.11)
- ecma-re-validator (~> 0.2)
- hana (~> 1.3)
- regexp_parser (~> 1.5)
- uri_template (~> 0.7)
- libyajl2 (1.2.0)
- license-acceptance (2.1.2)
+ json (2.5.1)
+ libyajl2 (2.1.0)
+ license-acceptance (2.1.13)
pastel (~> 0.7)
- tomlrb (~> 1.2)
+ tomlrb (>= 1.2, < 3.0)
tty-box (~> 0.6)
tty-prompt (~> 0.20)
little-plugger (1.1.4)
@@ -238,18 +233,18 @@ GEM
little-plugger (~> 1.1)
multi_json (~> 1.14)
method_source (1.0.0)
- mixlib-archive (1.0.7)
+ mixlib-archive (1.1.7)
mixlib-log
- mixlib-archive (1.0.7-universal-mingw32)
+ mixlib-archive (1.1.7-universal-mingw32)
mixlib-log
- mixlib-authentication (3.0.7)
+ mixlib-authentication (3.0.10)
mixlib-cli (2.1.8)
mixlib-config (3.0.9)
tomlrb
mixlib-log (3.0.9)
- mixlib-shellout (3.2.2)
+ mixlib-shellout (3.2.5)
chef-utils
- mixlib-shellout (3.2.2-universal-mingw32)
+ mixlib-shellout (3.2.5-universal-mingw32)
chef-utils
ffi-win32-extensions (~> 1.0.3)
win32-process (~> 0.9)
@@ -261,100 +256,91 @@ GEM
net-sftp (3.0.0)
net-ssh (>= 5.0.0, < 7.0.0)
net-ssh (6.1.0)
- net-ssh-gateway (2.0.0)
- net-ssh (>= 4.0.0)
- net-ssh-multi (1.2.1)
- net-ssh (>= 2.6.5)
- net-ssh-gateway (>= 1.2.0)
nori (2.6.0)
parallel (1.20.1)
- parser (2.7.2.0)
+ parser (3.0.1.1)
ast (~> 2.4.1)
parslet (1.8.2)
pastel (0.8.0)
tty-color (~> 0.5)
- plist (3.5.0)
+ plist (3.6.0)
proxifier (1.0.3)
- pry (0.13.1)
+ pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
- pry-byebug (3.9.0)
- byebug (~> 11.0)
- pry (~> 0.13.0)
- pry-stack_explorer (0.5.1)
- binding_of_caller (~> 0.7)
+ pry-stack_explorer (0.6.1)
+ binding_of_caller (~> 1.0)
pry (~> 0.13)
public_suffix (4.0.6)
rack (2.2.3)
rainbow (3.0.0)
- rake (13.0.1)
+ rake (13.0.3)
rb-readline (0.5.5)
- regexp_parser (1.8.2)
- rexml (3.2.4)
+ regexp_parser (2.1.1)
+ rexml (3.2.5)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0)
- rspec-core (3.10.0)
+ rspec-core (3.10.1)
rspec-support (~> 3.10.0)
- rspec-expectations (3.10.0)
+ rspec-expectations (3.10.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-its (1.3.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
- rspec-mocks (3.10.0)
+ rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
- rspec-support (3.10.0)
- rubocop (1.3.1)
+ rspec-support (3.10.2)
+ rubocop (1.15.0)
parallel (~> 1.10)
- parser (>= 2.7.1.5)
+ parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
- regexp_parser (>= 1.8)
+ regexp_parser (>= 1.8, < 3.0)
rexml
- rubocop-ast (>= 1.1.1)
+ rubocop-ast (>= 1.5.0, < 2.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 2.0)
- rubocop-ast (1.3.0)
- parser (>= 2.7.1.5)
- ruby-prof (1.2.0)
- ruby-progressbar (1.10.1)
- ruby-shadow (2.5.0)
- rubyntlm (0.6.2)
- rubyzip (1.3.0)
+ unicode-display_width (>= 1.4.0, < 3.0)
+ rubocop-ast (1.7.0)
+ parser (>= 3.0.1.1)
+ ruby-progressbar (1.11.0)
+ ruby2_keywords (0.0.4)
+ rubyntlm (0.6.3)
+ rubyzip (2.3.0)
semverse (3.0.0)
sslshake (1.3.1)
- strings (0.2.0)
+ strings (0.2.1)
strings-ansi (~> 0.2)
- unicode-display_width (~> 1.5)
+ unicode-display_width (>= 1.5, < 3.0)
unicode_utils (~> 1.4)
strings-ansi (0.2.0)
structured_warnings (0.4.0)
syslog-logger (1.6.8)
- thor (1.0.1)
- tomlrb (1.2.9)
- train-core (3.3.27)
+ thor (1.1.0)
+ tomlrb (1.3.0)
+ train-core (3.7.2)
addressable (~> 2.5)
ffi (!= 1.13.0)
json (>= 1.8, < 3.0)
mixlib-shellout (>= 2.0, < 4.0)
net-scp (>= 1.2, < 4.0)
net-ssh (>= 2.9, < 7.0)
- train-winrm (0.2.11)
- winrm (~> 2.0)
+ train-winrm (0.2.12)
+ winrm (>= 2.3.6, < 3.0)
winrm-elevated (~> 1.2.2)
winrm-fs (~> 1.0)
- tty-box (0.6.0)
+ tty-box (0.7.0)
pastel (~> 0.8)
strings (~> 0.2.0)
tty-cursor (~> 0.7)
tty-color (0.6.0)
tty-cursor (0.7.1)
- tty-prompt (0.22.0)
+ tty-prompt (0.23.1)
pastel (~> 0.8)
tty-reader (~> 0.8)
- tty-reader (0.8.0)
+ tty-reader (0.9.0)
tty-cursor (~> 0.7)
tty-screen (~> 0.8)
wisper (~> 2.0)
@@ -363,16 +349,16 @@ GEM
pastel (~> 0.8)
strings (~> 0.2.0)
tty-screen (~> 0.8)
- unicode-display_width (1.7.0)
+ unicode-display_width (2.0.0)
unicode_utils (1.4.0)
- uri_template (0.7.0)
- uuidtools (2.1.5)
- webmock (3.10.0)
+ uuidtools (2.2.0)
+ webmock (3.13.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
+ webrick (1.7.0)
win32-api (1.5.3-universal-mingw32)
- win32-certstore (0.4.1)
+ win32-certstore (0.6.2)
ffi
mixlib-shellout
win32-event (0.6.3)
@@ -393,7 +379,7 @@ GEM
win32-taskscheduler (2.0.4)
ffi
structured_warnings
- winrm (2.3.5)
+ winrm (2.3.6)
builder (>= 2.1.2)
erubi (~> 1.8)
gssapi (~> 1.2)
@@ -401,15 +387,15 @@ GEM
httpclient (~> 2.2, >= 2.2.0.2)
logging (>= 1.6.1, < 3.0)
nori (~> 2.0)
- rubyntlm (~> 0.6.0, >= 0.6.1)
+ rubyntlm (~> 0.6.0, >= 0.6.3)
winrm-elevated (1.2.3)
erubi (~> 1.8)
winrm (~> 2.0)
winrm-fs (~> 1.0)
- winrm-fs (1.3.3)
+ winrm-fs (1.3.5)
erubi (~> 1.8)
logging (>= 1.6.1, < 3.0)
- rubyzip (~> 1.1)
+ rubyzip (~> 2.0)
winrm (~> 2.0)
wisper (2.0.1)
wmi-lite (1.0.5)
@@ -424,23 +410,20 @@ DEPENDENCIES
chef!
chef-bin!
chef-config!
- chef-telemetry (>= 1.0.8)
chef-utils!
chef-vault
- cheffish (>= 14)
- chefstyle (= 1.5.2)
+ cheffish (>= 17)
+ chefstyle!
fauxhai-ng
- inspec-core-bin (~> 4.23)
+ inspec-core-bin (~> 4.24)
ohai!
pry
- pry-byebug
pry-stack_explorer
rake
rb-readline
rspec
- ruby-prof (< 1.3.0)
- ruby-shadow
+ ruby-shadow!
webmock
BUNDLED WITH
- 2.1.4
+ 2.2.19
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 3151158572..fca2f6b7d1 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,44 +1,870 @@
This file holds "in progress" release notes for the current release under development and is intended for consumption by the Chef Documentation team. Please see <https://docs.chef.io/release_notes/> for the official Chef release notes.
-# What's New in 16.7
+## What's New in 17.2
-## Performance Enhancements
+### Compliance Phase Improvements
+
+#### Chef InSpec 4.37
+
+We've updated Chef InSpec from 4.36.4 to 4.37.8:
+
+##### New Features
+
+- The new `inspec automate` command replaces the `inspec compliance` command, which is now deprecated.
+- Added support for `zfs_pool` and `zfs_dataset` resources on Linux.
+- Improved `port` resource performance: adding more specific search while using `ss` command.
+- Updated the `inspec init plugin` command with the following changes:
+ - The values of flags passed to the `inspec init plugin` command are now wrapped in double quotes instead of single quotes.
+ - Template files are now ERB files.
+ - The `activator` flag replaces the `hook` flag, which is now an alias.
+
+##### Bug Fixes
+
+- Fixed an error when using profile dependencies and require_controls.
+- Fixed the `windows_firewall_rule` resource when it failed to validate more than one rule.
+- The `http` resource response body is now coerced into UTF-8.
+- Modified the `windows_feature` resource to indicate if a feature is enabled rather than just available.
+- `file` resource `more_permissive_than` matcher returns nil instead of throwing an exception when the file does not exist.
+- `inspec detect --no-color` now returns color-free output.
+
+### Slow Resource Report
+
+Chef Infra Client now includes a `--slow-report` flag that shows the 10 slowest running resources in a Chef Infra Client run to help you troubleshoot and optimize your cookbooks. This new flag also takes an argument for the number of resources to list if you'd like to see additional resources included in the output. Our next release of Chef Workstation will include the ability to set this flag in Test Kitchen to allow testing for slow resources in the development process.
+
+#### Example Output
+
+```text
+Starting Chef Infra Client, version 17.2.12
+Patents: https://www.chef.io/patents
+resolving cookbooks for run list: ["test"]
+Synchronizing Cookbooks:
+ - test (0.0.1)
+Installing Cookbook Gems:
+Compiling Cookbooks...
+Converging 1 resources
+Recipe: test::default
+ * file[/tmp/foo.xzy] action create (up to date)
+
+Running handlers:
+
+Top 1 slowest resource:
+
+resource elapsed_time cookbook recipe source
+------------------ ------------ -------- ------- ----------------------------------------
+file[/tmp/foo.xzy] 0.015114 test default test/recipes/default.rb:2:in `from_file'
+
+ - Chef::Handler::SlowReport
+Running handlers complete
+Chef Infra Client finished, 0/1 resources updated in 03 seconds
+```
+
+### Improved YAML Recipe Support
+
+Chef Infra Client now supports both `.yaml` and `.yml` file extensions for recipes. If a `.yml` and `.yaml` recipe of the same name is present, Chef Infra Client will now fail as there is no way to determine which recipe should be loaded in this case.
+
+### Improved Reporting to Automate
+
+Chef Infra Client run reporting to Automate now respects attribute `allowlist` and `denylist` configurations set in the `client.rb`. This change allows users to limit the data sent to their Automate servers to prevent indexing sensitive data or to reduce the necessary storage space on the Automate server.
+
+### Updated Resources
+
+#### homebrew_path
+
+The `homebrew_path` now passes the `homebrew_path` when creating or deleting taps. This change prevents failures when running homebrew in a non-standard location or on a M1 system. Thanks [@mattlqx](https://github.com/mattlqx)!
+
+#### hostname
+
+The `hostname` resource now sets the hostname on Windows systems using native PowerShell calls for increased reliability and allows changing the hostname on domain-attached systems. To change the hostname on a domain-attached system, pass a domain administrator account using the new `domain_user` and `domain_password` properties.
+
+#### openssl_x509_certificate
+
+The `openssl_x509_certificate` no longer marks the creation of the X509 certificate file as sensitive since this makes troubleshooting difficult and this content is not sensitive. Thanks [@jasonwbarnett](https://github.com/jasonwbarnett)!
+
+#### windows_firewall_rule
+
+The `windows_firewall_rule` resource now allows specifying multiple IP addresses in the `remote_address` property.
+
+#### windows_pagefile
+
+The `windows_pagefile` resource features improved performance and support for the latest releases of Windows 10. These improvements also make managing pagefiles more predictable:
+
+- The `path` property now accepts a drive letter in addition to the full path of the pagefile on disk. For example, `C`, `C:`, or `C:\` can now be used to specify a pagefile stored at `C:\pagefile.sys`.
+- Creating a new pagefile no longer disables the system-managed pagefile by default. If you wish to create a pagefile while also disabling the system-managed pagefile, set `system_managed false`.
+
+#### windows_printer
+
+The `windows_printer` resource includes improved logging when adding or removing printers.
+
+#### windows_printer_port
+
+The `windows_printer_port` resource has been refactored with several improvements:
+
+- Better performance when adding and removing ports.
+- Supports updating existing ports with new values.
+- Clearer logging of changes made to ports.
+- Deprecated the `description` property, which does not set a description on the ports.
+
+#### windows_security_policy
+
+The `windows_security_policy` resource now limits the value of `ResetLockoutCount` to any value less than `LockoutDuration` rather than limiting it to 30 minutes.
+
+#### zypper_repository
+
+The `zypper_repository` resource now accepts an array of GPG key locations in the `gpgkey` property. Thanks for reporting this [@bkabrda](https://github.com/bkabrda).
+
+## What's New in 17.1
+
+### Compliance Phase Improvements
+
+#### cli reporter by default
+
+The compliance phase will now default to using both the `json-file` and the new `cli` reporter by default. This gives you a visual indication of the success of the Compliance Phase and is perfect for running both on the CLI and in Test Kitchen.
+
+#### inspec_waiver_file_entry resource
+
+Chef Infra Client now ships with a `inspec_waiver_file_entry` resource for managing Chef InSpec waivers. With this resource you can add and remove waiver entries to a single waiver file located at `c:\chef\inspec_waiver_file.yml` on Windows or `/etc/chef/inspec_waivers.yml` on all other systems.
+
+See the [inspec_waiver_file_entry documentation](https://docs.chef.io/resources/inspec_waiver_file_entry) for more information and usage examples.
+
+#### Chef InSpec 4.36
+
+We've updated Chef InSpec from 4.33.1 to 4.36.4:
+
+- Added the selinux resource which includes support for modules and booleans.
+- Added the pattern input option for DSL and metadata inputs.
+- Added the `members_array` property for group & groups resources.
+- Train now reads the username and port from the `.ssh/config` file and will use these values if present.
+- Switch to GNU timeout-based implementation of SSH timeouts.
+- Fixed the group resource when a member does not exist.
+
+### Unified Mode Improvements
+
+We've extended support for Unified Mode to the `edit_resource` helper and also improved the Unified Mode related deprecation warnings to provide more useful information and not warn when resources are deprecated or set to only run on older Chef Infra Client releases.
+
+### Resource Improvements
+
+#### service on systemd Hosts
+
+The `service` resource on systemd hosts will now properly load the state of the service. Thanks for this fix [@ramereth](https://github.com/ramereth)!
+
+#### systemd_unit
+
+We updated the `systemd_unit` resource to resolve a regression in Chef Infra Client 17.0 that would re-enable and restart unit files on each Chef Infra Client run. Thanks for this fix [@gene1wood](https://github.com/gene1wood)!
+
+#### template
+
+We updated the `template` resource to allow passing the `cookbook_name` variable to template files.
+
+#### Windows Resource
+
+We fixed a failure that could occur in multiple Windows resources due to larger 64-bit values that logged the error: `RangeError: bignum too big to convert into 'long'`.
+
+#### windows_security_policy
+
+The `windows_security_policy` resource now supports setting `AuditPolicyChange` and `LockoutDuration`.
+
+#### yum_package / dnf_package
+
+We've made multiple improvements to how we interact with the systems RPM database in the `yum_package` and `dnf_package` resources. These changes improve reliability interacting with the RPM database and includes significant performance improvements, especially when no installation or upgrade action is taken by Chef Infra Client.
+
+### Platform Detection
+
+[Rocky Linux](https://rockylinux.org/), a RHEL clone, is now detected as a member of the `rhel` platform family.
+
+### Packaging
+
+#### Improved Dependencies
+
+Chef Infra Client 17.1 is once again smaller than previous releases thanks to reduced dependencies in the packages.
+
+#### RHEL 8 Packages
+
+We improved our RHEL 8 packages with additional RHEL 8 optimizations and EL8 in the filename.
+
+## What's New in 17.0
+
+Chef Infra Client 17.0 is our yearly release for 2021. These yearly releases include new functionality, an update to the underlying Ruby release, as well as potentially breaking changes. These notes outline what's new and what you should be aware of as part of your upgrade process.
+
+### Compliance Phase
+
+Chef Infra Client's new Compliance Phase allows users to automatically execute compliance audits and view the results in Chef Automate as part of any Chef Infra Client Run. This new phase of the Chef Infra Client run replaces the legacy [audit cookbook](https://supermarket.chef.io/cookbooks/audit) and works using the existing audit cookbook attributes. With this new phase, you'll always have the latest compliance capabilities out of the box without the need to manage cookbook dependencies or juggle versions during Chef Infra Client updates.
+
+The Compliance Phase also features a new compliance reporter: `cli`. This reporter mimics the InSpec command line output giving you a visual indication of your system's compliance status. Thanks for this new reporter [@aknarts](https://github.com/aknarts/).
+
+Existing audit cookbook users can migrate to the new Compliance Phase by removing the audit cookbook from their run_list and setting the `node['audit']['compliance_phase']` attribute to `true`.
+
+For more information see our on-demand webinar [Configure Chef Infra & Compliance Using Built-In Functionality](https://pages.chef.io/202102-Webinar-ConfigureChefInfraComplianceUsingBuilt-InFunctionality_01Register.html)
+
+### Ruby 3
+
+Chef Infra Client 17 packages now ship with embedded Ruby 3.0. This new release of Ruby improves performance and offers many new language improvements for those writing advanced custom resources. See the [ruby-lang.org Ruby 3.0 Announcement](https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/) for additional details on what's new and improved in Ruby 3.0.
+
+### Knife Moved to Workstation
+
+For historical packaging reasons the Chef Infra Client packages have always shipped with the `knife` command for managing your Chef Infra nodes. With Chef Workstation there's no benefit to shipping knife in the Chef Infra Client package and there are several downsides. Shipping management tooling within the client is seen as a security risk to many and increases the side of the Chef Infra Client codebase by adding a large number of management dependencies. With Chef Infra Client 17 we've split knife into its own Ruby Gem, which will continue to ship in Chef Workstation, but will no longer come bundled with Chef Infra Client. We hope you'll enjoy the new faster and smaller Chef Infra Client while continuing to use knife in Chef Workstation uninterrupted.
+
+### Breaking Changes
+
+#### AIX Virtualization Improvements
+
+The Ohai :Virtualization plugin on AIX systems will now properly return the `lpar_no` and `wpar_no` values as Integers instead of Strings. This makes the data much easier to work within cookbooks, but may be a breaking change depending on how AIX users consumed these values.
+
+#### 32bit RHEL/CentOS 6 Support
+
+We will not produce Chef Infra Client 17 packages for 32bit RHEL/CentOS 6 systems. RHEL/CentOS 6 reached EOL in November 2020. We are extending support for 64-bit RHEL/CentOS 6 until Chef Infra Client 18 (April 2022) or when an upstream platform or library changes prevent us from building on these systems that are at the end of their lifecycle.
+
+#### Chef Client As A Service on Windows
+
+Based on customer feedback and observations in the field we've removed the ability to run the Chef Infra Client as a service on Windows nodes. We've seen the service manager for the Chef Infra Client consume excessive memory, hang preventing runs, or prevent nodes from updating to new client releases properly. We've always seen significantly better reliability by running Chef Infra Client as a scheduled task on Windows and in July of 2006 we introduced warnings to the [chef-client cookbook](https://supermarket.chef.io/cookbooks/chef-client) when running as a service. The ability to set up the client as a service was later removed from the cookbook entirely in October of 2017.
+
+For customers currently running Chef Infra Client as a service, we advise migrating to scheduled task-based execution. This allows for complex scheduling scenarios not possible with simple services, such as skipping Chef Infra Client execution on systems running on battery power or running the Chef Infra Client immediately after a system boot to ensure configuration.
+
+Chef Infra Client can be configured to run as a scheduled task using the [chef-client cookbook](https://supermarket.chef.io/cookbooks/chef-client) or ideally using the [chef_client_scheduled_task resource](https://docs.chef.io/resources/chef_client_scheduled_task/) built into Chef Infra Client 16 or later. For users already running as a service setting up the scheduled task and then stopping the existing service can be performed within a Chef Infra Client run to migrate systems.
+
+#### Gem Resource Ruby 1.9+
+
+The `gem` resource used to install Ruby Gems into the system's Ruby installation will now assume Ruby 1.9 or later. As Ruby 1.8 and below reached end of life almost 7 years ago, we believe there is little to no impact in this change.
+
+#### Legacy node['filesystem2'] removed on AIX/Solaris/FreeBSD
+
+The legacy `node['filesystem2']` attributes leftover from our multi-year migration of filesystem data on AIX, Solaris, and FreeBSD systems has been removed. This same data is now available at `node['filesystem']`
+
+#### node['filesystem'] Uses Updated Format on Windows
+
+In Chef Infra Client 16 we introduced `node['filesystem2']` on Windows to complete our migration to a unified structure for filesystem data regardless of platform. In Chef Infra Client 17 we are updating `node['filesystem']` on Windows with this same unified format. Both node attributes now have the same data allowing users to more easily migrate `filesystem2` to `filesystem` in their cookbooks. In Chef Infra Client 18, we will remove `node['filesystem2']` completely finishing our multi-year migration of Ohai filesystem data format.
+
+#### Removed Antergos and Pidora Detection
+
+Ohai detection of the end-of-life Antergos and Pidora distributions has been removed. Antergos ended releases and downloads of the distribution in May 2019 and Pidora stopped receiving updates in 2014.
+
+### Infra Language Improvements
+
+#### Lazy Attribute Loading
+
+A common problem when using the "wrapper cookbook" pattern is when the wrapped cookbook declares what are called "derived attributes", which are attributes that refer to other attributes. Because of the order that attribute files are parsed in, this does not work as intended when the base attribute is changed in a wrapper cookbook. By extending the use of the `lazy {}` helper to the declaration of node attributes, it makes it possible for the wrapped cookbook to cleanly allow wrapper cookbooks to override base attributes as intended.
+
+Use the lazy helper:
+
+```ruby
+default['myapp']['dir'] = '/opt/myapp'
+default['myapp']['bindir'] = lazy { "#{node['myapp']['dir']}/bin" }
+```
+
+Instead of:
+
+```ruby
+default['myapp']['dir'] = '/opt/myapp'
+default['myapp']['bindir'] = "#{node['myapp']['dir']}/bin"
+```
+
+With the lazy helper the wrapper cookbook can then override the base attribute and the derived attribute will change:
+
+```ruby
+default['myapp']['dir'] = "/opt/my_better_app" # this also changes the bindir attribute correctly
+```
+
+The use of this helper is not limited to declarations in attribute files and can be used whenever attributes are being assigned. For a complete description of the capabilities of lazy attribute evaluation see https://github.com/chef/chef/pull/10861
+
+#### Custom Resource Property Defaults
+
+Chef Infra Client's handling of default property values in Custom Resources has been improved to avoid potential Ruby errors. These values are now duplicated internally allowing them to be modified by the user in their recipes without potentially receiving fatal frozen value modification errors.
+
+#### effortless? helper
+
+A new `effortless?` helper identifies if a system is running Chef Infra Client using the Effortless Pattern.
+
+#### reboot_pending? Improvements
+
+The `reboot_pending?` helper now works on all Debian based platforms instead of just Ubuntu.
+
+### Resource Improvements
+
+#### Logging Improvements
+
+A large number of resources have seen improvements to the logging available in the `debug` log level providing better information for troubleshooting Chef Infra Client execution. Thanks for this improvement [@jaymzh](https://github.com/jaymzh)!
+
+#### apt_package
+
+The `apt_package` resource now properly handles downgrading package versions. Please note that full versions must be provided in the `version` property and invalid version strings will now raise an error. Thanks for this improvement [@jaymzh](https://github.com/jaymzh)!
+
+#### chef_client_launchd / macosx_service
+
+The `chef_client_launchd` and `macosx_service` resources have been updated to use the full path to the `launchctl` command. This avoids failures running these resources with incorrect PATH environment variables. Thanks for this improvement [@krackajak](https://github.com/krackajak)!
+
+#### execute
+
+The `execute` resource includes a new `login` property allowing you to run commands with a login shell. This helps ensure you have all potential environment variables defined in the user's shell.
+
+#### hostname
+
+The `hostname` resource now includes a new `fqdn` property to allow you to set a custom fqdn in the hostname file in addition to the system's hostname. Thanks for suggesting this improvement [@evandam](https://github.com/evandam)!
+
+#### systemd_unit
+
+The `systemd_unit` resource has been improved to only shell out once to determine the state of the systemd unit. This optimization should result in significant performance improvements when using large numbers of `systemd_unit` resources. Thanks [@joshuamiller01](https://github.com/joshuamiller01)!
+
+#### windows_certificate
+
+The `windows_certificate` resource has undergone a large overhaul, with improved support for importing and exporting certificate objects, the ability to create certificate objects from a URL, and a new `output_path` property for use with exporting.
+
+#### windows_task
+
+The `windows_task` resource now has a new `backup` property that allows you to control the number of XML backups that will be kept of your Windows Scheduled Task definition. This default for this setting is `5` and can be disabled by setting the property to `false`. Thanks [@ kimbernator](https://github.com/kimbernator)!
+
+### Ohai
+
+#### Podman Detection
+
+Ohai now includes detection for hosts running the Podman containerization engine or Chef Infra Client running in containers under Podman.
+
+For hosts the following attributes will be set:
+
+```json
+{
+ "systems": {
+ "podman": "host",
+ },
+ "system": "podman",
+ "role": "host"
+}
+```
+
+For Chef Infra Client within containers the following attributes will be set:
+
+```json
+{
+ "systems": {
+ "podman": "guest",
+ },
+ "system": "podman",
+ "role": "guest"
+}
+```
+
+Thanks for this addition [@ramereth](https://github.com/ramereth)!
+
+#### Habitat Support
+
+Ohai includes a new `:Habitat` plugin that gathers information about the Habitat installation, including installed Habitat version, installed packages, and running services.
+
+Sample Habitat attribute output:
+
+```json
+{
+ "version": "1.6.288/20210402191717",
+ "packages": ["core/busybox-static/1.31.0/20200306011713",
+ "core/bzip2/1.0.8/20200305225842",
+ "core/cacerts/2020.01.01/20200306005234",
+ "core/gcc-libs/9.1.0/20200305225533",
+ "core/glibc/2.29/20200305172459",
+ "core/hab-launcher/15358/20210402194815",
+ "core/hab-sup/1.6.288/20210402194826",
+ "core/libedit/3.1.20150325/20200319193649",
+ "core/libsodium/1.0.18/20200319192446",
+ "core/linux-headers/4.19.62/20200305172241",
+ "core/ncurses/6.1/20200305230210",
+ "core/nginx/1.18.0/20200506101012",
+ "core/openssl-fips/2.0.16/20200306005307",
+ "core/openssl/1.0.2t/20200306005450",
+ "core/pcre/8.42/20200305232429",
+ "core/zeromq/4.3.1/20200319192759",
+ "core/zlib/1.2.11/20200305174519"
+ ],
+ "services": [{
+ "identity": "core/nginx/1.18.0/20200506101012",
+ "topology": "standalone",
+ "state_desired": "up",
+ "state_actual": "up"
+ }]
+}
+ ```
+
+#### Alibaba Detection
+
+Ohai now includes detection of nodes running on the Alibaba cloud and supports gathering Alibaba instance metadata.
+
+Sample `node['alibaba']` values:
+
+```json
+{
+ "meta_data": {
+ "dns_conf_": "nameservers",
+ "eipv4": "47.89.242.123",
+ "hibernation_": "configured",
+ "hostname": "1234",
+ "image_id": "aliyun_2_1903_x64_20G_alibase_20210120.vhd",
+ "instance_id": "i-12345",
+ "instance_": {
+ "instance_type": "ecs.t6-c2m1.large",
+ "last_host_landing_time": "2021-02-07 19:10:04",
+ "max_netbw_egress": 81920,
+ "max_netbw_ingress": 81920,
+ "virtualization_solution": "ECS Virt",
+ "virtualization_solution_version": 2.0
+ },
+ "mac": "00:16:3e:00:d9:01",
+ "network_type": "vpc",
+ "network_": "interfaces/",
+ "ntp_conf_": "ntp-servers",
+ "owner_account_id": 1234,
+ "private_ipv4": "172.25.58.242",
+ "region_id": "us-west-1",
+ "serial_number": "ac344378-4d5d-4b9e-851b-1234",
+ "source_address": "http://us1.mirrors.cloud.aliyuncs.com",
+ "sub_private_ipv4_list": "172.25.58.243",
+ "vpc_cidr_block": "172.16.0.0/12",
+ "vpc_id": "vpc-1234",
+ "vswitch_cidr_block": "172.25.48.0/20",
+ "vswitch_id": "vsw-rj9eiw6yqh6zll23h0tlt",
+ "zone_id": "us-west-1b"
+ },
+ "user_data": null,
+ "dynamic": "instance-identity",
+ "global_config": null,
+ "maintenance": "active-system-events"
+}
+```
+
+Sample `node['cloud'] values:
+
+```json
+{
+ "public_ipv4_addrs": [
+ "47.89.242.123"
+ ],
+ "local_ipv4_addrs": [
+ "172.25.58.242"
+ ],
+ "provider": "alibaba",
+ "local_hostname": "123",
+ "public_ipv4": "47.89.242.123",
+ "local_ipv4": "172.25.58.242"
+}
+```
+
+The Chef Infra Language now includes an `alibaba?` helper method to check for instances running on Alibaba as well.
+
+### Improved Linux CPU Data
+
+Data collection in the `:Cpu` plugin on Linux has been greatly expanded to give enhanced information on architecture, cache, virtualization status, and overall model and configuration data. Thanks for this addition [@ramereth](https://github.com/ramereth)!
+
+### Packaging Improvements
+
+### PowerPC RHEL FIPS Support
+
+We now produce FIPS capable packages for RHEL on PowerPC
+
+### Sample client.rb on *nix Platforms
+
+On AIX, Solaris, macOS, and Linux platforms the Chef Infra Client packages will now create the various configuration directories under `/etc/chef` as well as a sample `/etc/chef/client.rb` file to make it easier to get started running the client.
+
+### New Deprecations
+
+### Unified Mode in Custom Resources
+
+In Chef Infra Client 16 we introduced Unified Mode allowing you to collapse the sometimes confusing compile and converge phases into a single unified phase. Unified mode makes it easier to write and troubleshoot failures in custom resources and for Chef Infra Client 18 we plan to make this the default execution phase for custom resources. We've backported the unified mode feature to the Chef Infra Client 14 and 15 systems and for Chef Infra Client 17 we will now begin warning if resources don't explicitly set this new mode. Enabling unified mode now lets you validate that resources will continue to function as expected in Chef Infra Client 18. To enable unified mode in your resource add `unified_mode true` to the file.
+
+## What's New in 16.13
+
+### Chef InSpec 4.31
+
+Chef InSpec has been updated from 4.29.3 to 4.31.1.
+
+#### New Features
+
+- Commands can now be set to timeout using the [command resource](https://docs.chef.io/inspec/resources/command/) or the [`--command-timeout`](https://docs.chef.io/inspec/cli/) option in the CLI. Commands timeout by default after one hour.
+- Added the [`--docker-url`](https://docs.chef.io/inspec/cli/) CLI option, which can be used to specify the URI to connect to the Docker Engine.
+- Added support for targeting Linux and Windows containers running on Docker for Windows.
+
+#### Bug Fixes
+
+- Hash inputs will now be loaded consistently and accessed as strings or symbols. ([#5446](https://github.com/inspec/inspec/pull/5446))
+
+### Ubuntu FIPS Support
+
+Our Ubuntu packages are now FIPS compliant for all your FedRAMP needs.
+
+### Chef Language Additions
+
+We now include a `centos_stream_platform?` helper to determine if your CentOS release is a standard [CentOS](https://www.centos.org/centos-linux/) release or a [CentOS Stream](https://www.centos.org/centos-stream/) release. This helper can be used in attributes files, recipes, and custom resources. Thanks for this new helper [@ramereth](https://github.com/ramereth)!
+
+### Resource Improvements
+
+#### dsc_script and dsc_resource
+
+Our PowerShell integration has been improved to better handle failures that were silently occurring when running some DSC code in Chef Infra Client 16.8 and later. Thanks for reporting this problem [@jeremyciak](https://github.com/jeremyciak)!
+
+### Platform Support Updates
+
+#### Ubuntu 16.04 EOL
+
+Packages will no longer be built for Ubuntu 16.04 as Canonical ended maintenance updates on April 30, 2021. See Chef's [Platform End-of-Life Policy](https://docs.chef.io/platforms/#platform-end-of-life-policy) for more information on when Chef ends support for an OS release.
+
+### Improved System Detection
+
+Ohai now includes a new `:OsRelease` plugin for Linux hosts that includes the content of `/etc/os_release`. This data can be very useful for accurately identifying the Linux distribution that Chef Infra Client is running on. Thanks for this new plugin [@ramereth](https://github.com/ramereth)!
+
+#### Sample `:OsRelease` Output
+
+```json
+{
+ "name": "Ubuntu",
+ "version": "18.04.5 LTS (Bionic Beaver)",
+ "id": "ubuntu",
+ "id_like": [
+ "debian"
+ ],
+ "pretty_name": "Ubuntu 18.04.5 LTS",
+ "version_id": "18.04",
+ "home_url": "https://www.ubuntu.com/",
+ "support_url": "https://help.ubuntu.com/",
+ "bug_report_url": "https://bugs.launchpad.net/ubuntu/",
+ "privacy_policy_url": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
+ "version_codename": "bionic",
+ "ubuntu_codename": "bionic"
+}
+```
+
+### Security
+
+#### Ruby 2.7.3
+
+Ruby has been updated to 2.7.3, which provides a large number of bug fixes and also resolves the following CVEs:
+
+- [CVE-2021-28966](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28966)
+- [CVE-2021-28966](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28966)
+
+## What's New in 16.12
+
+### Chef InSpec 4.29
+
+Chef InSpec has been updated from 4.28 to 4.29.3.
+
+#### New Features
+
+- The JSON metadata pass through configuration has been moved from the Automate reporter to the JSON Reporter. ([#5430](https://github.com/inspec/inspec/pull/5430))
+
+#### Bug Fixes
+
+- The apt resource now correctly fetches all package repositories using the `-name` flag in an environment where ZSH is the user's default shell. ([#5437](https://github.com/inspec/inspec/pull/5437))
+- Updates how InSpec profiles are created with GCP or AWS providers so they use `inputs` instead of `attributes`. ([#5435](https://github.com/inspec/inspec/pull/5435))
+
+### Resource Improvements
+
+#### service and chef_client_launchd
+
+The `service` and `chef_client_launchd` resources on macOS now use the full path to `launchctl` to avoid potential failures. Thanks [@krackajak](https://github.com/krackajak)!
+
+#### file
+
+Verifiers in the `file` resource are only run if the content actually changes. This can significantly speed execution of Chef Infra Client when no actual changes occur. Thanks [@joshuamiller01](https://github.com/joshuamiller01)!
+
+#### mount
+
+The mount resource now properly handles NFS mounts with a root of `/`. Thanks for reporting this [@eheydrick](https://github.com/eheydrick) and thanks for the fix [@ramereth](https://github.com/ramereth)!
+
+### powershell_script and dsc_script
+
+Our embedded PowerShell libraries have been updated for improved execution of PowerShell and DSC code on Windows systems.
+
+### Improved System Detection
+
+Ohai has been updated to better detect system configuration details:
+
+- Ohai now detects Chef Infra Clients running in the Effortless pattern at `node['chef_packages']['chef']['chef_effortless']`.
+- Windows packages installed for the current user are now detected in addition to system wide package installations. Thanks [@jaymzh](https://github.com/jaymzh)!
+- `Sangoma Linux` is now detected as part of the `rhel` platform family. Thanks [@hron84](https://github.com/hron84)!
+- Docker is now properly detected even if it's running on a virtualized system. Thanks [@jaymzh](https://github.com/jaymzh)!
+- Alibaba Cloud Linux is now detected as platform `alibabalinux` and platform family `rhel`.
+
+### Security
+
+Upgraded OpenSSL on macOS hosts to 1.1.1k, which resolves the following CVEs:
+
+- [CVE-2021-3450](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3450)
+- [CVE-2021-3449](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3449)
+
+## What's New in 16.11.7
+
+### Native Apple M1 Architecture Packages
+
+We now build and test native Apple M1 architecture builds of Chef Infra Client. These builds are available at [downloads.chef.io](https://downloads.chef.io), our `install.sh` scripts, and the [Omnitruck API](https://docs.chef.io/api_omnitruck/).
+
+### Chef InSpec 4.28
+
+Chef InSpec has been updated from 4.26.4 to 4.28.0.
+
+#### New Features
+
+- Added the option to filter out empty profiles from reports.
+- Exposed the `conf_path`, `content`, and `params` properties to the `auditd_conf` resource.
+- Added the ability to specify `--user` when connecting to docker containers.
+
+#### Bug Fixes
+
+- Fixed the `crontab` resource when passing a username to AIX.
+- Stopped a backtrace from occurring when using `cmp` to compare `nil` with a non-existing file.
+- Fixed `skip_control` to work on deeply nested profiles.
+- The `ssh_config` and `sshd_config` resources now correctly use the first value when a setting is repeated.
+
+### Fixes and Improvements
+
+- Upgraded openSSL on macOS from 1.0.2 to 1.1.1 in order to support Apple M1 builds.
+- Resolved an issue that caused the DNF and YUM package helpers to exit with error codes, which would show up in system logs.
+- Added a new attribute to make the upcoming Compliance Phase an opt-in feature: `node['audit']['compliance_phase']`. This should prevent the Compliance Phase from incorrectly running when using named run_lists or override run_lists. If you're currently testing this new phase, make sure to set this attribute to `true`.
+- `chef_client_cron`: the `append_log_file` property now sets up the cron job to use shell redirection (`>>`) instead of the `-L` flag
+
+## What's New in 16.10.17
+
+### Bugfixes
+
+- Resolved installation failures on some Windows systems
+- Fixed the `mount` resource for network mounts using the root level as the device. Thanks [@ramereth](https://github.com/ramereth)!
+- Resolved a Compliance Phase failure with profile names using the `@` symbol.
+
+### Security
+
+Upgraded OpenSSL to 1.0.2y, which resolves the following CVEs:
+
+- [CVE-2021-23841](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23841)
+- [CVE-2021-23839](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23839)
+- [CVE-2021-23840](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23840)
+
+### Platform Updates
+
+With the release of macOS 11 we will no longer produce packages for macOS 10.13 systems. See our [Platform End-of-Life Policy](https://docs.chef.io/platforms/#platform-end-of-life-policy) for details on the platform lifecycle.
+
+## What's New in 16.10
+
+### Improvements
+
+#### Improved Linux Network Detection
+
+On Linux systems, Chef Infra Client now detects all installed NICs on systems with more than 10 interfaces and will populate Ethernet pause frame information if present. Thanks for these improvements [@kuba-moo](https://github.com/kuba-moo) and [@Babar](https://github.com/Babar)!
+
+#### AWS Instance Metadata Service Version 2 (IMDSv2) support
+
+Chef Infra Client now supports the latest generation of AWS metadata services (IMDSv2). This allows you to secure the contents of the metadata endpoint while still exposing this data for use in Chef Infra cookbooks. Thanks for this new functionality [@wilkosz](https://github.com/wilkosz) and [@sawanoboly](https://github.com/sawanoboly)!
+
+#### Improved AWS Metadata Gathering
+
+On AWS instances, we now gather data from the latest metadata API versions, exposing new AWS instance information for use in Infra Cookbooks:
+
+- elastic-gpus/associations/elastic-gpu-id
+- elastic-inference/associations/eia-id
+- events/maintenance/history
+- events/maintenance/scheduled
+- events/recommendations/rebalance
+- instance-life-cycle
+- network/interfaces/macs/mac/network-card-index
+- placement/availability-zone-id
+- placement/group-name
+- placement/host-id
+- placement/partition-number
+- placement/region
+- spot/instance-action
+
+#### Alma Linux Detection
+
+Chef Infra Client now maps [Alma Linux](https://almalinux.org/) to the `rhel` `platform_family` value. Alma Linux is a new open-source RHEL fork produced by the CloudLinux team. Alma Linux falls under Chef's [Community Support](https://docs.chef.io/platforms/#community-support) platform support policy providing community driven support without the extensive testing given to commercially supported platforms in Chef Infra Client.
+
+You can test cookbooks on Alma Linux in Test Kitchen using [Alma Linux 8 Vagrant Images](https://app.vagrantup.com/bento/boxes/almalinux-8 on VirtualBox, Parallels, and VMware hypervisors as follows:
+
+```yaml
+platforms:
+ - name: almalinux-8
+ driver:
+ box: bento/almalinux-8
+```
+
+#### Knife Bootstrapping Without Sudo
+
+The `knife bootstrap` command now supports elevating privileges on systems without `sudo` by using the `su` command instead. Use the new `--su-user` and `--su-password` flags to specify credentials for `su`.
+
+### Resource Updates
+
+#### dnf_package
+
+The `dnf_package` has been updated to maintain idempotency when using the `:upgrade` action when the RPM release "number" contains a dot (`.`).
+
+#### windows_certificate
+
+The `windows_certificate` resource now honors the `user_store` property to manage certificates in the User store instead of the System store.
+
+## What's New in 16.9.32
+
+### Improvements
+
+- Resolved orphaned PowerShell processes when using Compliance Remediation content.
+- Reduced Chef Infra Client install size by up to 5%.
+
+### Chef InSpec 4.26.4
+
+Chef InSpec has been updated from 4.25.1 to 4.26.4.
+
+#### New Features
+
+- You can now directly refer to settings in the `nginx_conf` resource using the `its` syntax. Thanks [@rgeissert](https://github.com/rgeissert)!
+- You can now specify the shell type for WinRM connections using the `--winrm-shell-type` option. Thanks [@catriona1](https://github.com/catriona1)!
+- Plugin settings can now be set programmatically. Thanks [@tecracer-theinen](https:/github.com/tecracer-theinen)!
+
+#### Bug Fixes
+
+- Updated the `oracledb_session` to use more general invocation options. Thanks [@pacopal](https://github.com/pacopal)!
+- Fixed an error with the `http` resource in Chef Infra Client by including `faraday_middleware` in the gemspec.
+- Fixed an incompatibility between `parslet` and `toml` in Chef Infra Client.
+- Improved programmatic plugin configuration.
+
+## What's New in 16.9.29
+
+### Chef InSpec 4.25.1
+
+Chef InSpec has been updated from 4.24.8 to 4.25.1:
+
+- OpenSSH Client on Windows can now be tested with the ssh_config and sshd_config resources. Thanks [@rgeissert](https://github.com/rgeissert)!
+- The `--reporter-message-truncation` option now also truncates the `code_desc` field, preventing failures when sending large reports to Automate.
+
+### Bug Fixes
+
+- Resolved failures from running `chef-client` on some Windows systems.
+- Compliance Phase: Improved detection of the `audit` cookbook when it is used for compliance reporting.
+- chef-shell: Added support for loading configs in `client.d` directories - Thanks [@jaymzh](https://github.com/jaymzh)!
+- Duplicate gems in our packaging have been removed to further shrink the package sizes and improve load time.
+
+## What's New in 16.9.20
+
+- Updated the package resource on FreeBSD to work with recent changes to the pkgng executable. Thanks [@mrtazz](https://github.com/mrtazz/)
+- Added a missing dependency in the chef-zero binary that could cause failures when running chef-zero.
+- Resolved failures when running the audit cookbook from our yet-to-be-fully-released Chef Infra Compliance Phase. As it turns out, this dark launch was not as dark as we had hoped.
+
+## What's New in 16.9
+
+### Knife Improvements
+
+- The `knife bootstrap` command now properly formats the `trusted_certs_dir` configuration value on Windows hosts. Thanks for this fix [@axelrtgs](https://github.com/axelrtgs)!
+- The `knife bootstrap` command now only specifies the ssh option `-o IdentitiesOnly=yes` if keys are present. Thanks for this fix [@drbrain](https://github.com/drbrain)!
+- The `knife status` command with the `-F json` flag no longer fails if cloud nodes have no public IP.
+
+### Updated Resources
+
+#### cron_d
+
+The `cron_d` resource now respects the use of the `sensitive` property. Thanks for this fix [@axl89](https://github.com/axl89)!
+
+#### dnf
+
+The `dnf` resource has received a large number of improvements to provide improved idempotency and to better handle uses of the `version` and `arch` properties. Thanks for reporting these issues [@epilatow](https://github.com/epilatow) and [@Blorpy](https://github.com/Blorpy)!
+
+#### homebrew_cask
+
+The `homebrew_cask` resource has been updated to work with the latest command syntax requirements in the `brew` command. Thanks for reporting this issue [@bcg62](https://github.com/bcg62)!
+
+#### locale
+
+The allowed execution time for the `locale-gen` command in the `locale` resource has been extended to 1800 seconds to make sure the Chef Infra Client run doesn't fail before the command completes on slower systems. Thanks for reporting this issue [@janskarvall](https://github.com/janskarvall)!
+
+#### plist / macosx_service / osx_profile / macos_userdefaults
+
+Parsing of plist files has been improved in the `plist`, `macosx_service`, `osx_profile`, and `macos_userdefaults` resources thanks to updates to the plist gem by [@reitermarkus](https://github.com/reitermarkus) and [@tboyko](https://github.com/tboyko).
+
+#### user
+
+The `user` resource on Windows hosts now properly handles `uid` values passed as strings instead of integers. Thanks for reporting this issue [@jaymzh](https://github.com/jaymzh)!
+
+#### yum_repository
+
+The `yum_repository` resource has been updated with a new `reposdir` property to control the path where the Yum repository configuration files will be written. Thanks for suggesting this [@wildcrazyman](https://github.com/wildcrazyman)!
+
+### Security
+
+- The bundled Nokogiri Ruby gem has been updated to 1.11 resolve [CVE-2020-26247](https://nvd.nist.gov/vuln/detail/CVE-2020-26247).
+
+## What's New in 16.8.14
+
+- Updated openSSL to 1.0.2x to resolve [CVE-2020-1971](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1971).
+- Updated libarchive to 3.5.0, which powers the `archive_file` resource. This new release resolves extraction failures and better handles symlinks in archives.
+- `knife ssh` with the `--sudo` flag will no longer silently fail. Thanks for the fix [@rveznaver](https://github.com/rveznaver)!
+- Resolve failures running the Compliance Phase introduced in the 16.8.9 release. Thanks for reporting this issue [@axelrtgs](https://github.com/axelrtgs)!
+
+## What's New in 16.8.9
+
+### Chef InSpec 4.24
+
+Chef InSpec has been updated to 4.24.8 including the following improvements:
+
+- An unset `HOME` environment variable will not cause execution failures
+- You can use wildcards in `platform-name` and `release` in InSpec profiles
+- The support for arrays in the `WMI` resource, so it can return multiple objects
+- The `package` resource on Windows properly escapes package names
+- The `grub_conf` resource succeeds even if without a `menuentry` in the grub config
+- Loaded plugins won't try to re-load themselves
+
+### Updated Resources
+
+#### dsc_resource / dsc_script
+
+The `dsc_resource` and `dsc_script` resources have been updated to use the `powershell_exec` helper for significantly improved performance executing the PowerShell commands.
+
+#### hostname
+
+The `hostname` resource has been updated to prevent failures when the default system hostname is set on macOS hosts.
+
+#### remote_file
+
+The `remote_file` resource has been updated to use certificates located in Chef Infra Client's `trusted_certificates` directory. Thanks for reporting this issue [@carguel](https://github.com/carguel/)!
+
+#### windows_certificate
+
+The `windows_certificate` has been updated with a new `exportable` property that marks PFX files as exportable in the certificate store.
+
+### Ohai Improvements
+
+- A new optional `Grub2` plugin can be enabled to expose GRUB2 environment variables.
+- Linode cloud detection has been improved.
+
+### Platform Packages
+
+We are once again building packages for Solaris on Sparc and x86 platforms.
+
+## What's New in 16.7
+
+### Performance Enhancements
In Chef Infra Client 16.7, we've put a particular focus on optimizing the performance of the client. We've created several dozen minor optimizations that increase performance and reduce overall memory usage across all platforms. On Windows, our work has been particularly pronounced as we've improved resource execution and Chef Infra Client installation. Chef Infra Client install times on Windows are now up to 3x faster than previous releases. Resources that use PowerShell to make changes now execute significantly faster. This improvement will be the most noticeable in Chef Infra Client runs that don't make actual system changes (no-op runs), where determining the current system state was previously resource-intensive.
-## Windows Bootstrap Improvements
+### Windows Bootstrap Improvements
We've improved how Windows nodes are bootstrapped when using the `knife bootstrap` command. The `knife bootstrap` `--secret` flag is now respected on Windows hosts, allowing for the proper setup of nodes to use encrypted data bags. Thanks for reporting this issue [@AMC-7](https://github.com/AMC-7)! Additionally, during the bootstrap we now force connections to use TLS 1.2, preventing failures on Windows 2012-2016. Thanks for this improvement [@TimothyTitan](https://github.com/TimothyTitan)!
-## Chef Vault 4.1
+### Chef Vault 4.1
We've updated the release of `chef-vault` bundled with Chef Infra Client to 4.1. Chef Vault 4.1 properly handles escape strings in secrets and greatly improves performance for users with large numbers of secrets. Thanks for the performance work [@Annih](https://github.com/Annih)!
-## Updated Resources
+### Updated Resources
-### build_essential
+#### build_essential
-The `build_essential` resource has been updated to resolve idempotency issues and greatly improve performance on macOS hosts.
+The `build_essential` resource has been updated to resolve idempotency issues and greatly improve performance on macOS hosts.
-### chef_client_config
+#### chef_client_config
The `chef_client_config` resource has been updated to no longer produce invalid `client.rb` content.
-### group
+#### group
The `group` resource has been improved to provide log output of changes being made and on Windows now properly translates group SIDs to names in order to operate idempotently.
Thanks for these improvements [@jaymzh](https://github.com/jaymzh)!
-### homebrew_update
+#### homebrew_update
The `homebrew_update` has been updated to resolve failures that would occur when running the resource.
-### ifconfig
+#### ifconfig
The `ifconfig` resource has been updated to better support Linux distributions that are derivatives of either Ubuntu or Debian. Support for setting the `BRIDGE` property on RHEL-based systems has also been added.
-### mount
+#### mount
The `mount` resource has been updated to resolve several issues:
@@ -48,25 +874,25 @@ The `mount` resource has been updated to resolve several issues:
Thanks for reporting these issues [@limitusus](https://github.com/limitusus), [@axelrtgs](https://github.com/axelrtgs), and [@scarpe01](https://github.com/scarpe01)!
-### powershell_package
+#### powershell_package
The `powershell_package` resource has been updated to better force connections to use TLS 1.2 when communicating with the PowerShell Gallery on Windows Server 2012-2016. Connections must be forced to use TLS 1.2 as the system default cipher suite because Windows 2012-2016 did not include TLS 1.2.
-### powershell_script
+#### powershell_script
-The `powershell_script` resource has been updated to not fail when using a `not_if` or `only_if` guard when specifying the `user` property. Thanks for reporting this issue [@Blorpy](https://github.com/Blorpy)
+The `powershell_script` resource has been updated to not fail when using a `not_if` or `only_if` guard when specifying the `user` property. Thanks for reporting this issue [@Blorpy](https://github.com/Blorpy)!
-### user
+#### user
The `user` resource has been improved to provide log output of changes being made.
Thanks for this improvement [@jaymzh](https://github.com/jaymzh)!
-### zypper_package
+#### zypper_package
The `zypper_package` resource has been refactored to improve idempotency when specifying a version of the package to either install or downgrade.
-## Ohai Improvements
+### Ohai Improvements
- The `Joyent` plugin has been removed as the Joyent public cloud was shutdown 11/2019
- `pop_os` is now detected as having the `platform_family` of `debian`. Thanks for this improvement [@chasebolt](https://github.com/chasebolt)!
@@ -77,13 +903,13 @@ The `zypper_package` resource has been refactored to improve idempotency when sp
- Performance of system configuration gathering on AIX systems has been improved
- The `Virtualization` plugin on AIX systems now gathers a state `state` per WPAR and properly gathers LPAR names that include spaces
-# What's New in 16.6
+## What's New in 16.6
-## pwsh Support
+### pwsh Support
We've updated multiple parts of the Chef Infra Client to fully support Microsoft's `pwsh` (commonly known as PowerShell Core) in addition to our previous support for `PowerShell`.
-### powershell_script resource
+#### powershell_script resource
The `powershell_script` resource includes a new `interpreter` property that accepts either `powershell` or `pwsh`.
@@ -94,7 +920,7 @@ powershell_script 'check version table' do
end
```
-### powershell_out / powershell_exec helpers
+#### powershell_out / powershell_exec helpers
The `powershell_out` and `powershell_exec` helpers for use in custom resources have been updated to support `pwsh` with a new argument that accepts either `:pwsh` or `:powershell`.
@@ -102,17 +928,17 @@ The `powershell_out` and `powershell_exec` helpers for use in custom resources h
powershell_exec('$PSVersionTable', :pwsh)
```
-## Enhanced 32-bit Windows Support
+### Enhanced 32-bit Windows Support
The `powershell_exec` helper now supports the 32-bit version of Windows. This ensures many of the newer PowerShell based resources in Chef Infra Client will function as expected on 32-bit systems.
-## New Resources
+### New Resources
-### chef_client_config
+#### chef_client_config
The `chef_client_config` resource allows you to manage Chef Infra Client's `client.rb` file without the need for the `chef-client` cookbook.
-#### Example
+##### Example
```ruby
chef_client_config 'Create client.rb' do
@@ -120,7 +946,7 @@ chef_client_config 'Create client.rb' do
end
```
-#### chef-client Cookbook Future
+##### chef-client Cookbook Future
With the inclusion of the `chef_client_config` resource in Chef Infra Client 16.6, it is now possible to fully manage the Chef Infra Client without the need for the `chef-client` cookbook. We highly recommend using the `chef_client_config`, `chef_client_trusted_certificate`, and `chef_client_*` service resources to manage your clients instead of the `chef-client` cookbook. In the future we will mark that cookbook as deprecated, at which time it will no longer receive updates.
@@ -163,23 +989,23 @@ chef_client_systemd_timer "Run chef-client as a systemd timer" do
end
```
-## Target Mode Improvements
+### Target Mode Improvements
Chef Infra Client 16 introduced an experimental Target Mode feature for executing resources remotely against hosts that do not have a Chef Infra Client or even Ruby installed. For Chef Infra Client 16.6 we've improved this functionality by converting the majority of the Ohai plugins to run remotely. This means when using Target Mode you'll have the majority of Ohai data as if the Chef Infra Client was installed on the node. Keep in mind this data collection can be time consuming over high latency network connections, and cloud plugins which fetch metadata cannot currently be run remotely. Ohai also now includes a `--target` option for remote data gathering, which accepts a Train URI: `ohai --target ssh://foobar.example.org/`. We still consider Target Mode to be an experimental feature, and we'd love your feedback on what works and what doesn't in your environment. A super huge thanks for the countless hours of work put in by [tecRacer](https://www.tecracer.de/), [@tecracer-theinen](https://github.com/tecracer-theinen), and [burtlo](https://github.com/burtlo) to make this a reality.
-## Updated Resources
+### Updated Resources
-### ifconfig
+#### ifconfig
The `ifconfig` resource has been updated to no longer add empty blank lines to the configuration files. Thanks for this improvement [@jmherbst](https://github.com/jmherbst/)!
-### windows_audit_policy
+#### windows_audit_policy
The `windows_audit_policy` resource has been updated to fix a bug on failure-only auditing.
-## Ohai Improvements
+### Ohai Improvements
-### Passwd Plugin For Windows
+#### Passwd Plugin For Windows
The optional Ohai `Passwd` plugin now supports Windows hosts in addition to Unix-like systems. To collect user/group data on Windows hosts you can use the `ohai_optional_plugins` property in the new `chef_client_config` resource to enable this plugin.
@@ -192,57 +1018,57 @@ end
Thanks for adding Windows support to this plugin [@jaymzh](https://github.com/jaymzh)!
-### Improved Azure Detection
+#### Improved Azure Detection
The `Azure` plugin has been improved to better detect Windows hosts running on Azure. The plugin will now look for DHCP with the domain of `reddog.microsoft.com`. Thanks for this improvement [@jasonwbarnett](https://github.com/jasonwbarnett/)!
-### EC2 IAM Role Data
+#### EC2 IAM Role Data
Ohai now collects IAM Role data on EC2 hosts including the role name and info. To address potential security concerns the data we collect is sanitized to ensure we don't report security credentials to the Chef Infra Server. Thanks for this improvement [@kcbraunschweig](https://github.com/kcbraunschweig)!
-## Security
+### Security
Ruby has been updated to 2.7.2, which includes a fix for [CVE-2020-25613](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25613).
-# Whats New in 16.5.77
+## Whats New in 16.5.77
-* Added missing requires to prevent errors when loading `chef/policy_builder/dynamic`.
-* The `homebrew_package` resource will now check for the full and short package names. Both `homebrew_package 'homebrew/core/vim'` and `homebrew_package 'vim'` styles should now work correctly.
-* Resolved errors that occurred in cookbooks requiring `addressable/uri`.
-* Improved the license acceptance flow to give helpful information if the user passes an invalid value in the environment variable or command line argument.
-* Updated Chef InSpec to 4.23.11 in order to resolve issues when running the new `junit2` reporter.
-* Additional performance improvements to reduce the startup time of the `chef-client` and `knife` commands.
-* `knife vault` commands now output proper JSON or YAML when using the `-f json` or `-f yaml` flags.
+- Added missing requires to prevent errors when loading `chef/policy_builder/dynamic`.
+- The `homebrew_package` resource will now check for the full and short package names. Both `homebrew_package 'homebrew/core/vim'` and `homebrew_package 'vim'` styles should now work correctly.
+- Resolved errors that occurred in cookbooks requiring `addressable/uri`.
+- Improved the license acceptance flow to give helpful information if the user passes an invalid value in the environment variable or command line argument.
+- Updated Chef InSpec to 4.23.11 in order to resolve issues when running the new `junit2` reporter.
+- Additional performance improvements to reduce the startup time of the `chef-client` and `knife` commands.
+- `knife vault` commands now output proper JSON or YAML when using the `-f json` or `-f yaml` flags.
-# What's New in 16.5
+## What's New in 16.5
-## Performance Improvements
+### Performance Improvements
We continue to reduce the size of the Chef Infra Client install and optimize the performance of the client. With Chef Infra Client 16.5 we've greatly reduced the startup time of the `chef-client` process. Startup times on macOS, Linux, and Windows hosts are now approximately 2x faster than the 16.4 release.
-## CLI Improvements
+### CLI Improvements
-* The client license acceptance logic has been improved to provide helpful error messages when an incorrect value is passed and to accept license values in any text case.
-* A new `chef-client` process exit code of 43 has been added to signal that an invalid configuration was specified. Thanks [@NaomiReeves](https://github.com/NaomiReeves)!
-* The `knife ssh` command no longer hangs when connecting to Windows nodes over SSH.
-* The `knife config` commands have been renamed to make them shorter and table output has been improved:
- * knife config get-profile -> knife config use
- * knife config use-profile [NAME] -> knife config use [NAME]
- * knife config list-profiles -> knife config list
- * knife config get -> knife config show
+- The client license acceptance logic has been improved to provide helpful error messages when an incorrect value is passed and to accept license values in any text case.
+- A new `chef-client` process exit code of 43 has been added to signal that an invalid configuration was specified. Thanks [@NaomiReeves](https://github.com/NaomiReeves)!
+- The `knife ssh` command no longer hangs when connecting to Windows nodes over SSH.
+- The `knife config` commands have been renamed to make them shorter and table output has been improved:
+ - knife config get-profile -> knife config use
+ - knife config use-profile [NAME] -> knife config use [NAME]
+ - knife config list-profiles -> knife config list
+ - knife config get -> knife config show
-## Chef InSpec 4.23.4
+### Chef InSpec 4.23.4
Chef InSpec has been updated from 4.22.1 to 4.23.4. This new release includes the following improvements:
-* A new mechanism marks inputs as sensitive: true and replaces their values with `***`.
-* Use the `--no-diff` CLI option to suppress diff output for textual tests.
-* Control the order of controls in output, but not execution order, with the `--sort_results_by=none|control|file|random` CLI option.
-* Disable caching of inputs with a cache_inputs: true setting.
+- A new mechanism marks inputs as sensitive: true and replaces their values with `***`.
+- Use the `--no-diff` CLI option to suppress diff output for textual tests.
+- Control the order of controls in output, but not execution order, with the `--sort_results_by=none|control|file|random` CLI option.
+- Disable caching of inputs with a cache_inputs: true setting.
-## New Resources
+### New Resources
-### chef_client_launchd
+#### chef_client_launchd
The `chef_client_launchd` resource allows you to configure Chef Infra Client to run as a global launchd daemon on macOS hosts. This resource mirrors the configuration of other `chef_client_*` resources and allows for simple out-of-the-box configuration of the daemon, while also providing advanced tunables. If you've used the `chef-client` cookbook in the past, you'll notice a number of improvements in the new resource including configuration update handling, splay times support, nice level support, and an out-of-the-box configuration of low IO priority execution. In order to handle restarting the Chef Infra Client launchd daemon when configuration changes occur, the resource also installs a new `com.chef.restarter` daemon. This daemon watches for daemon configuration changes and gracefully handles the restart to ensure the client process continues to run.
@@ -253,7 +1079,7 @@ chef_client_launchd 'Setup the Chef Infra Client to run every 30 minutes' do
end
```
-### chef_client_trusted_certificate
+#### chef_client_trusted_certificate
The `chef_client_trusted_certificate` resource allows you to add a certificate to Chef Infra Client's trusted certificate directory. The resource handles platform-specific locations and creates the trusted certificates directory if it doesn't already exist. Once a certificate is added, it will be used by the client itself to communicate with the Chef Infra Server and by resources such as `remote_file`.
@@ -285,145 +1111,145 @@ chef_client_trusted_certificate 'self-signed.badssl.com' do
end
```
-## Resource Updates
+### Resource Updates
-### chef_client_cron
+#### chef_client_cron
The `chef_client_cron` resource has been updated with a new `nice` property that allows you to set the nice level for the `chef-client` process. Nice level changes only apply to the `chef-client` process and not any subprocesses like `ohai` or system utility calls. If you need to ensure that the `chef-client` process does not negatively impact system performance, we highly recommend instead using the `cpu_quota` property in the `chef_client_systemd_timer` resource which applies to all child processes.
-### chef_client_systemd_timer
+#### chef_client_systemd_timer
The `chef_client_systemd_timer` resource has been updated with a new `cpu_quota` property that allows you to control the systemd `CPUQuota` value for the `chef-client` process. This allows you to ensure `chef-client` execution doesn't adversely impact performance on your systems.
-### launchd
+#### launchd
The `launchd` resource has been updated to better validate inputs to the `nice` property so we can make sure these are acceptable nice values.
-### mount
+#### mount
The `mount` resource on Linux has new improved idempotency in some scenarios by switching to `findmnt` to determine the current state of the system. Thanks for reporting this issue [@pollosp](https://github.com/pollosp)!
-### osx_profile
+#### osx_profile
The `osx_profile` resource will now allow you to remove profiles from macOS 11 (Big Sur) systems. Due to security changes in macOS 11, it is no longer possible to locally install profiles, but this will allow you to cleanup existing profiles left over after an upgrade from an earlier macOS release. The resource has been updated to resolve a regression introduced in Chef Infra Client 16.4 that caused the resource to attempt to update profiles on each converge. Thanks for reporting these issues [@chilcote](https://github.com/chilcote)!
-### rhsm_register
+#### rhsm_register
The `rhsm_register` resource has been updated to reduce the load on the RedHat Satellite server when checking if a system is already registered. Thanks for reporting this issue [@donwlewis](https://github.com/donwlewis)! A new `system_name` property has also been added to allow you to register a name other than the system's hostname. Thanks for this improvement [@jasonwbarnett](https://github.com/jasonwbarnett/)!
-### windows_ad_join
+#### windows_ad_join
The `windows_ad_join` resource has been updated with a new `reboot_delay` property which allows you to control the delay time before restarting systems.
-### windows_firewall_profile
+#### windows_firewall_profile
The `windows_firewall_profile` resource was updated to prevent NilClass errors from loading the firewall state.
-### windows_user_privilege
+#### windows_user_privilege
The `windows_user_privilege` resource has been updated to better validate the `privilege` property and to allow the `users` property to accept String values. Thanks for reporting this issue [@jeremyciak](https://github.com/jeremyciak)!
-### Windows securable resources
+#### Windows securable resources
All Windows securable resources now support using SID in addition to user or group name when specifying `owner`, `group`, or `rights` principal. These resources include the `template`, `file`, `remote_file`, `cookbook_file`, `directory`, and `remote_directory` resources. When using a SID, you may use either the standard string representation of a SID (S-R-I-S-S) or one of the [SDDL string constants](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings).
-## Ohai Improvements
+### Ohai Improvements
-* Ohai now uses the same underlying code for shelling out to external commands as Chef Infra Client. This may resolve issues from determining the state on some non-English systems.
-* The `Packages` plugin has been updated to gather package installation information on macOS hosts.
+- Ohai now uses the same underlying code for shelling out to external commands as Chef Infra Client. This may resolve issues from determining the state on some non-English systems.
+- The `Packages` plugin has been updated to gather package installation information on macOS hosts.
-## Platform Packages
+### Platform Packages
-* We are once again building Chef Infra Client packages for RHEL 7 / SLES 12 on the S390x architecture. In addition to these packages, we've also added S390x packages for RHEL 8 / SLES 15.
-* We now produce packages for Apple's upcoming macOS 11 Big Sur release.
+- We are once again building Chef Infra Client packages for RHEL 7 / SLES 12 on the S390x architecture. In addition to these packages, we've also added S390x packages for RHEL 8 / SLES 15.
+- We now produce packages for Apple's upcoming macOS 11 Big Sur release.
-## Security
+### Security
OpenSSL has been updated to 1.0.2w which includes a fix for [CVE-2020-1968](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-1968).
-# What's New in 16.4
+## What's New in 16.4
-## Resource Updates
+### Resource Updates
-### chef_client_systemd_timer
+#### chef_client_systemd_timer
The `chef_client_systemd_timer` resource has been updated to prevent failures running the `:remove` action.
-### openssl resource
+#### openssl resource
The various openssl_* resources were refactored to better report the changed state of the resource to Automate or other handlers.
-### osx_profile
+#### osx_profile
The `osx_profile` resource has been refactored as a custom resource internally. This update also better reports the changed state of the resource to Automate or other handlers and no longer silently continues if the attempts to shellout fail.
-### powershell_package_source
+#### powershell_package_source
The `powershell_package_source` resource no longer requires the `url` property to be set when using the `:unregister` action. Thanks for this fix [@kimbernator](https://github.com/kimbernator)!
-### powershell_script
+#### powershell_script
The `powershell_script` resource has been refactored to better report the changed state of the resource to Automate or other handlers.
-### windows_feature
+#### windows_feature
The `windows_feature` resource has been updated to allow installing features that have been removed if a source location is provided. Thanks for reporting this [@stefanwb](https://github.com/stefanwb)!
-### windows_font
+#### windows_font
The `windows_font` resource will no longer fail on newer releases of Windows if a font is already installed. Thanks for reporting this [@bmiller08](https://github.com/bmiller08)!
-### windows_workgroup
+#### windows_workgroup
The `windows_workgroup` resource has been updated to treat the `password` property as a sensitive property. The value of the `password` property will no longer be shown in logs or handlers.
-## Security
+### Security
-### CA Root Certificates
+#### CA Root Certificates
The included `cacerts` bundle in Chef Infra Client has been updated to the 7-22-2020 release. This new release removes 4 legacy root certificates and adds 4 additional root certificates.
-### Reduced Dependencies
+#### Reduced Dependencies
We've audited the included dependencies that we ship with Chef Infra Client to reduce the 3rd party code we ship. We've removed many of the embedded binaries that shipped with the client in the past, but were not directly used. We've also reduced the feature set built into many of the libraries that we depend on, and removed several Ruby gem dependencies that were no longer necessary. This reduces the future potential for CVEs in the product and reduces package size at the same time.
-# What's New in 16.3.45
+## What's New in 16.3.45
-* Resolved failures negotiating protocol versions with the Chef Infra Server.
-* Improved log output on Windows systems in the `hostname` resource.
-* Added support to the `archive_file` resource for `pzstd` compressed files.
+- Resolved failures negotiating protocol versions with the Chef Infra Server.
+- Improved log output on Windows systems in the `hostname` resource.
+- Added support to the `archive_file` resource for `pzstd` compressed files.
-# What's New in 16.3.38
+## What's New in 16.3.38
-## Renamed Client Configuration Options
+### Renamed Client Configuration Options
We took a hard look at many of the terms we've historically used throughout the Chef Infra Client configuration sub-system and came to the realization that we weren't living up to the words of our [Community Code of Conduct](https://community.chef.io/code-of-conduct/). From the code of conduct: "Be careful in the words that you choose. Be kind to others. Practice empathy". Terms such as blacklist and sanity don't meet that bar so we've chosen to rename these configuration options:
-* `automatic_attribute_blacklist` -> `blocked_automatic_attributes`
-* `default_attribute_blacklist` -> `blocked_default_attributes`
-* `normal_attribute_blacklist` -> `blocked_normal_attributes`
-* `override_attribute_blacklist` -> `blocked_override_attributes`
-* `automatic_attribute_whitelist` -> `allowed_automatic_attributes`
-* `default_attribute_whitelist` -> `allowed_default_attributes`
-* `normal_attribute_whitelist` -> ``allowed_normal_attributes``
-* `override_attribute_whitelist` -> `allowed_override_attributes`
-* `enforce_path_sanity` -> `enforce_default_paths`
+- `automatic_attribute_blacklist` -> `blocked_automatic_attributes`
+- `default_attribute_blacklist` -> `blocked_default_attributes`
+- `normal_attribute_blacklist` -> `blocked_normal_attributes`
+- `override_attribute_blacklist` -> `blocked_override_attributes`
+- `automatic_attribute_whitelist` -> `allowed_automatic_attributes`
+- `default_attribute_whitelist` -> `allowed_default_attributes`
+- `normal_attribute_whitelist` -> ``allowed_normal_attributes``
+- `override_attribute_whitelist` -> `allowed_override_attributes`
+- `enforce_path_sanity` -> `enforce_default_paths`
Existing configuration options will continue to function for now, but will raise a deprecation warning and will be removed entirely from a future release of Chef Infra Client.
-## Chef InSpec 4.22.1
+### Chef InSpec 4.22.1
Chef InSpec has been updated from 4.21.1 to 4.22.1. This new release includes the following improvements:
-* The `=` character is now allowed for command line inputs
-* `apt-cdrom` repositories are now skipped when parsing out the list of apt repositories
-* Faulty profiles are now reported instead of causing a crash
-* Errors are no longer logged to stdout with the `html2` reporter
-* macOS Big Sur is now correctly identified as macOS
+- The `=` character is now allowed for command line inputs
+- `apt-cdrom` repositories are now skipped when parsing out the list of apt repositories
+- Faulty profiles are now reported instead of causing a crash
+- Errors are no longer logged to stdout with the `html2` reporter
+- macOS Big Sur is now correctly identified as macOS
-## New Resources
+### New Resources
-### windows_firewall_profile
+#### windows_firewall_profile
The `windows_firewall_profile` allows you to `enable`, `disable`, or `configure` Windows Firewall profiles. For example, you can now set up default actions and configure rules for the `Public` profile using this single resource instead of managing your own PowerShell code in a `powershell_script` resource:
@@ -439,84 +1265,84 @@ end
For a complete guide to all properties and additional examples, see the [windows_firewall_profile documentation](https://docs.chef.io/resources/windows_firewall_profile).
-## Resource Updates
+### Resource Updates
-### build_essential
+#### build_essential
Log output has been improved in the `build_essential` resource when running on macOS systems.
-### chef_client_scheduled_task
+#### chef_client_scheduled_task
The `chef_client_scheduled_task` resource no longer sets up the schedule task with invalid double quoting around the specified command. Thanks for reporting this issue [@tiobagio](https://github.com/tiobagio/).
-### execute
+#### execute
The `user` property in the `execute` resource can now accept user IDs as Integers.
-### git
+#### git
The `git` resource will no longer fail if syncing a branch that already exists locally. Thanks for fixing this [@lotooo](https://github.com/lotooo/).
-### macos_user_defaults
+#### macos_user_defaults
The `macos_user_defaults` has received a ground-up refactoring with new actions, additional properties, and better overall reliability:
-* Improved idempotency by properly loading the current state of domains.
-* Improved how we set `dict` and `array` type data.
-* Improved logging to show the existing key/value pair that is changed, and improved the property state data that the resource sends to handlers and/or Chef Automate.
-* Fixed a failure when setting keys or values that included a space.
-* Replaced the existing non-functional `global` property with a new default for the `domain` property. To set a key/value pair on the `NSGlobalDomain` domain, you can either set that value explicitly or just skip the `domain` property entirely and Chef Infra Client will default to `NSGlobalDomain`. The existing property has been marked as deprecated and we will ship a Cookstyle rule to detect cookbooks using this property in the future.
-* Fixed the `type` property to only accept valid inputs. Previously typos or otherwise incorrect values would just be ignored resulting in unexpected behavior. This may cause failures in your codebase if you previously used incorrect values. We will be shipping a Cookstyle rule to detect and correct these values in the future.
-* Added a new `delete` action to allow users to remove a key from a domain.
-* Added a new `host` property that lets you set per-host values. If you set this to `:current` it sets the -currentHost flag.
+- Improved idempotency by properly loading the current state of domains.
+- Improved how we set `dict` and `array` type data.
+- Improved logging to show the existing key/value pair that is changed, and improved the property state data that the resource sends to handlers and/or Chef Automate.
+- Fixed a failure when setting keys or values that included a space.
+- Replaced the existing non-functional `global` property with a new default for the `domain` property. To set a key/value pair on the `NSGlobalDomain` domain, you can either set that value explicitly or just skip the `domain` property entirely and Chef Infra Client will default to `NSGlobalDomain`. The existing property has been marked as deprecated and we will ship a Cookstyle rule to detect cookbooks using this property in the future.
+- Fixed the `type` property to only accept valid inputs. Previously typos or otherwise incorrect values would just be ignored resulting in unexpected behavior. This may cause failures in your codebase if you previously used incorrect values. We will be shipping a Cookstyle rule to detect and correct these values in the future.
+- Added a new `delete` action to allow users to remove a key from a domain.
+- Added a new `host` property that lets you set per-host values. If you set this to `:current` it sets the -currentHost flag.
-### windows_dns_record
+#### windows_dns_record
The `windows_dns_record` resource includes a new optional property, `dns_server`, allowing you to make changes against remote servers. Thanks for this addition [@jeremyciak](https://github.com/jeremyciak/).
-### windows_package
+#### windows_package
A Chef Infra Client 16 regression within `windows_package` that prevented specifying `path` in the `remote_file_attributes` property has been resolved. Thanks for reporting this issue [@asvinours](https://github.com/asvinours/).
-### windows_security_policy
+#### windows_security_policy
The `windows_security_policy` resource has been refactored to improve idempotency and improve log output when changes are made. You'll now see more complete change information in logs and any handler consuming this data will also receive more detailed change information.
-## Knife Improvements
+### Knife Improvements
-* Ctrl-C can now be used to exit knife even when being prompted for input.
-* `knife bootstrap` will now properly error if attempting to bootstrap an AIX system using an account with an expired password.
-* `knife profile` commands will no longer error if an invalid profile was previously set.
-* The `-o` flag for `knife cookbook upload` can now be used on Windows systems.
-* `knife ssh` now once again accepts legacy DSS host keys although we highly recommend upgrading to a more secure key algorithm if possible.
-* Several changes were made to knife to that may prevent intermittent failures running cookbook commands
+- Ctrl-C can now be used to exit knife even when being prompted for input.
+- `knife bootstrap` will now properly error if attempting to bootstrap an AIX system using an account with an expired password.
+- `knife profile` commands will no longer error if an invalid profile was previously set.
+- The `-o` flag for `knife cookbook upload` can now be used on Windows systems.
+- `knife ssh` now once again accepts legacy DSS host keys although we highly recommend upgrading to a more secure key algorithm if possible.
+- Several changes were made to knife to that may prevent intermittent failures running cookbook commands
-## Habitat Package Improvements
+### Habitat Package Improvements
Habitat packages for Windows, Linux and Linux2 are now built and tested against each pull request to Chef Infra Client. Additionally we've improved how these packages are built to reduce the size of the package, which reduces network utilization when using the Effortless deployment pattern.
-# What's New in 16.2.72
+## What's New in 16.2.72
-* Habitat packages for Chef Infra Client 16 are now published with full support for the `powershell_exec` helper now added.
-* Added a new `clear` action to the `windows_user_privilege` resource.
-* Resolved a regression in Chef Infra Client 16.1 and later that caused failures running on FIPS enabled systems.
-* Resolved failures in the `archive_file` resource when running on Windows hosts.
-* Resolved a failure when running `chef-apply` with the `-j` option. Thanks [@komazarari](https://github.com/komazarari).
-* Chef Infra Client running within GitHub Actions is now properly identified as running in a Docker container. Thanks [@jaymzh](http://github.com/jaymzh).
-* SSH connections are now reused, improving the speed of knife bootstrap and remote resources on slow network links. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen).
-* `node['network']['interfaces']` data now correctly identifies IPv6 next hops for IPv4 routes. Thanks [@cooperlees](https://github.com/cooperlees).
-* Updated InSpec from 4.20.10 to 4.21.1.
+- Habitat packages for Chef Infra Client 16 are now published with full support for the `powershell_exec` helper now added.
+- Added a new `clear` action to the `windows_user_privilege` resource.
+- Resolved a regression in Chef Infra Client 16.1 and later that caused failures running on FIPS enabled systems.
+- Resolved failures in the `archive_file` resource when running on Windows hosts.
+- Resolved a failure when running `chef-apply` with the `-j` option. Thanks [@komazarari](https://github.com/komazarari).
+- Chef Infra Client running within GitHub Actions is now properly identified as running in a Docker container. Thanks [@jaymzh](http://github.com/jaymzh).
+- SSH connections are now reused, improving the speed of knife bootstrap and remote resources on slow network links. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen).
+- `node['network']['interfaces']` data now correctly identifies IPv6 next hops for IPv4 routes. Thanks [@cooperlees](https://github.com/cooperlees).
+- Updated InSpec from 4.20.10 to 4.21.1.
-# What's New in 16.2.50
+## What's New in 16.2.50
-* Correctly identify the new macOS Big Sur (11.0) beta as platform "mac_os_x".
-* Fix `knife config use-profile` to fail if an invalid profile is provided.
-* Fix failures running the `windows_security_policy` resource.
-* Update InSpec from 4.20.6 to 4.20.10.
+- Correctly identify the new macOS Big Sur (11.0) beta as platform "mac_os_x".
+- Fix `knife config use-profile` to fail if an invalid profile is provided.
+- Fix failures running the `windows_security_policy` resource.
+- Update InSpec from 4.20.6 to 4.20.10.
-# What's New in 16.2.44
+## What's New in 16.2.44
-## Breaking Change in Resources
+### Breaking Change in Resources
In Chef Infra Client 16.0, we changed the way that custom resource names are applied in order to resolve some longstanding edge-cases. This change had several unintended side effects, so we're further changing how custom names are set in this release of Chef Infra Client.
@@ -547,21 +1373,21 @@ We've introduced several Cookstyle rules to detect both custom resources and leg
**[ChefDeprecations/HWRPWithoutProvides](https://github.com/chef/cookstyle/blob/master/docs/cops_chefdeprecations.md#chefdeprecationshwrpwithoutprovides)**: detects legacy HWRPs that don't include the necessary provides and resource_name calls for Chef Infra Client 16.
-## Chef InSpec 4.20.6
+### Chef InSpec 4.20.6
Chef InSpec has been updated from 4.18.114 to 4.2.0.6. This new release includes the following improvements:
-* Develop your own Chef InSpec Reporter plugins to control how Chef InSpec will report result data.
-* The `inspec archive` command packs your profile into a `tar.gz` file that includes the profile in JSON form as the inspec.json file.
-* Certain substrings within a `.toml` file no longer cause unexpected crashes.
-* Accurate InSpec CLI input parsing for numeric values and structured data, which were previously treated as strings. Numeric values are cast to an `integer` or `float` and `YAML` or `JSON` structures are converted to a hash or an array.
-* Suppress deprecation warnings on inspec exec with the `--silence-deprecations` option.
+- Develop your own Chef InSpec Reporter plugins to control how Chef InSpec will report result data.
+- The `inspec archive` command packs your profile into a `tar.gz` file that includes the profile in JSON form as the inspec.json file.
+- Certain substrings within a `.toml` file no longer cause unexpected crashes.
+- Accurate InSpec CLI input parsing for numeric values and structured data, which were previously treated as strings. Numeric values are cast to an `integer` or `float` and `YAML` or `JSON` structures are converted to a hash or an array.
+- Suppress deprecation warnings on inspec exec with the `--silence-deprecations` option.
-## New Resources
+### New Resources
-### windows_audit_policy
+#### windows_audit_policy
-The `windows_audit_policy` resource is used to configure system-level and per-user Windows advanced audit policy settings. See the [windows_audit_policy Documentation](https://docs.chef.io/resources/windows_audit_policy/) for complete usage information.
+The `windows_audit_policy` resource is used to configure system-level and per-user Windows advanced audit policy settings. See the [windows_audit_policy Documentation](/resources/windows_audit_policy/) for complete usage information.
For example, you can enable auditing of successful credential validation:
@@ -574,86 +1400,86 @@ windows_audit_policy "Set Audit Policy for 'Credential Validation' actions to 'S
end
```
-### homebrew_update
+#### homebrew_update
-The `homebrew_update` resource is used to update the available package cache for the Homebrew package system similar to the behavior of the `apt_update` resource. See the [homebrew_update Documentation](https://docs.chef.io/resources/homebrew_update/) for complete usage information. Thanks for adding this new resource, [@damacus](http://github.com/damacus).
+The `homebrew_update` resource is used to update the available package cache for the Homebrew package system similar to the behavior of the `apt_update` resource. See the [homebrew_update Documentation](/resources/homebrew_update/) for complete usage information. Thanks for adding this new resource, [@damacus](http://github.com/damacus).
-## Resource Updates
+### Resource Updates
-### All resources now include umask property
+#### All resources now include umask property
All resources, including custom resources, now have a `umask` property which allows you to specify a umask for file creation. If not specified the system default will continue to be used.
-### archive_file
+#### archive_file
The `archive_file` resource has been updated with two important fixes. The resource will no longer fail with uninitialized constant errors under some scenarios. Additionally, the behavior of the `mode` property has been improved to prevent incorrect file modes from being applied to the decompressed files. Due to how file modes and Integer values are processed in Ruby, this resource will now produce a deprecation warning if integer values are passed. Using string values lets us accurately pass values such as '644' or '0644' without ambiguity as to the user's intent. Thanks for reporting these issues [@sfiggins](http://github.com/sfiggins) and [@hammerhead](http://github.com/hammerhead).
-### chef_client_scheduled_task
+#### chef_client_scheduled_task
The `chef_client_scheduled_task` resource has been updated to default the `frequency_modifier` property to `30` if the `frequency` property is set to `minutes`, otherwise it still defaults to `1`. This provides a more predictable schedule behavior for users.
-### cron / cron_d
+#### cron / cron_d
The `cron` and `cron_d` resources have been updated using the new Custom Resource Partials functionality introduced in Chef Infra Client 16. This has allowed us to standardize the properties used to declare cron job timing between the two resources. The timing properties in both resources all accept the same types and ranges, and include the same validation, which makes moving from `cron` to `cron_d` seamless.
-### cron_access
+#### cron_access
The `cron_access` resource has been updated to support Solaris and AIX systems. Thanks [@aklyachkin](http://github.com/aklyachkin).
-### execute
+#### execute
The `execute` resource has a new `input` property which allows you to pass `stdin` input to the command being executed.
-### powershell_package
+#### powershell_package
The `powershell_package` resource has been updated to use TLS 1.2 when communicating with the PowerShell Gallery on Windows Server 2012-2016. Previously this resource used the system default cipher suite which did not include TLS 1.2. The PowerShell Gallery now requires TLS 1.2 for all communication, which caused failures on Windows Server 2012-2016. Thanks for reporting this issue [@Xorima](http://github.com/Xorima).
-### remote_file
+#### remote_file
The `remote_file` resource has a new property `ssl_verify_mode` which allows you to control SSL validation at the property level. This can be used to verify certificates (Chef Infra Client's defaults) with `:verify_peer` or to skip verification in the case of a self-signed certificate with `:verify_none`. Thanks [@jaymzh](http://github.com/jaymzh).
-### script
+#### script
The various `script` resources such as `bash` or `ruby` now pass the provided script content to the interpreter using system pipes instead of writing to a temporary file and executing it. Executing script content using pipes is faster, more secure as potentially sensitive scripts aren't written to disk, and bypasses issues around user privileges.
-### snap_package
+#### snap_package
Multiple issues with the `snap_package` resource have been resolved, including an infinite wait that occurred, and issues with specifying the package version or channel. Thanks [@jaymzh](http://github.com/jaymzh).
-### zypper_repository
+#### zypper_repository
The `zypper_repository` resource has been updated to work with the newer release of GPG in openSUSE 15 and SLES 15. This prevents failures when importing GPG keys in the resource.
-## Knife bootstrap updates
+### Knife bootstrap updates
-* Knife bootstrap will now warn when bootstrapping a system using a validation key. Users should instead use `validatorless bootstrapping` with `knife bootstrap` which generates node and client keys using the client key of the user bootstrapping the node. This method is far more secure as an org-wide validation key does not not need to be distributed or rotated. Users can switch to `validatorless bootstrapping` by removing any `validation_key` entries in their `config.rb (knife.rb)` file.
-* Resolved an error bootstrapping Linux nodes from Windows hosts
-* Improved information messages during the bootstrap process
+- Knife bootstrap will now warn when bootstrapping a system using a validation key. Users should instead use `validatorless bootstrapping` with `knife bootstrap` which generates node and client keys using the client key of the user bootstrapping the node. This method is far more secure as an org-wide validation key does not not need to be distributed or rotated. Users can switch to `validatorless bootstrapping` by removing any `validation_key` entries in their `config.rb (knife.rb)` file.
+- Resolved an error bootstrapping Linux nodes from Windows hosts
+- Improved information messages during the bootstrap process
-## Platform Packages
+### Platform Packages
-* Debian 8 packages are no longer being produced as Debian 8 is now end-of-life.
-* We now produce Windows 8 packages
+- Debian 8 packages are no longer being produced as Debian 8 is now end-of-life.
+- We now produce Windows 8 packages
-# What's New in 16.1.16
+## What's New in 16.1.16
This release resolves high-priority bugs in the 16.1 release of Chef Infra Client:
-* Resolved a critical performance regression in the Rubygems release within Ruby 2.7, which was discovered by a Chef engineer.
-* Resolved several Ruby 2.7 deprecation warnings.
-* Added `armv6l` and `armv7l` architectures to the `arm?` and `armhf?` helpers
-* Resolved failures in the Windows bootstrap script
-* Resolved incorrect paths when bootstrapping Windows nodes
+- Resolved a critical performance regression in the Rubygems release within Ruby 2.7, which was discovered by a Chef engineer.
+- Resolved several Ruby 2.7 deprecation warnings.
+- Added `armv6l` and `armv7l` architectures to the `arm?` and `armhf?` helpers
+- Resolved failures in the Windows bootstrap script
+- Resolved incorrect paths when bootstrapping Windows nodes
-## Security Updates
+### Security Updates
-### openSSL
+#### openSSL
openSSL has been updated from 1.0.2u to 1.0.2v which does not address any particular CVEs, but includes multiple security hardening updates.
-# What's New in 16.1
+## What's New in 16.1
-## Ohai 16.1
+### Ohai 16.1
Ohai 16.1 includes a new `Selinux` plugin which exposes `node['selinux']['status']`, `node['selinux']['policy_booleans']`, `node['selinux']['process_contexts']`, and `node['selinux']['file_contexts']`. Thanks [@davide125](http://github.com/davide125) for this contribution. This new plugin is an optional plugin which is disabled by default. It can be enabled within your `client.rb`:
@@ -661,47 +1487,47 @@ Ohai 16.1 includes a new `Selinux` plugin which exposes `node['selinux']['status
ohai.optional_plugins = [ :Selinux ]
```
-## Chef InSpec 4.18.114
+### Chef InSpec 4.18.114
InSpec has been updated from 4.18.111 to 4.18.114. This update adds new `--reporter_message_truncation` and `--reporter_backtrace_inclusion` reporter options to truncate messages and suppress backtraces.
-## Debian 10 aarch64
+### Debian 10 aarch64
Chef Infra Client packages are now produced for Debian 10 on the aarch64 architecture. These packages are available at [downloads.chef.io](https://downloads.chef.io/chef/).
-## Bug Fixes
+### Bug Fixes
-* Resolved a regression in the `launchd` resource that prevented it from converging.
-* The `:disable` action in the `launchd` resource no longer fails if the plist was not found.
-* Several Ruby 2.7 deprecation warnings have been resolved.
+- Resolved a regression in the `launchd` resource that prevented it from converging.
+- The `:disable` action in the `launchd` resource no longer fails if the plist was not found.
+- Several Ruby 2.7 deprecation warnings have been resolved.
-# What's New in 16.0.287
+## What's New in 16.0.287
The Chef Infra Client 16.0.287 release includes important bug fixes for the Chef Infra Client 16 release:
-* Fixes the failure to install Windows packages on the 2nd convergence of the Chef Infra Client.
-* Resolves several failures in the `launchd` resource.
-* Removes an extra `.java` file on Windows installations that would cause a failure in the IIS 8.5 Server Security Technical Implementation Guide audit.
-* Updates the `windows_printer` resource so that the driver property will only be required when using the `:create` action.
-* Fixes the incorrectly spelled `knife user invite recind` command to be `knife user invite rescind`. <!-* cspell:disable-line !-->
-* Update Chef InSpec to 4.8.111 with several minor improvements.
+- Fixes the failure to install Windows packages on the 2nd convergence of the Chef Infra Client.
+- Resolves several failures in the `launchd` resource.
+- Removes an extra `.java` file on Windows installations that would cause a failure in the IIS 8.5 Server Security Technical Implementation Guide audit.
+- Updates the `windows_printer` resource so that the driver property will only be required when using the `:create` action.
+- Fixes the incorrectly spelled `knife user invite recind` command to be `knife user invite rescind`. [//]: # "cspell:disable-line"
+- Update Chef InSpec to 4.8.111 with several minor improvements.
-# What's New in 16.0.275
+## What's New in 16.0.275
The Chef Infra Client 16.0.275 release includes important regression fixes for the Chef Infra Client 16 release:
-* Resolved failures when using the `windows_package` resource. Thanks for reporting this issue [@cookiecurse](https://github.com/cookiecurse).
-* Resolved log warnings when running `execute` resources.
-* The appropriate `cron` or `cron_d` resource call is now called when using the `:delete` action in chef_client_cron. Thanks for reporting this issue [jimwise](https://github.com/jimwise).
-* The `chef_client_cron` resource now creates the log directory with `750` permissions not `640`. Thanks for this fix [DhaneshRaghavan](https://github.com/DhaneshRaghavan).
-* The `knife yaml convert` command now correctly converts symbol values.
-* The `sysctl`, `apt_preference`, and `cron_d` remove actions no longer fail with missing property warnings.
+- Resolved failures when using the `windows_package` resource. Thanks for reporting this issue [@cookiecurse](https://github.com/cookiecurse).
+- Resolved log warnings when running `execute` resources.
+- The appropriate `cron` or `cron_d` resource call is now called when using the `:delete` action in chef_client_cron. Thanks for reporting this issue [jimwise](https://github.com/jimwise).
+- The `chef_client_cron` resource now creates the log directory with `750` permissions not `640`. Thanks for this fix [DhaneshRaghavan](https://github.com/DhaneshRaghavan).
+- The `knife yaml convert` command now correctly converts symbol values.
+- The `sysctl`, `apt_preference`, and `cron_d` remove actions no longer fail with missing property warnings.
-# What's New in 16.0
+## What's New in 16.0
-## Breaking Changes
+### Breaking Changes
-### Log Resource Notification Behavior
+#### Log Resource Notification Behavior
The `log` resource in a recipe or resource will no longer trigger notifications by default. This allows authors to more liberally use `log` resources without impacting the updated resources count or impacting reporting to Chef Automate. This change will impact users that used the `log` resource to aggregate notifications from other resources, so they could limit the number of times a notification would fire. If you used the `log` resource to aggregate multiple notifications, you should convert to using the `notify group` resource, which was introduced in Chef Infra Client 15.8.
@@ -745,35 +1571,35 @@ The `ChefDeprecations/LogResourceNotifications` cop in Cookstyle 6.0 and later d
To restore the previous behavior, set `count_log_resource_updates true` in your `client.rb`.
-### HWRP Style Resources Now Require resource_name / provides
+#### HWRP Style Resources Now Require resource_name / provides
Legacy HWRP-style resources, written as Ruby classes in the libraries directory of a cookbook, will now require either the use of `resource_name` or `provides` methods to define the resource names. Previously, Chef Infra Client would infer the desired resource name from the class, but this magic was problematic and has been removed.
The `ChefDeprecations/ResourceWithoutNameOrProvides` cop in Cookstyle 6.0 and later detects this deprecation.
-### build_essential GCC Updated on Solaris
+#### build_essential GCC Updated on Solaris
On Solaris systems, we no longer constrain the version of GCC to 4.8.2 in the `build_essential` resource to allow for GCC 5 installations.
-### git Resource Branch Checkout Changes
+#### git Resource Branch Checkout Changes
The `git` resource no longer checks out to a new branch named `deploy` by default. Many users found this branching behavior confusing and unexpected so we've decided to implement a more predictable default. The resource will now default to either checking out the branch specified with the `checkout_branch` property or a detached HEAD state. If you'd like to revert to the previous behavior you can set the `checkout_branch` to `deploy`.
-### s390x Packaging
+#### s390x Packaging
As outlined in our blog post at <https://blog.chef.io/chef-infra-end-of-life-announcement-for-linux-client-on-ibm-s390x-architecture/>, we will no longer be producing s390x platform packages for Chef Infra Client.
-### filesystem2 Node Data Replaces filesystem on FreeBSD / AIX / Solaris
+#### filesystem2 Node Data Replaces filesystem on FreeBSD / AIX / Solaris
In Chef Infra Client 14 we introduced a modernized filesystem layout of Ohai data on FreeBSD, AIX, and Solaris at `node['fileystem2']`. With the release of 16.0, we are now replacing the existing data at `node['filesystem']` with this updated filesystem data. This data has a standardized format that matches Linux and macOS data to make it easier to write cross-platform cookbooks. In a future release of Chef Infra Client we'll remove the `node['filesystem2']` as we complete this migration.
-### required: true on Properties Now Behaves As Expected
+#### required: true on Properties Now Behaves As Expected
The behavior of `required: true` has been changed to better align with the expected behavior. Previously, if you set a property `required: true` on a custom resource property and did not explicitly reference the property in an action, then Chef Infra Client would not raise an exception. This meant many users would add their own validation to raise for resources they wanted to ensure they were always set. `required: true` will now properly raise if a property has not been set.
-We have also expanded the `required` field for added flexibility in defining exactly which actions a property is required for. See [Improved property require behavior](#Improved-property-require-behavior) below for more details.
+We have also expanded the `required` field for added flexibility in defining exactly which actions a property is required for. See [Improved property require behavior](#improved-property-require-behavior) below for more details.
-### Removal of Legacy metadata.rb depends Version Constraints
+#### Removal of Legacy metadata.rb depends Version Constraints
Support for the `<<` and `>>` version constraints in metadata.rb has been removed. This was an undocumented feature from the Chef 0.10 era, which is not used in any cookbooks on the Supermarket. We are mentioning it since it is technically a breaking change, but it unlikely that this change will be impacting.
@@ -784,11 +1610,11 @@ depends 'windows', '<< 1.0'
depends 'windows', '>> 1.0'
```
-### Logging Improvements May Cause Behavior Changes
+#### Logging Improvements May Cause Behavior Changes
-We've made low-level changes to how logging behaves in Chef Infra Client that resolves many complaints we've heard of the years. With these change you'll now see the same logging output when you run `chef-client` on the command line as you will in logs from a daemonized client run. This also corrects often confusing behavior where running `chef-client` on the command line would log to the console, but not to the log file location defined your `client.rb`. In that scenario you'll now see logs in your console and in your log file. We believe this is the expected behavior and will mean that your on-disk log files can always be the source of truth for changes that were made by Chef Infra Client. This may cause unexpected behavior changes for users that relied on using the command line flags to override the `client.rb` log location * in this case logging will be sent to *both* the locations in `client.rb` and on the command line. If you have daemons running that log using the command line options you want to make sure that `client.rb` log location either matches or isn't defined.
+We've made low-level changes to how logging behaves in Chef Infra Client that resolves many complaints we've heard over the years. With these change you'll now see the same logging output when you run `chef-client` on the command line as you will in logs from a daemonized client run. This also corrects often confusing behavior where running `chef-client` on the command line would log to the console, but not to the log file location defined your `client.rb`. In that scenario you'll now see logs in your console and in your log file. We believe this is the expected behavior and will mean that your on-disk log files can always be the source of truth for changes that were made by Chef Infra Client. This may cause unexpected behavior changes for users that relied on using the command line flags to override the `client.rb` log location - in this case logging will be sent to _both_ the location in the `client.rb` and on the command line. If you have daemons running that log using the command line options you want to make sure that `client.rb` log location either matches or isn't defined.
-### Red Hat / CentOS 6 Systems Require C11 GCC for Some Gem Installations
+#### Red Hat / CentOS 6 Systems Require C11 GCC for Some Gem Installations
The included release of Ruby in Chef Infra Client 16 now requires a [C99](https://en.wikipedia.org/wiki/C99) compliant compiler when using the `chef_gem` resource with gems that require compilation. Some systems, such as RHEL 6, do not ship with a C99 compiler and will fail if the gems they're attempting to install require compilation. If it is necessary to install compiled gems into the Chef Infra Client installation on one of these systems you can upgrade to a modern GCC release.
@@ -808,91 +1634,91 @@ yum install devtoolset-7
scl enable devtoolset-7 bash
```
-### Changes to Improve Gem Source behavior
+#### Changes to Improve Gem Source behavior
We've improved the behavior for those that use custom rubygem sources, particularly those operating in air-gapped installations. These improvements involved changes to many of the default `client.rb` values and `gem_package`/`chef_gem` properties that require updating your usage of `chef_gem` and `gem_package` resources
The default value of the `clear_sources` property of `gem_package` and `chef_gem` resources has been changed to `nil`. The possible behaviors for clear_sources are now:
-* `true`: Always clear sources.
-* `false`: Never clear sources.
-* `nil`: Clear sources if `source` property is set, but don't clear sources otherwise.
+- `true`: Always clear sources.
+- `false`: Never clear sources.
+- `nil`: Clear sources if `source` property is set, but don't clear sources otherwise.
The default value of the `include_default_source` property of `gem_package` and `chef_gem` resources has been changed to `nil`. The possible behaviors for include_default_source are now:
-* `true`: Always include the default source.
-* `false`: Never include the default source.
-* `nil`: Include the default source if `rubygems_url` `client.rb` value is set or if `source` and `clear_sources` are not set on the resource.
+- `true`: Always include the default source.
+- `false`: Never include the default source.
+- `nil`: Include the default source if `rubygems_url` `client.rb` value is set or if `source` and `clear_sources` are not set on the resource.
The default values of the `rubygems_url` `client.rb` config option has been changed to `nil`. Setting to nil previously had similar behavior to setting `clear_sources` to true, but with some differences. The new behavior is to always use `https://rubygems.org` as the default rubygems repo unless explicitly changed, and whether to use this value is determined by `clear_sources` and `include_default_source`.
-### Behavior Changes in Knife
+#### Behavior Changes in Knife
-#### knife status --long uses cloud attribute
+**knife status --long uses cloud attribute**
The `knife status --long` resource now uses Ohai's cloud data instead of ec2 specific data. This improves, but changes, the data output for users on non-AWS clouds.
-#### knife download role/environment format update
+**knife download role/environment format update**
The `knife download role` and `knife download environment` commands now include all possible data fields including those without any data set. This new output behavior matches the behavior of other commands such as `knife role show` or `knife environment show`
-#### Deprecated knife cookbook site command removed
+**Deprecated knife cookbook site command removed**
The previously deprecated `knife cookbook site` commands have been removed. Use the `knife supermarket` commands instead.
-#### Deprecated knife data bag create -s short option removed
+**Deprecated knife data bag create -s short option removed**
The deprecated `knife data bag create -s` option that was not properly honored has been removed. Use the `--secret` option instead to set a data bag secret file during data bag creation.
-#### sites-cookbooks directory no longer in cookbook_path
+**sites-cookbooks directory no longer in cookbook_path**
The legacy `sites-cookbooks` directory is no longer added to the default `cookbook_path` value. With this change, any users with a legacy `sites-cookbooks` directory will need to use the `-O` flag to override the cookbook directory when running commands such as `knife cookbook upload`.
If you have a repository that contains a `site-cookbooks` directory, we highly recommend using Policyfiles or Berkshelf to properly resolve these external cookbook dependencies without the need to copy them locally. Alternatively, you can move the contents of this folder into your main cookbook directory and they will continue to be seen by knife commands.
-## New Resources
+### New Resources
-### alternatives
+#### alternatives
Use the `alternatives` resource to manage symbolic links to specify default command versions on Linux hosts. See the [alternatives documentation](https://docs.chef.io/resources/alternatives/) for full usage information. Thanks [@vkhatri](https://github.com/vkhatri) for the original cookbook alternatives resource.
-### chef_client resources
+#### chef_client resources
We've added new resources to Chef Infra Client for setting the client to run on an interval using native system schedulers. We believe that these native schedulers provide a more flexible and reliable method for running the client than the traditional method of running as a full service. Using the native schedulers reduces hung clients and eases upgrades. This is the first of many steps towards removing the need for the `chef-client` cookbook and allowing Chef Infra Client to configure itself out of the box.
-### chef_client_cron
+**chef_client_cron**
Use the `chef_client_cron` resource to setup the Chef Infra Client to run on a schedule using cron on Linux, Solaris, and AIX systems. See the [chef_client_cron documentation](https://docs.chef.io/resources/chef_client_cron/) for full usage information.
-### chef_client_systemd_timer
+**chef_client_systemd_timer**
Use the `chef_client_systemd_timer` resource to setup the Chef Infra Client to run on a schedule using a systemd timer on systemd based Linux systems (RHEL 7+, Debian 8+, Ubuntu 16.04+ SLES 12+). See the [chef_client_systemd_timer documentation](https://docs.chef.io/resources/chef_client_systemd_timer/) for full usage information.
-### chef_client_scheduled_task
+**chef_client_scheduled_task**
-Use the `chef_client_scheduled_task` resource to setup the Chef Infra Client to run on a schedule using Windows Scheduled Tasks. See the [chef_client_scheduled_task documentation](https://docs.chef.io/resources/chef_client_scheduled_task/) for full usage information.
+Use the `chef_client_scheduled_task` resource to setup the Chef Infra Client to run on a schedule using Windows Scheduled Tasks. See the [chef_client_scheduled_task documentation](https://docs.chef.io/resources/chef_client_scheduled_task) for full usage information.
-### plist
+#### plist
Use the `plist` resource to generate plist files on macOS hosts. See the [plist documentation](https://docs.chef.io/resources/plist/) for full usage information. Thanks Microsoft and [@americanhanko](https://github.com/americanhanko) for the original work on this resource in the [macos cookbook](https://supermarket.chef.io/cookbooks/macos).
-### user_ulimit
+#### user_ulimit
-Use the `user_ulimit` resource to set per-user ulimit values on Linux systems. See the [user_ulimit documentation](https://docs.chef.io/resources/user_ulimit/) for full usage information. Thanks [@bmhatfield](https://github.com/bmhatfield) for the original work on this resource in the [ulimit cookbook](https://supermarket.chef.io/cookbooks/ulimit).
+Use the `user_ulimit` resource to set per user ulimit values on Linux systems. See the [user_ulimit documentation](https://docs.chef.io/resources/user_ulimit/) for full usage information. Thanks [@bmhatfield](https://github.com/bmhatfield) for the original work on this resource in the [ulimit cookbook](https://supermarket.chef.io/cookbooks/ulimit).
-### windows_security_policy
+#### windows_security_policy
Use the `windows_security_policy` resource to modify location security policies on Windows hosts. See the [windows_security_policy documentation](https://docs.chef.io/resources/windows_security_policy/) for full usage information.
-### windows_user_privilege
+#### windows_user_privilege
Use the `windows_user_privilege` resource to add users and groups to the specified privileges on Windows hosts. See the [windows_user_privilege documentation](https://docs.chef.io/resources/windows_user_privilege/) for full usage information.
-## Improved Resources
+### Improved Resources
-### compile_time on all resources
+#### compile_time on all resources
-The `compile_time` property is now available for all resources so that they can be set to run at compile time without the need forcing the action.
+The `compile_time` property is now available for all resources so that they can be set to run at compile time without the need to force the action.
Set the `compile_time` property instead of forcing the resource to run at compile time:
@@ -910,36 +1736,36 @@ With the simpler `compile_time` property:
end
```
-### build_essential
+#### build_essential
The `build_essential` resource includes a new `:upgrade` action for macOS systems that allows you to install updates to the Xcode Command Line Tools available via Software Update.
-### cron
+#### cron
The `cron` resource has been updated to use the same property validation for cron times that the `cron_d` resource uses. This improves failure messages when invalid inputs are set and also allows for `jan`-`dec` values to be used in the `month` property.
-### dnf_package
+#### dnf_package
The `dnf_package` resource, which provides `package` under the hood on any system shipping with DNF, has been greatly refactored to resolve multiple issues. The version behavior and overall resource capabilities now match that of the `yum_package` resource.
-* The `:lock` action now works on RHEL 8.
-* Fixes to prevent attempting to install the same package during each Chef Infra Client run.
-* Resolved several idempotency issues.
-* Resolved an issue where installing a package with `options '--enablerepo=foo'` would fail.
+- The `:lock` action now works on RHEL 8.
+- Fixes to prevent attempting to install the same package during each Chef Infra Client run.
+- Resolved several idempotency issues.
+- Resolved an issue where installing a package with `options '--enablerepo=foo'` would fail.
-### git
+#### git
The `git` resource now fully supports why-run mode and no longer checks out the `deploy` branch by default as mentioned in the breaking changes section.
-### locale
+#### locale
The `locale` resource now supports setting the system locale on Windows hosts.
-### msu_package resource improvements
+#### msu_package resource improvements
The `msu_package` resource has been improved to work better with Microsoft's cumulative update packages. Newer releases of these cumulative update packages will not correctly install over the previous versions. We also extended the default timeout for installing MSU packages to 60 minutes. Thanks for reporting the timeout issue, [@danielfloyd](https://github.com/danielfloyd).
-### package
+#### package
The `package` resource on macOS and Arch Linux systems now supports passing multiple packages into a single package resource via an array. This allows you to collapse multiple resources into a single resource for simpler cookbook authoring, which is significantly faster as it requires fewer calls to the packaging systems. Thanks for the Arch Linux support, [@ingobecker](https://github.com/ingobecker)!
@@ -965,52 +1791,52 @@ can now be simplified to:
package %w(git curl packer)
```
-### service
+#### service
The `service` resource has been updated to support newer releases of `update-rc.d` so that it properly disables sys-v init services on Debian Linux distributions. Thanks [@robuye](https://github.com/robuye)!
-### windows_firewall_rule
+#### windows_firewall_rule
The `windows_firewall_rule` resource has been greatly improved thanks to work by [@pschaumburg](https://github.com/pschaumburg) and [@tecracer-theinen](https://github.com/tecracer-theinen).
-* New `icmp_type` property, which allows setting the ICMP type when setting up ICMP protocol rules.
-* New `displayname` property, which allows defining the display name of the firewall rule.
-* New `group` property, which allows you to specify that only matching firewall rules of the indicated group association are copied.
-* The `description` property will now update if changed.
-* Fixed setting rules with multiple profiles.
+- New `icmp_type` property, which allows setting the ICMP type when setting up ICMP protocol rules.
+- New `displayname` property, which allows defining the display name of the firewall rule.
+- New `group` property, which allows you to specify that only matching firewall rules of the indicated group association are copied.
+- The `description` property will now update if changed.
+- Fixed setting rules with multiple profiles.
-### windows_package
+#### windows_package
The `windows_package` resource now considers `3010` to be a valid exit code by default. The `3010` exit code means that a package has been successfully installed, but requires a reboot.
-#### knife-acl is now built-in
+**knife-acl is now built-in**
The `knife-acl` gem is now part of Chef Infra Client. This gives you the ability to manage Chef organizations and ACLs directly.
-## YAML Recipes
+### YAML Recipes
We added support for writing recipes in YAML to provide a low-code syntax for simple use cases. To write recipes in YAML, Chef resources and any user-defined parameters can be added as elements in a `resources` hash, such as the example below:
-​
+
```yaml
---
resources:
- * type: "package"
+ - type: "package"
name: "httpd"
- * type: "template"
+ - type: "template"
name: "/var/www/html/index.html"
source: "index.html.erb"
- * type: "service"
+ - type: "service"
name: "httpd"
action:
- * enable
- * start
+ - enable
+ - start
```
-​
+
This implementation is restrictive and does not support arbitrary Ruby code, helper functions, or attributes. However, if the need for additional customization arises, YAML recipes can be automatically converted into the DSL via the `knife yaml convert` command.
-## Custom Resource Improvements
+### Custom Resource Improvements
-### Improved property require behavior
+#### Improved property require behavior
As noted in the breaking changes above, we improved how the required value is set on custom resource properties, in order to give a more predictable behavior. This new behavior now allows you to specify actions where individual properties are required. This is especially useful when `:create` actions require certain properties that may not be required for a `:remove` type property.
@@ -1028,7 +1854,7 @@ action :remove do
end
```
-### Resource Partials
+#### Resource Partials
Resource partials allow you to define reusable portions of code that can be included in multiple custom resources. This feature is particularly useful when there are common properties, such as authentication properties, that you want to define in a single location, but use for multiple resources. Internally in the Chef Infra Client codebase, we have already used this feature to remove duplicate properties from our `subversion` and `git` resources and make them easier to maintain.
@@ -1080,29 +1906,29 @@ action_class do
end
```
-### after_resource
+#### after_resource
A new `after_resource` state has been added to resources that allows you to better control the resource state information reported to Chef Automate when a resource converges. If your custom resource uses the `load_current_value` helper, then this after state is calculated automatically. If you don't utilize the `load_current_value` helper and would like fine grained control over the state information sent to Chef Automate, you can use a new `load_after_resource` helper to load the state of each property for reporting.
-### identity Improvements
+#### identity Improvements
A resource's name property is now set to be the identity property by default and to have `desired_state: false` set by default. This eliminates the need to set `identity: true, desired_state: false` on these properties and better exposes identity data to handler and reporting.
-### compile_time property
+#### compile_time property
The `compile_time` property is now defined for all custom resources, so there is no need to add your own compile-time logic to your resource.
-## Other Improvements
+### Other Improvements
-### Up to 33% smaller on disk
+#### Up to 33% smaller on disk
We optimized the files that ship with Chef Infra Client and eliminated many unnecessary files from the installation, reducing the on-disk size of Chef Infra Client by up to 33%.
-### Windows Performance Improvements
+#### Windows Performance Improvements
We've optimized the Chef Infra Client for modern Windows releases and improved the performance on these systems.
-### Simpler Version Comparisons with node['platform_version']
+#### Simpler Version Comparisons with node['platform_version']
The `node['platform_version']` attribute returned from Ohai can now be intelligently compared as a version instead of as a String or Integer. Previously, to compare the platform_version, many users would first convert the version String to a Float with `node['platform_version']`. This introduced problems on many platforms, such as macOS, where macOS 10.9 would appear to be a greater version number than 10.15. You can now directly compare the version without converting it first.
@@ -1118,7 +1944,7 @@ Comparison using Ruby's pessimistic operator:
node['platform_version'] =~ '~> 10.15'
```
-### New helpers for recipes and resources
+#### New helpers for recipes and resources
Several helpers introduced in Chef Infra Client 15.5 are now available for use in any resource or recipe. These helpers include:
@@ -1138,7 +1964,7 @@ which('systemctl')
which('my_app', extra_path: '/opt/my_app/bin')
```
-### eager_load_libraries metadata.rb setting
+#### eager_load_libraries metadata.rb setting
By default, Chef Infra Client eagerly loads all ruby files in each cookbook's libraries directory at runtime. A new metadata.rb option `eager_load_libraries` has been introduced and allows you to control if and when a cookbook library is loaded. Depending on the construction of your libraries, this new option may greatly improve the runtime performance of your cookbook. With eager loading disabled, you may manually load libraries included in your cookbook using Ruby's standard `require` method. Metadata.rb configuration options:
@@ -1150,7 +1976,7 @@ eager_load_libraries %w(helper_library_1.rb helper_library_2.rb) # eager load bo
Note: Unless you are experiencing performance issues in your libraries, we advise against changing the loading behavior.
-### always_dump_stacktrace client.rb option
+#### always_dump_stacktrace client.rb option
A new `always_dump_stacktrace` client.rb configuration option and command line option allows you to have any Ruby stacktraces from Chef Infra Client logged directly to the log file. This may help troubleshooting when used in conjunction with centralized logging systems such as Splunk. To enable this new option, run `chef-client --always-dump-stacktrace` or add the following to your `client.rb`:
@@ -1158,29 +1984,29 @@ A new `always_dump_stacktrace` client.rb configuration option and command line o
always_dump_stacktrace true
```
-### Chef Vault Functionality Out of the Box
+#### Chef Vault Functionality Out of the Box
Chef Infra Client now ships with built-in Chef Vault functionality, so there's no need to depend on the `chef-vault` cookbook or gem. Chef Vault helpers `chef_vault_item`, `chef_vault`, and `chef_vault_item_for_environment` are included, as well as the `chef_vault_secret` resource. Additionally, the Chef Vault knife commands are also available out of the box. We do not recommend new users adopt the Chef Vault workflow due to limitations with autoscaling new systems, so these resources should only be consumed by existing Chef Vault users.
-### Ruby 2.7
+#### Ruby 2.7
Chef Infra Client's ruby installation has been updated to from Ruby 2.6 to Ruby 2.7, which includes many features available for use in resources and libraries.
See <https://medium.com/rubyinside/whats-new-in-ruby-2-7-79c98b265502> for details on many of the new features.
-### Ohai 16 Improvements
+#### Ohai 16 Improvements
Ohai has been improved to gather additional system configuration information for use when authoring recipes and resources.
-#### filesystem2 Node Data available on Windows
+**filesystem2 Node Data available on Windows**
In previous Chef Infra Clients we've introduced a modernized filesystem layout of Ohai data for many platforms. In Chef Infra Client 16.0, Windows now has this layout available in `node['filesystem2']`. In Chef Infra Client 17, it will replace `node['filesystem']` to match all other platforms.
-#### Extended Azure Metadata
+**Extended Azure Metadata**
The `Azure` Ohai plugin now gathers the latest version of the metadata provided by the Azure metadata endpoint. This greatly expands the information available on Azure instances. See [Ohai PR 1427](https://github.com/chef/ohai/pull/1427) for an example of the new data gathered.
-#### New Ohai Plugins
+**New Ohai Plugins**
New `IPC` and `Interupts` plugins have been added to Ohai. The IPC plugin exposes SysV IPC shmem information and interupts plugin exposes data from `/proc/interrupts` and `/proc/irq`. Thanks [@jsvana](https://github.com/jsvana) and [@davide125](https://github.com/davide125) for these new plugins.
@@ -1193,178 +2019,470 @@ ohai.optional_plugins = [
]
```
-#### Improved Linux Network Plugin Data
+**Improved Linux Network Plugin Data**
The Linux Network plugin has been improved to gather additional information from the `ethtool` utility. This includes the number of queues (`ethtool -l`), the coalesce parameters (`ethtool -c`), and information about the NIC driver (`ethtool -i`). Thanks [@matt-c-clark](https://github.com/matt-c-clark) for these improvements.
-#### Windows DMI plugin
+**Windows DMI plugin**
Windows systems now include a new `DMI` plugin which presents data in a similar format to the `DMI` plugin on *nix systems. This makes it easier to detect system information like manufacturer, serial number, or asset tag number in a cross-platform way.
-## New Platforms
+### New Platforms
Over the last quarter, we worked to greatly expand the platforms that we support with the addition of Chef Infra Client packages for Ubuntu 20.04 amd64, Amazon Linux 2 x86_64/aarch64, and Debian 10 amd64. With the release of Chef Infra Client 16, we expanded our platform support again with the following new platforms:
-* RHEL 8 aarch64
-* Ubuntu 20.04 aarch64
-* SLES 16 aarch64
+- RHEL 8 aarch64
+- Ubuntu 20.04 aarch64
+- SLES 16 aarch64
-## Newly Introduced Deprecations
+### Newly Introduced Deprecations
Several legacy Windows helpers have been deprecated as they will always return true when running on Chef Infra Client's currently supported platforms. The helpers previously detected systems prior to Windows 2012 and systems running Windows Nano, which has been discontinued by Microsoft. These helpers were never documented externally so their usage is most likely minimal. A new Cookstyle rule has been introduced to detect the usage of `older_than_win_2012_or_8?`: [ChefDeprecations/DeprecatedWindowsVersionCheck](https://github.com/chef/cookstyle/blob/master/docs/cops_chefdeprecations.md#chefdeprecationsdeprecatedwindowsversioncheck).
-* Chef::Platform.supports_msi?
-* Chef::Platform.older_than_win_2012_or_8?
-* Chef::Platform.supports_powershell_execution_bypass?
-* Chef::Platform.windows_nano_server?
+- Chef::Platform.supports_msi?
+- Chef::Platform.older_than_win_2012_or_8?
+- Chef::Platform.supports_powershell_execution_bypass?
+- Chef::Platform.windows_nano_server?
+
+## What's new in 15.17
+
+### Chef InSpec 4.32
+
+Updated Chef InSpec from 4.29.3 to 4.32.
+
+#### New Features
+
+- Commands can now be set to timeout using the [command resource](https://docs.chef.io/inspec/resources/command/) or the [`--command-timeout`](https://docs.chef.io/inspec/cli/) option in the CLI. Commands timeout by default after one hour.
+- Added the [`--docker-url`](https://docs.chef.io/inspec/cli/) CLI option, which can be used to specify the URI to connect to the Docker Engine.
+- Added support for targeting Linux and Windows containers running on Docker for Windows.
+- Added ability to pass inputs to InSpec shell using input file and cli. For more information, see [How can I set Inputs?](https://docs.chef.io/inspec/inputs/#how-can-i-set-inputs) in the InSpec documentation.
+
+#### Bug Fixes
+
+- Hash inputs will now be loaded consistently and accessed as strings or symbols. ([#5446](https://github.com/inspec/inspec/pull/5446))
+
+### Security
+
+#### Ruby
+
+We updated Ruby from 2.6.6 to 2.6.7 to resolve a large number of bugs as well as the following CVEs:
+
+- [CVE-2021-28966](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28966)
+- [CVE-2021-28965](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28965)
+- [CVE-2020-25613](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25613)
+
+## What's new in 15.16
+
+### Fixes and Improvements
+
+- Improved license acceptance failure messaging if incorrect values are provided.
+- License acceptance values are no longer case sensitive.
+- Resolved several failures that could occur in the `windows_certificate` resource.
+- Improved handling of WinRM connections when bootstrapping Windows nodes.
+- Switched docker containers back to EL6 packages to prevent failures running the containers with Kitchen Dokken to test RHEL 6 systems.
+- Fixed non-0 exit codes in the Yum and DNF helper scripts which caused errors in system logs.
+- Fixed package failures in FreeBSD due to changes in `pkgng` exit codes.
+- Added support for `client.d` configuration files in `chef-shell`.
+
+### Chef InSpec
+
+Chef InSpec has been updated from 4.24.8 to 4.29.3.
+
+#### New Features
+
+- The JSON metadata pass-through configuration has been moved from the Automate reporter to the JSON Reporter.
+- Added the option to filter out empty profiles from reports.
+- Exposed the `conf_path`, `content`, and `params` properties to the `auditd_conf` resource.
+- You can now directly refer to settings in the `nginx_conf` resource using the `its` syntax. Thanks [@rgeissert](https://github.com/rgeissert)!
+- Plugin settings can now be set programmatically. Thanks [@tecracer-theinen](https:/github.com/tecracer-theinen)!
+- OpenSSH Client on Windows can now be tested with the `ssh_config` and `sshd_config` resources. Thanks [@rgeissert](https://github.com/rgeissert)!
+
+#### Bug Fixes
+
+- The `--reporter-message-truncation` option now also truncates the `code_desc` field, preventing failures when sending large reports to Automate.
+- Fixed `skip_control` to work on deeply nested profiles.
+- The `ssh_config` and `sshd_config` resources now correctly use the first value when a setting is repeated.
+- Fixed the `crontab` resource when passing a username to AIX.
+- Stopped a backtrace from occurring when using `cmp` to compare `nil` with a non-existing file.
+- The `apt` resource now correctly fetches all package repositories using the `-name` flag in an environment where ZSH is the user's default shell.
+- The `--controls` option in `inspec exec` now correctly filters the controls by name.
+- Updates how InSpec profiles are created with GCP or AWS providers so they use `inputs` instead of `attributes`.
+- `inspec exec` will now fetch profiles via Git regardless of the name of the default branches now correctly use the first value when a setting is repeated.
+- Updated the `oracledb_session` to use more general invocation options. Thanks [@pacopal](https://github.com/pacopal)!
+- Fixed an error with the `http` resource in Chef Infra Client by including `faraday_middleware` in the gemspec.
+- Fixed an incompatibility between `parslet` and `toml` in Chef Infra Client.
+- Improved programmatic plugin configuration.
+
+### Security
+
+Upgraded OpenSSL to 1.0.2y, which resolves the following CVEs:
+
+- [CVE-2021-23841](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23841)
+- [CVE-2021-23839](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23839)
+- [CVE-2021-23840](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-23840)
+
+### Platform Updates
+
+With the release of macOS 11, we will no longer produce packages for macOS 10.13 systems. See our [Platform End-of-Life Policy](https://docs.chef.io/platforms/#platform-end-of-life-policy) for details on the platform lifecycle.
+
+## What's new in 15.15
+
+### Chef InSpec 4.24.8
+
+Chef InSpec has been updated from 4.22.22 to 4.24.8 with the following improvements:
+
+- An unset `HOME environment variable will not cause execution failures
+- You can use wildcards in `platform-name` and `release` in InSpec profiles
+- The support for arrays in the `WMI` resource, so it can return multiple objects
+- The `package` resource on Windows properly escapes package names
+- The `grub_conf` resource succeeds even if without a `menuentry` in the grub config
+- Loaded plugins won't try to re-load themselves
+- A new mechanism marks inputs as sensitive: true and replaces their values with `***`.
+- Use the `--no-diff` CLI option to suppress diff output for textual tests.
+- Control the order of controls in output, but not execution order, with the `--sort_results_by=none|control|file|random` CLI option.
+- Disable caching of inputs with a cache_inputs: true setting.
+
+### Chef Vault 4.1
+
+We've updated the release of `chef-vault` bundled with Chef Infra Client to 4.1. Chef Vault 4.1 properly handles escape strings in secrets and greatly improves performance for users with large numbers of secrets. Thanks for the performance work [@Annih](https://github.com/Annih)!
+
+### Resource Improvements
+
+#### cron_d
+
+The `cron_d` resource now respects the use of the `sensitive` property. Thanks for this fix [@axl89](https://github.com/axl89)!
+
+#### homebrew_cask
+
+The `homebrew_cask` resource has been updated to work with the latest command syntax requirements in the `brew` command. Thanks for reporting this issue [@bcg62](https://github.com/bcg62)!
+
+#### locale
+
+The allowed execution time for the `locale-gen` command in the `locale` resource has been extended to 1800 seconds to make sure the Chef Infra Client run doesn't fail before the command completes on slower systems. Thanks for reporting this issue [@janskarvall](https://github.com/janskarvall)!
+
+#### plist / macosx_service / osx_profile / macos_userdefaults
+
+Parsing of plist files has been improved in the `plist`, `macosx_service`, `osx_profile`, and `macos_userdefaults` resources thanks to updates to the plist gem by [@reitermarkus](https://github.com/reitermarkus) and [@tboyko](https://github.com/tboyko).
+
+### Security
+
+- The bundled Nokogiri Ruby gem has been updated to 1.11 resolve [CVE-2020-26247](https://nvd.nist.gov/vuln/detail/CVE-2020-26247).
+- openSSL has been updated to 1.0.2x to resolve [CVE-2020-1971](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1971).
+
+## What's New In 15.14
+
+### Chef InSpec 4.22.22
+
+Chef InSpec has been updated from 4.22.1 to 4.22.22. This new release includes the following improvements:
+
+- Fix mysql_session stdout, stderr and exit_status parameters. Thanks [@ramereth](https://github.com/ramereth)!
+- Add new windows_firewall and windows_firewall_rule resources. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen)!
+
+### Fixes and Improvements
+
+- The `knife ssh` command no longer hangs when connecting to Windows nodes over SSH.
+- Resolved several failures that could occur in the included chef-vault gem.
+
+### Resource Updates
+
+#### hostname
+
+The `hostname` resource has been updated to improve logging on Windows systems.
+
+#### windows_feature
+
+The `windows_feature` resource has been updated to allow installing features that have been removed if a source location is provided. Thanks for reporting this [@stefanwb](https://github.com/stefanwb)!
+
+#### windows_font
+
+The `windows_font` resource will no longer fail on newer releases of Windows if a font is already installed. Thanks for reporting this [@bmiller08](https://github.com/bmiller08)!
+
+### Platform Packages
+
+- We are once again building Chef Infra Client packages for RHEL 7 / SLES 12 on the S390x architecture. In addition to these packages, we've also added S390x packages for SLES 15.
+- We now produce packages for Apple's upcoming macOS 11 Big Sur release.
+
+### Security
+
+#### OpenSSL
+
+OpenSSL has been updated to 1.0.2w which includes a fix for [CVE-2020-1968](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-1968).
+
+#### CA Root Certificates
+
+The included `cacerts` bundle in Chef Infra Client has been updated to the 7-22-2020 release. This new release removes 4 legacy root certificates and adds 4 additional root certificates.
+
+## What's New In 15.13
+
+### Chef InSpec 4.22.1
+
+Chef InSpec has been updated from 4.20.6 to 4.22.1. This new release includes the following improvements:
+
+- `apt-cdrom` repositories are now skipped when parsing out the list of apt repositories
+- Faulty profiles are now reported instead of causing a crash
+- Errors are no longer logged to stdout with the `html2` reporter
+- macOS Big Sur is now correctly identified as macOS
+- macOS/BSD support added to the interface resource along with new `ipv4_address`, `ipv4_addresses`, `ipv4_addresses_netmask`, `ipv4_cidrs`, `ipv6_addresses`, and `ipv6_cidrs` properties
+
+### Fixes and Improvements
+
+- Support for legacy DSA host keys has been restored in `knife ssh` and `knife bootstrap` commands.
+- The collision warning error message when a cookbook includes a resource that now ships in Chef Infra Client has been improved to better explain the issue.
+- Package sizes have been reduced with fewer installed files on disk.
+- The `archive_file` resource now supports `pzstd` compressed files.
+
+### New Deprecations
+
+Chef Infra Client 16.2 and later require `provides` when assigning a name to a custom resource. In order to prepare for Chef Infra Client 16, make sure to include both `resource_name` and `provides` in resources when specifying a custom name.
+
+## What's New In 15.12
+
+### Chef InSpec 4.20.6
+
+Chef InSpec has been updated from 4.18.114 to 4.2.0.6. This new release includes the following improvements:
+
+- Develop your own Chef InSpec Reporter plugins to control how Chef InSpec will report result data.
+- The `inspec archive` command packs your profile into a `tar.gz` file that includes the profile in JSON form as the inspec.json file.
+- Certain substrings within a `.toml` file no longer cause unexpected crashes.
+- Accurate InSpec CLI input parsing for numeric values and structured data, which were previously treated as strings. Numeric values are cast to an `integer` or `float` and `YAML` or `JSON` structures are converted to a hash or an array.
+- Suppress deprecation warnings on `inspec exec` with the `--silence-deprecations` option.
+
+### Resource Updates
+
+#### archive_file
+
+The `archive_file` resource has been updated with two important fixes. The resource will no longer fail with uninitialized constant errors under some scenarios. Additionally, the behavior of the `mode` property has been improved to prevent incorrect file modes from being applied to the decompressed files. Due to how file modes and Integer values are processed in Ruby, this resource will now produce a deprecation warning if integer values are passed. Using string values lets us accurately pass values such as '644' or '0644' without ambiguity as to the user's intent. Thanks for reporting these issues [@sfiggins](http://github.com/sfiggins) and [@hammerhead](http://github.com/hammerhead).
+
+#### cron_access
+
+The `cron_access` resource has been updated to support Solaris and AIX systems. Thanks [@aklyachkin](http://github.com/aklyachkin).
+
+#### msu_package resource improvements
+
+The `msu_package` resource has been improved to work better with Microsoft's cumulative update packages. Newer releases of these cumulative update packages will not correctly install over the previous versions. We also extended the default timeout for installing MSU packages to 60 minutes. Thanks for reporting the timeout issue [@danielfloyd](https://github.com/danielfloyd).
+
+#### powershell_package
+
+The `powershell_package` resource has been updated to use TLS 1.2 when communicating with the PowerShell Gallery on Windows Server 2012-2016. Previously, this resource used the system default cipher suite which did not include TLS 1.2. The PowerShell Gallery now requires TLS 1.2 for all communication, which caused failures on Windows Server 2012-2016. Thanks for reporting this issue [@Xorima](http://github.com/Xorima).
+
+#### snap_package
+
+Multiple issues with the `snap_package` resource have been resolved, including an infinite wait that occurred and issues with specifying the package version or channel. Thanks [@jaymzh](http://github.com/jaymzh).
+
+#### zypper_repository
+
+The `zypper_repository` resource has been updated to work with the newer release of GPG in openSUSE 15 and SLES 15. This prevents failures when importing GPG keys in the resource.
+
+### Knife bootstrap updates
+
+- Knife bootstrap will now warn when bootstrapping a system using a validation key. Users should instead use `validatorless bootstrapping` with `knife bootstrap` which generates node and client keys using the client key of the user bootstrapping the node. This method is far more secure as an org-wide validation key does not not need to be distributed or rotated. Users can switch to `validatorless bootstrapping` by removing any `validation_key` entries in their `config.rb (knife.rb)` file.
+- Resolved an error bootstrapping Linux nodes from Windows hosts
+- Improved information messages during the bootstrap process
+
+### SSH Improvements
+
+The `net-ssh` library used by the `knife ssh` and `knife bootstrap` commands has been updated bringing improvements to SSH connectivity:
-# What's New in 15.10
+- Support for additional key exchange and transport algorithms
+- Support algorithm subtraction syntax in the `ssh_config` file
+- Support empty lines and comments in `known_hosts` file
-## Improvements
+### Initial macOS Big Sur Support
-* The `systemd_unit` resource now respects the `sensitive` property and will no longer output the contents of the unit file to logs if this is set.
-* A new `arm?` helper has been added which can be used in recipes and resources to determine if a system is on the ARM architecture.
+Chef Infra Client now correctly detects macOS Big Sur (11.0) beta as being platform "mac_os_x". Chef Infra Client 15.12 has not been fully qualified for macOS Big Sur, but we will continue to validate against this release and provide any additional support updates.
-## Bug Fixes
+### Platform Packages
-* Resolved a bug that prevented users from bootstrapping nodes using knife when specifying the `--use_sudo_password`.
-* Resolved a bug that prevented the `--bootstrap-version` flag from being honored when bootstrapping in knife.
+- Debian 8 packages are no longer being produced as Debian 8 is now end-of-life.
+- We now produce Windows 8 packages
-## Chef InSpec 4.18.104
+## What's New In 15.11
-* Resolved a regression that prevented the `service` resource from working correctly on Windows. Thanks [@Axuba](https://github.com/Axuba)
-* Implemented VMware and Hyper-V detection on Linux systems
-* Implemented VMware, Hyper-V, VirtualBox, KVM and Xen detection on Windows systems
-* Added helpers `virtual_system?` and `physical_system?`. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen)
+### Bootstrapping Bugfixes
-## Ohai 15.9
+This release of Chef Infra Client resolves multiple issues when using `knife bootstrap` to bootstrap new nodes to a Chef Infra Server:
-* Improve the resiliency of the `Shard` plugin when `dmidecode` cannot be found on a system. Thanks [@jaymzh](https://github.com/jaymzh)
-* Fixed detection of Openstack guests via DMI data. Thanks [@ramereth](https://github.com/ramereth)
+- Bootstrapping from a Windows host to a Linux host with an ED25519 ssh key no longer fails
+- Resolved failures in the Windows bootstrap script
+- Incorrect paths when bootstrapping Windows nodes have been resolved
-## Platform Support
+### Chef InSpec 4.18.114
-### Amazon Linux 2
+Chef InSpec was updated from 4.18.104 to 4.18.114 with the following improvements:
+
+- Added new `--reporter_message_truncation` and `--reporter_backtrace_inclusion` reporter options to truncate messages and suppress backtraces.
+- Fixed a warning when an input is provided
+- Inputs and controls can now have the same name
+
+### Resource Improvements
+
+#### windows_firewall
+
+The `windows_firewall` resource has been updated to support firewall rules that are associated with more than one profile. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen).
+
+#### chocolatey_package
+
+The `chocolatey_package` resource has been updated to properly handle quotes within the `options` property. Thanks for reporting this issue [@dave-q](https://github.com/dave-q).
+
+### Platform Support
+
+#### Additional aarch64 Builds
+
+Chef Infra Client is now tested on Debian 10, SLES 15, and Ubuntu 20.04 on the aarch64 architecture with packages available on the [Chef Downloads Page](https://downloads.chef.io/chef).
+
+### Security Updates
+
+#### openSSL
+
+openSSL has been updated from 1.0.2u to 1.0.2v which does not address any particular CVEs, but includes multiple security hardening updates.
+
+## What's New in 15.10
+
+### Improvements
+
+- The `systemd_unit` resource now respects the `sensitive` property and will no longer output the contents of the unit file to logs if this is set.
+- A new `arm?` helper has been added which can be used in recipes and resources to determine if a system is on the ARM architecture.
+
+### Bug Fixes
+
+- Resolved a bug that prevented users from bootstrapping nodes using knife when specifying the `--use_sudo_password`.
+- Resolved a bug that prevented the `--bootstrap-version` flag from being honored when bootstrapping in knife.
+
+### Chef InSpec 4.18.104
+
+- Resolved a regression that prevented the `service` resource from working correctly on Windows. Thanks [@Axuba](https://github.com/Axuba)
+- Implemented VMware and Hyper-V detection on Linux systems
+- Implemented VMware, Hyper-V, Virtualbox, KVM and Xen detection on Windows systems
+- Added helpers `virtual_system?` and `physical_system?`. Thanks [@tecracer-theinen](https://github.com/tecracer-theinen)
+
+### Ohai 15.9
+
+- Improve the resiliency of the `Shard` plugin when `dmidecode` cannot be found on a system. Thanks [@jaymzh](https://github.com/jaymzh)
+- Fixed detection of Openstack guests via DMI data. Thanks [@ramereth](https://github.com/ramereth)
+
+### Platform Support
+
+#### Amazon Linux 2
Chef Infra Client is now tested on Amazon Linux 2 running on x86_64 and aarch64 with packages available on the [Chef Downloads Page](https://downloads.chef.io/chef).
-# What's New in 15.9
+## What's New in 15.9
-## Chef InSpec 4.18.100
+### Chef InSpec 4.18.100
Chef InSpec has been updated from 4.18.85 to 4.18.100:
-* Resolved several failures in executing resources
-* Fixed `auditd` resource processing of action and list
-* Fixed platform detection when running in Habitat
-* "inspec schema" has been revised to be in the JSON Schema draft 7 format
-* Improved the functionality of the `oracledb_session` resource
+- Resolved several failures in executing resources
+- Fixed `auditd` resource processing of action and list
+- Fixed platform detection when running in Habitat
+- "inspec schema" has been revised to be in the JSON Schema draft 7 format
+- Improved the functionality of the `oracledb_session` resource
-## Ohai 15.8
+### Ohai 15.8
Ohai has been updated to 15.8.0 which includes a fix for failures that occurred in the OpenStack plugin (thanks [@sawanoboly](https://github.com/sawanoboly/)) and improved parsing of data in the `optional_plugins` config option (thanks [@salzig](https://github.com/salzig/)).
-## Resource Improvements
+### Resource Improvements
-### build_essential
+#### build_essential
The `build_essential` resource has been updated to better detect if the Xcode CLI Tools package needs to be installed on macOS. macOS 10.15 (Catalina) is now supported with this update. Thank you [@w0de](https://github.com/w0de/) for kicking this work off, [@jazaval](https://github.com/jazaval/) for advice on macOS package parsing, and Microsoft for their work in the macOS cookbook.
-### rhsm_errata / rhsm_errata_level
+#### rhsm_errata / rhsm_errata_level
The `rhsm_errata` and `rhsm_errata_level` resources have been updated to properly function on RHEL 8 systems.
-### rhsm_register
+#### rhsm_register
The `rhsm_register` resource has a new property `https_for_ca_consumer` that enables using https connections during registration. Thanks for this improvement [@jasonwbarnett](https://github.com/jasonwbarnett/). This resource has also been updated to properly function on RHEL 8.
-### windows_share
+#### windows_share
Resolved failures in the `windows_share` resource when setting the `path` property. Thanks for reporting this issue [@Kundan22](https://github.com/Kundan22/).
-## Platform Support
+### Platform Support
-### Ubuntu 20.04
+#### Ubuntu 20.04
Chef Infra Client is now tested on Ubuntu 20.04 (AMD64) with packages available on the [Chef Downloads Page](https://downloads.chef.io/chef).
-### Ubuntu 18.04 aarch64
+#### Ubuntu 18.04 aarch64
Chef Infra Client is now tested on Ubuntu 18.04 aarch64 with packages available on the [Chef Downloads Page](https://downloads.chef.io/chef).
-### Windows 10
+#### Windows 10
Our Windows 10 Chef Infra Client packages now receive an additional layer of testing to ensure they function as expected.
-## Security Updates
+### Security Updates
-### Ruby
+#### Ruby
Ruby has been updated from 2.6.5 to 2.6.6 to resolve the following CVEs:
-* [CVE-2020-16255](https://www.ruby-lang.org/en/news/2020/03/19/json-dos-cve-2020-10663/): Unsafe Object Creation Vulnerability in JSON (Additional fix)
-* [CVE-2020-10933](https://www.ruby-lang.org/en/news/2020/03/31/heap-exposure-in-socket-cve-2020-10933/): Heap exposure vulnerability in the socket library
+ - [CVE-2020-16255](https://www.ruby-lang.org/en/news/2020/03/19/json-dos-cve-2020-10663/): Unsafe Object Creation Vulnerability in JSON (Additional fix)
+ - [CVE-2020-10933](https://www.ruby-lang.org/en/news/2020/03/31/heap-exposure-in-socket-cve-2020-10933/): Heap exposure vulnerability in the socket library
-### libarchive
+#### libarchive
libarchive has been updated from 3.4.0 to 3.4.2 to resolve multiple security vulnerabilities including the following CVEs:
-* [CVE-2019-19221](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-19221): archive_wstring_append_from_mbs in archive_string.c has an out-of-bounds read because of an incorrect mbrtowc or mbtowc call
-* [CVE-2020-9308](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9308): archive_read_support_format_rar5.c in libarchive before 3.4.2 attempts to unpack a RAR5 file with an invalid or corrupted header
+ - [CVE-2019-19221](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-19221): archive_wstring_append_from_mbs in archive_string.c has an out-of-bounds read because of an incorrect mbrtowc or mbtowc call
+ - [CVE-2020-9308](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9308): archive_read_support_format_rar5.c in libarchive before 3.4.2 attempts to unpack a RAR5 file with an invalid or corrupted header
-# What's New in 15.8
+## What's New in 15.8
-## New notify_group functionality
+### New notify_group functionality
Chef Infra Client now includes a new `notify_group` feature that can be used to extract multiple common notifies out of individual resources to reduce duplicate code in your cookbooks and custom resources. Previously cookbook authors would often use a `log` resource to achieve a similar outcome, but using the log resource results in unnecessary Chef Infra Client log output. The `notify_group` method produces no additional logging, but fires all defined notifications when the `:run` action is set.
Example notify_group that stops, sleeps, and then starts service when a service config is updated:
```ruby
- service "crude" do
- action [ :enable, :start ]
- end
+service "crude" do
+ action [ :enable, :start ]
+end
- chef_sleep "60" do
- action :nothing
- end
+chef_sleep "60" do
+ action :nothing
+end
- notify_group "crude_stop_and_start" do
- notifies :stop, "service[crude]", :immediately
- notifies :sleep, "chef_sleep[60]", :immediately
- notifies :start, "service[crude]", :immediately
- end
+notify_group "crude_stop_and_start" do
+ notifies :stop, "service[crude]", :immediately
+ notifies :sleep, "chef_sleep[60]", :immediately
+ notifies :start, "service[crude]", :immediately
+end
- template "/etc/crude/crude.conf" do
- source "crude.conf.erb"
- variables node["crude"]
- notifies :run, "notify_group[crude_stop_and_start]", :immediately
- end
+template "/etc/crude/crude.conf" do
+ source "crude.conf.erb"
+ variables node["crude"]
+ notifies :run, "notify_group[crude_stop_and_start]", :immediately
+end
```
-## Chef InSpec 4.18.85
+### Chef InSpec 4.18.85
Chef InSpec has been updated from 4.18.39 to 4.18.85. This release includes a large number of bug fixes in addition to some great resource enhancements:
-* The service resource features new support for yocto-based linux distributions. Thank you to [@michaellihs](https://github.com/michaellihs) for this addition!
-* The package resource now includes support for FreeBSD. Thank you to [@fzipi](https://github.com/fzipi) for this work!
-* We standardized the platform for the etc_hosts, virtualization, ini, and xml resources.
-* The oracledb_session resource works again due to a missing quote fix.
-* The groups resource on macOS no longer reports duplicates anymore.
+- The service resource features new support for yocto-based linux distributions. Thank you to [@michaellihs](https://github.com/michaellihs) for this addition!
+- The package resource now includes support for FreeBSD. Thank you to [@fzipi](https://github.com/fzipi) for this work!
+- We standardized the platform for the etc_hosts, virtualization, ini, and xml resources.
+- The oracledb_session resource works again due to a missing quote fix.
+- The groups resource on macOS no longer reports duplicates anymore.
command.exist? now conforms to POSIX standards. Thanks to [@PiQuer](https://github.com/PiQuer)!
-* Changed the postfix_conf resource's supported platform to the broader unix. Thank you to [@fzipi](https://github.com/fzipi) for this fix!
+- Changed the postfix_conf resource's supported platform to the broader unix. Thank you to [@fzipi](https://github.com/fzipi) for this fix!
-## New Cookbook Helpers
+### New Cookbook Helpers
New helpers have been added to make writing cookbooks easier.
-### Platform Version Helpers
+#### Platform Version Helpers
New helpers for checking platform versions have been added. These helpers return parsed version strings so there's no need to convert the returned values to Integers or Floats before comparing them. Additionally, comparisons with version objects properly understand the order of versions so `5.11` will compare as larger than `5.9`, whereas converting those values to Floats would result in `5.9` being larger than `5.11`.
-* `windows_nt_version` returns the NT kernel version which often differs from Microsoft's marketing versions. This helper offers a good way to find desktop and server releases that are based on the same codebase. For example, NT 6.3 is both Windows 8.1 and Windows 2012 R2.
-* `powershell_version` returns the version of PowerShell installed on the system.
-* `platform_version` returns the value of node['platform_version'].
+- `windows_nt_version` returns the NT kernel version which often differs from Microsoft's marketing versions. This helper offers a good way to find desktop and server releases that are based on the same codebase. For example, NT 6.3 is both Windows 8.1 and Windows 2012 R2.
+- `powershell_version` returns the version of PowerShell installed on the system.
+- `platform_version` returns the value of node['platform_version'].
Example comparison using windows_nt_version:
@@ -1374,43 +2492,43 @@ if windows_nt_version >= 10
end
```
-### Cloud Helpers
+#### Cloud Helpers
The cloud helpers from chef-sugar have been ported to Chef Infra Client:
-* `cloud?` * if the node is running in any cloud, including internal clouds
-* `ec2?` * if the node is running in ec2
-* `gce?` * if the node is running in gce
-* `rackspace?` * if the node is running in rackspace
-* `eucalyptus?` * if the node is running under eucalyptus
-* `linode?` * if the node is running in linode
-* `openstack?` * if the node is running under openstack
-* `azure?` * if the node is running in azure
-* `digital_ocean?` * if the node is running in digital ocean
-* `softlayer?` * if the node is running in softlayer
+- `cloud?` - if the node is running in any cloud, including internal clouds
+- `ec2?` - if the node is running in ec2
+- `gce?` - if the node is running in gce
+- `rackspace?` - if the node is running in rackspace
+- `eucalyptus?` - if the node is running under eucalyptus
+- `linode?` - if the node is running in linode
+- `openstack?` - if the node is running under openstack
+- `azure?` - if the node is running in azure
+- `digital_ocean?` - if the node is running in digital ocean
+- `softlayer?` - if the node is running in softlayer
-### Virtualization Helpers
+#### Virtualization Helpers
The virtualization helpers from chef-sugar have been ported to Chef Infra Client and extended with helpers to detect hypervisor hosts, physical, and guest systems.
-* `kvm?` * if the node is a kvm guest
-* `kvm_host?` * if the node is a kvm host
-* `lxc?` * if the node is an lxc guest
-* `lxc_host?` * if the node is an lxc host
-* `parallels?`* if the node is a parallels guest
-* `parallels_host?`* if the node is a parallels host
-* `vbox?` * if the node is a virtualbox guest
-* `vbox_host?` * if the node is a virtualbox host
-* `vmware?` * if the node is a vmware guest
-* `vmware_host?` * if the node is a vmware host
-* `openvz?` * if the node is an openvz guest
-* `openvz_host?` * if the node is an openvz host
-* `guest?` * if the node is detected as any kind of guest
-* `hypervisor?` * if the node is detected as being any kind of hypervisor
-* `physical?` * the node is not running as a guest (may be a hypervisor or may be bare-metal)
-* `vagrant?` * attempts to identify the node as a vagrant guest (this check may be error-prone)
-
-### include_recipe? helper
+- `kvm?` - if the node is a kvm guest
+- `kvm_host?` - if the node is a kvm host
+- `lxc?` - if the node is an lxc guest
+- `lxc_host?` - if the node is an lxc host
+- `parallels?`- if the node is a parallels guest
+- `parallels_host?`- if the node is a parallels host
+- `vbox?` - if the node is a virtualbox guest
+- `vbox_host?` - if the node is a virtualbox host
+- `vmware?` - if the node is a vmware guest
+- `vmware_host?` - if the node is a vmware host
+- `openvz?` - if the node is an openvz guest
+- `openvz_host?` - if the node is an openvz host
+- `guest?` - if the node is detected as any kind of guest
+- `hypervisor?` - if the node is detected as being any kind of hypervisor
+- `physical?` - the node is not running as a guest (may be a hypervisor or may be bare-metal)
+- `vagrant?` - attempts to identify the node as a vagrant guest (this check may be error-prone)
+
+#### include_recipe? helper
chef-sugar's `include_recipe?` has been added to Chef Infra Client providing a simple way to see if a recipe has been included on a node already.
@@ -1423,17 +2541,17 @@ execute 'install my_app'
end
```
-## Updated Resources
+### Updated Resources
-### ifconfig
+#### ifconfig
The `ifconfig` resource now supports the newer `ifconfig` release that ships in Debian 10.
-### mac_user
+#### mac_user
The `mac_user` resource, used when creating a user on Mac systems, has been improved to work better with macOS Catalina (10.15). The resource now properly looks up the numeric GID when creating a user, once again supports the `system` property, and includes a new `hidden` property which prevents the user from showing on the login screen. Thanks [@chilcote](https://github.com/chilcote) for these fixes and improvements.
-### sysctl
+#### sysctl
The `sysctl` resource has been updated to allow the inclusion of descriptive comments. Comments may be passed as an array or as a string. Any comments provided are prefixed with '#' signs and precede the `sysctl` setting in generated files.
@@ -1467,164 +2585,164 @@ which results in `/etc/sysctl.d/99-chef-vm.swappiness.conf` as follows:
vm.swappiness = 10
```
-## Platform Support
+### Platform Support
-* Chef Infra Clients packages are now validated for Debian 10.
+- Chef Infra Clients packages are now validated for Debian 10.
-## macOS Binary Signing
+### macOS Binary Signing
Each binary in the macOS Chef Infra Client installation is now signed to improve the integrity of the installation and ensure compatibility with macOS Catalina security requirements.
-# What's New in 15.7
+## What's New in 15.7
-## Updated Resources
+### Updated Resources
-### archive_file
+#### archive_file
The `archive_file` resource will now only change ownership on files and directories that were part of the archive itself. This prevents changing permissions on important high level directories such as /etc or /bin when you extract a file into those directories. Thanks for this fix, [@bobchaos](https://github.com/bobchaos/).
-### cron and cron_d
+#### cron and cron_d
The `cron` and `cron_d` resources now include a `timeout` property, which allows you to configure actions to perform when a job times out. This property accepts a hash of timeout configuration options:
-* `preserve-status`: `true`/`false` with a default of `false`
-* `foreground`: `true`/`false` with a default of `false`
-* `kill-after`: `Integer` for the timeout in seconds
-* `signal`: `String` or `Integer` to send to the process such as `HUP`
+- `preserve-status`: `true`/`false` with a default of `false`
+- `foreground`: `true`/`false` with a default of `false`
+- `kill-after`: `Integer` for the timeout in seconds
+- `signal`: `String` or `Integer` to send to the process such as `HUP`
-### launchd
+#### launchd
The `launchd` resource has been updated to properly capitalize `HardResourceLimits`. Thanks for this fix, [@rb2k](https://github.com/rb2k/).
-### sudo
+#### sudo
The `sudo` resource no longer fails on the second Chef Infra Client run when using a `Cmnd_Alias`. Thanks for reporting this issue, [@Rudikza](https://github.com/Rudikza).
-### user
+#### user
The `user` resource on AIX no longer forces the user to change the password after Chef Infra Client modifies the password. Thanks for this fix, [@Triodes](https://github.com/Triodes).
The `user` resource on macOS 10.15 has received several important fixes to improve logging and prevent failures.
-### windows_task
+#### windows_task
The `windows_task` resource is now idempotent when a system is joined to a domain and the job runs under a local user account.
-### x509_certificate
+#### x509_certificate
The `x509_certificate` resource now includes a new `renew_before_expiry` property that allows you to auto renew certificates a specified number of days before they expire. Thanks [@julienhuon](https://github.com/julienhuon/) for this improvement.
-## Additional Recipe Helpers
+### Additional Recipe Helpers
We have added new helpers for identifying Windows releases that can be used in any part of your cookbooks.
-### windows_workstation?
+#### windows_workstation?
Returns `true` if the system is a Windows Workstation edition.
-### windows_server?
+#### windows_server?
Returns `true` if the system is a Windows Server edition.
-### windows_server_core?
+#### windows_server_core?
Returns `true` if the system is a Windows Server Core edition.
-## Notable Changes and Fixes
+### Notable Changes and Fixes
-* `knife upload` and `knife cookbook upload` will now generate a metadata.json file from metadata.rb when uploading a cookbook to the Chef Infra Server.
-* A bug in `knife bootstrap` behavior that caused failures when bootstrapping Windows hosts from non-Windows hosts and vice versa has been resolved.
-* The existing system path is now preserved when bootstrapping Windows nodes. Thanks for this fix, [@Xorima](https://github.com/Xorima/).
-* Ohai now properly returns the drive name on Windows and includes new drive_type fields to allow you to determine the type of attached disk. Thanks for this improvement [@sshock](https://github.com/sshock/).
-* Ohai has been updated to properly return DMI data to Chef Infra Client. Thanks for troubleshooting this, [@zmscwx](https://github.com/zmscwx) and [@Sliim](https://github.com/Sliim).
+- `knife upload` and `knife cookbook upload` will now generate a metadata.json file from metadata.rb when uploading a cookbook to the Chef Infra Server.
+- A bug in `knife bootstrap` behavior that caused failures when bootstrapping Windows hosts from non-Windows hosts and vice versa has been resolved.
+- The existing system path is now preserved when bootstrapping Windows nodes. Thanks for this fix, [@Xorima](https://github.com/Xorima/).
+- Ohai now properly returns the drive name on Windows and includes new drive_type fields to allow you to determine the type of attached disk. Thanks for this improvement [@sshock](https://github.com/sshock/).
+- Ohai has been updated to properly return DMI data to Chef Infra Client. Thanks for troubleshooting this, [@zmscwx](https://github.com/zmscwx) and [@Sliim](https://github.com/Sliim).
-## Platform Support
+### Platform Support
-* Chef Infra Clients packages are no longer produced for Windows 2008 R2 as this release reached its end of life on Jan 14th, 2020.
-* Chef Infra Client packages are no longer produced for RHEL 6 on the s390x platform. Builds will continue to be published for RHEL 7 on the s390x platform.
+- Chef Infra Clients packages are no longer produced for Windows 2008 R2 as this release reached its end of life on Jan 14th, 2020.
+- Chef Infra Client packages are no longer produced for RHEL 6 on the s390x platform. Builds will continue to be published for RHEL 7 on the s390x platform.
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2u to resolve [CVE-2019-1551](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551)
-# What's New in 15.6
+## What's New in 15.6
-## Updated Resources
+### Updated Resources
-## apt_repository
+### apt_repository
The `apt_repository` resource now properly escapes repository URIs instead of quoting them. This prevents failures when using the `apt-file` command, which was unable to parse the quoted URIs. Thanks for reporting this [@Seb-Solon](https://github.com/Seb-Solon)
-## file
+### file
The `file` resource now shows the output of any failures when running commands specified in the `verify` property. This means you can more easily validate config files before potentially writing an incorrect file to disk. Chef Infra Client will shellout to any specified command and will show the results of failures for further troubleshooting.
-## user
+### user
The `user` resource on Linux systems now continues successfully when `usermod` returns an exit code of 12. Exit code 12 occurs when a user's home directory is changed and the underlying directory already exists. Thanks [@skippyj](https://github.com/skippyj) for this fix.
-## yum_repository
+### yum_repository
The `yum_repository` now properly formats the repository configuration when multiple `baseurl` values are present. Thanks [@bugok](https://github.com/bugok) for this fix.
-## Performance Improvements
+### Performance Improvements
This release of Chef Infra Client ships with several optimizations to our Ruby installation to improve the performance of loading the chef-client and knife commands. These improvements are particularly noticeable on non-SSD hosts and on Windows.
-## Smaller Install Footprint
+### Smaller Install Footprint
We've further optimized our install footprint and reduced the size of `/opt/chef` by ~7% by removing unnecessary test files and libraries that shipped in previous releases.
-## filesystem2 Ohai Data on Windows
+### filesystem2 Ohai Data on Windows
Ohai 15.6 includes new `node['filesystem2']` data on Windows hosts. Filesystem2 presents filesystem data by both mountpoint and by device name. This data structure matches that of the filesystem plugin on Linux and other *nix operating systems. Thanks [@jaymzh](https://github.com/jaymzh) for this new data structure.
-# What's New in 15.5.15
+## What's New in 15.5.15
The Chef Infra Client 15.5.15 release includes fixes for two regressions. A regression in the `build_essential` resource caused failures on `rhel` platforms and a second regression caused Chef Infra Client to fail when starting with `enforce_path_sanity` enabled. As part of this fix we've added a new property, `raise_if_unsupported`, to the `build-essential` resource. Instead of silently continuing, this property will fail a Chef Infra Client run if an unknown platform is encountered.
We've also updated the `windows_package` resource. The resource will now provide better error messages if invalid options are passed to the `installer_type` property and the `checksum` property will now accept uppercase SHA256 checksums.
-# What's New in 15.5.9
+## What's New in 15.5.9
-## New Cookbook Helpers
+### New Cookbook Helpers
Chef Infra Client now includes a new `chef-utils` gem, which ships with a large number of helpers to make writing cookbooks easier. Many of these helpers existed previously in the `chef-sugar` gem. We have renamed many of the named helpers for consistency, while providing backwards compatibility with existing `chef-sugar` names. Existing cookbooks written with `chef-sugar` should work unmodified with any of these new helpers. Expect a Cookstyle rule in the near future to help you update existing `chef-sugar` code to use the newer built-in helpers.
For more information all all of the new helpers available, see the [chef-utils readme](https://github.com/chef/chef/blob/master/chef-utils/README.md)
-## Chefignore Improvements
+### Chefignore Improvements
We've reworked how chefignore files are handled in `knife`, which has allowed us to close out a large number of long outstanding bugs. `knife` will now traverse all the way up the directory structure looking for a chefignore file. This means you can place a chefignore file in each cookbook or any parent directory in your repository structure. Additionally, we have made fixes that ensure that commands like `knife diff` and `knife cookbook upload` always honor your chefignore files.
-## Windows Habitat Plan
+### Windows Habitat Plan
Official Habitat packages of Chef Infra Client are now available for Windows. It has all the executables of the traditional omnibus packages, but in Habitat form. You can find it in the Habitat Builder under [chef/chef-infra-client](https://bldr.habitat.sh/#/pkgs/chef/chef-infra-client/latest/windows).
-## Performance Improvements
+### Performance Improvements
This release of Chef Infra Client ships with several optimizations to our Ruby installation that improve the performance of the chef-client and knife commands, especially on Windows systems. Expect to see more here in future releases.
-## Chef InSpec 4.18.39
+### Chef InSpec 4.18.39
Chef InSpec has been updated from 4.17.17 to 4.18.38. This release includes a large number of bug fixes in addition to some great resource enhancements:
-* Inputs can now be used within a `describe.one` block
-* The `service` resource now includes a `startname` property for Windows and systemd services
-* The `interface` resource now includes a `name` property
-* The `user` resource now better supports Windows with the addition of `passwordage`, `maxbadpasswords`, and `badpasswordattempts` properties
-* The `nginx` resource now includes parsing support for wildcard, dot prefix, and regex
-* The `iis_app_pool` resource now handles empty app pools
-* The `filesystem` resource now supports devices with very long names
-* The `apt` better handles URIs and supports repos with an `arch`
-* The `oracledb_session` has received multiple fixes to make it work better
-* The `npm` resource now works under sudo on Unix and on Windows with a custom PATH
+- Inputs can now be used within a `describe.one` block
+- The `service` resource now includes a `startname` property for Windows and systemd services
+- The `interface` resource now includes a `name` property
+- The `user` resource now better supports Windows with the addition of `passwordage`, `maxbadpasswords`, and `badpasswordattempts` properties
+- The `nginx` resource now includes parsing support for wildcard, dot prefix, and regex
+- The `iis_app_pool` resource now handles empty app pools
+- The `filesystem` resource now supports devices with very long names
+- The `apt` better handles URIs and supports repos with an `arch`
+- The `oracledb_session` has received multiple fixes to make it work better
+- The `npm` resource now works under sudo on Unix and on Windows with a custom PATH
-## New Resources
+### New Resources
-### chef_sleep
+#### chef_sleep
The `chef_sleep` resource can be used to sleep for a specified number of seconds during a Chef Infra Client run. This may be helpful to use with other commands that return a completed status before they are actually ready. In general, do not use this resource unless you truly need it.
@@ -1643,52 +2761,52 @@ chef_sleep 'wait for service start' do
end
```
-## Updated Resources
+### Updated Resources
-## systemd_unit / service
+### systemd_unit / service
The `systemd_unit` and `service` resources (when on systemd) have been updated to not re-enable services with an indirect status. Thanks [@jaymzh](https://github.com/jaymzh) for this fix.
-## windows_firewall
+### windows_firewall
The `windows_firewall` resource has been updated to support passing in an array of profiles in the `profile` property. Thanks [@Happycoil](https://github.com/Happycoil) for this improvement.
-## Security Updates
+### Security Updates
-### libxslt
+#### libxslt
libxslt has been updated to 1.1.34 to resolve [CVE-2019-13118](https://nvd.nist.gov/vuln/detail/CVE-2019-13118).
-# What's New in 15.4
+## What's New in 15.4
-## converge_if_changed Improvements
+### converge_if_changed Improvements
Chef Infra Client will now take into account any `default` values specified in custom resources when making converge determinations with the `converge_if_changed` helper. Previously, default values would be ignored, which caused necessary changes to be skipped. Note: This change may cause behavior changes for some users, but we believe this original behavior is an impacting bug for enough users to make it outside of a major release. Thanks [@ jakauppila](https://github.com/jakauppila) for reporting this.
-## Bootstrap Improvements
+### Bootstrap Improvements
Several improvements have been made to the `knife bootstrap` command to make it more reliable and secure:
-* File creation is now wrapped in a umask to avoid potential race conditions
-* `NameError` and `RuntimeError` failures during bootstrap have been resolved
-* `Undefined method 'empty?' for nil:NilClass` during bootstrap have been resolved
-* Single quotes in attributes during bootstrap no longer result in bootstrap failures
-* The bootstrap command no longer appears in PS on the host while bootstrapping is running
+- File creation is now wrapped in a umask to avoid potential race conditions
+- `NameError` and `RuntimeError` failures during bootstrap have been resolved
+- `Undefined method 'empty?' for nil:NilClass` during bootstrap have been resolved
+- Single quotes in attributes during bootstrap no longer result in bootstrap failures
+- The bootstrap command no longer appears in PS on the host while bootstrapping is running
-## knife supermarket list Improvements
+### knife supermarket list Improvements
The `knife supermarket list` command now includes two new options:
-* `--sort-by [recently_updated recently_added most_downloaded most_followed]`: Sort cookbooks returned from the Supermarket API
-* `--owned_by`: Limit returned cookbooks to a particular owner
+- `--sort-by [recently_updated recently_added most_downloaded most_followed]`: Sort cookbooks returned from the Supermarket API
+- `--owned_by`: Limit returned cookbooks to a particular owner
-## Updated Resources
+### Updated Resources
-### chocolatey_package
+#### chocolatey_package
The `chocolatey_package` resource no longer fails when passing options with the `options` property. Thanks for reporting this issue [@kenmacleod](https://github.com/kenmacleod).
-### kernel_module
+#### kernel_module
The `kernel_module` resource includes a new `options` property, which allows users to set module specific parameters and settings. Thanks [@ramereth](https://github.com/ramereth) for this new feature.
@@ -1700,15 +2818,15 @@ Example of a kernel_module resource using the new options property:
end
```
-### remote_file
+#### remote_file
The `remote_file` resource has been updated to better display progress when using the `show_progress` resource. Thanks for reporting this issue [@isuftin](https://github.com/isuftin).
-### sudo
+#### sudo
The `sudo` resource now runs sudo config validation against all of the sudo configuration files on the system instead of only the file being written. This allows us to detect configuration errors that occur when configs conflict with each other. Thanks for reporting this issue [@drzewiec](https://github.com/drzewiec).
-### windows_ad_join
+#### windows_ad_join
The `windows_ad_join` has a new `:leave` action for leaving an Active Directory domain and rejoining a workgroup. This new action also has a new `workgroup_name` property for specifying the workgroup to join upon leaving the domain. Thanks [@jasonwbarnett](https://github.com/jasonwbarnett) for adding this new action.
@@ -1721,32 +2839,32 @@ windows_ad_join 'Leave the domain' do
end
```
-### windows_package
+#### windows_package
The `windows_package` resource no longer updates environmental variables before installing the package. This prevents potential modifications that may cause a package installation to fail. Thanks [@jeremyhage](https://github.com/jeremyhage) for this fix.
-### windows_service
+#### windows_service
The `windows_service` resource no longer updates the service and triggers notifications if the case of the `run_as_user` property does not match the user set on the service. Thanks [@jasonwbarnett](https://github.com/jasonwbarnett) for this fix.
-### windows_share
+#### windows_share
The `windows_share` resource is now fully idempotent by better validating the provided `path` property from the user. Thanks [@Happycoil](https://github.com/Happycoil) for this fix.
-## Security Updates
+### Security Updates
-### Ruby
+#### Ruby
Ruby has been updated from 2.6.4 to 2.6.5 in order to resolve the following CVEs:
-* [CVE-2019-16255](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16255): A code injection vulnerability of Shell#[] and Shell#test
-* [CVE-2019-16254](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16254): HTTP response splitting in WEBrick (Additional fix)
-* [CVE-2019-15845](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-15845): A NUL injection vulnerability of File.fnmatch and File.fnmatch?
-* [CVE-2019-16201](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16201): Regular Expression Denial of Service vulnerability of WEBrick's Digest access authentication
+- [CVE-2019-16255](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16255): A code injection vulnerability of Shell#[] and Shell#test
+- [CVE-2019-16254](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16254): HTTP response splitting in WEBrick (Additional fix)
+- [CVE-2019-15845](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-15845): A NUL injection vulnerability of File.fnmatch and File.fnmatch?
+- [CVE-2019-16201](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16201): Regular Expression Denial of Service vulnerability of WEBrick's Digest access authentication
-# What's New in 15.3
+## What's New in 15.3
-## Custom Resource Unified Mode
+### Custom Resource Unified Mode
Chef Infra Client 15.3 introduces an exciting new way to easily write custom resources that mix built-in Chef Infra resources with Ruby code. Previously custom resources would use Chef Infra's standard compile and converge phases, which meant that Ruby would be evaluated first and then the resources would be converged. This often results in confusing and undesirable behavior when you are trying to mix resources with Ruby logic. Many custom resource authors would attempt to get around this by forcing resources to run at compile time so that all the code in their resource would execute during the compile phase.
@@ -1770,39 +2888,39 @@ action :create do
end
```
-## Interval Mode Now Fails on Windows
+### Interval Mode Now Fails on Windows
Chef Infra Client 15.3 will now raise an error if you attempt to keep the chef-client process running long-term by enabling interval runs. Interval runs have already raised failures on non-Windows platforms and we've suggested that users move away from them on Windows for many years. The long-running chef-client process on Windows will load and reload cookbooks over each other in memory. This could produce a running state which is not a representation of the cookbook code that the authors wrote or tested, and behavior that may be wildly different depending on how long the chef-client process has been running and on the sequence that the cookbooks were uploaded.
-## Updated Resources
+### Updated Resources
-### ifconfig
+#### ifconfig
The `ifconfig` resource has been updated to properly support interfaces with a hyphen in their name. This is most commonly encountered with bridge interfaces that are named `br-1234`.
-### archive_file
+#### archive_file
The `archive_file` resource now supports archives in the RAR 5.0 format as well as zip files compressed using xz, lzma, ppmd8 and bzip2 compression.
-### user
+#### user
-#### macOS 10.14 / 10.15 support
+**macOS 10.14 / 10.15 support**
The `user` resource now supports the creation of users on macOS 10.14 and 10.15 systems. The updated resource now complies with macOS TCC policies by using a user with admin privileges to create and modify users. The following new properties have been added for macOS user creation:
-* `admin` sets a user to be an admin.
+- `admin` sets a user to be an admin.
-* `admin_username` and `admin_password` define the admin user credentials required for toggling SecureToken for a user. The value of 'admin_username' must correspond to a system user that is part of the 'admin' with SecureToken enabled in order to toggle SecureToken.
+- `admin_username` and `admin_password` define the admin user credentials required for toggling SecureToken for a user. The value of 'admin_username' must correspond to a system user that is part of the 'admin' with SecureToken enabled in order to toggle SecureToken.
-* `secure_token` is a boolean property that sets the desired state for SecureToken. FileVault requires a SecureToken for full disk encryption.
+- `secure_token` is a boolean property that sets the desired state for SecureToken. FileVault requires a SecureToken for full disk encryption.
-* `secure_token_password` is the plaintext password required to enable or disable `secure_token` for a user. If no salt is specified we assume the 'password' property corresponds to a plaintext password and will attempt to use it in place of secure_token_password if it is not set.
+- `secure_token_password` is the plaintext password required to enable or disable `secure_token` for a user. If no salt is specified we assume the 'password' property corresponds to a plaintext password and will attempt to use it in place of secure_token_password if it is not set.
-#### Password property is now sensitive
+**Password property is now sensitive**
The `password` property is now set to sensitive to prevent the password from being shown in debug or failure logs.
-#### gid property can now be a string
+**gid property can now be a string**
The `gid` property now allows specifying the user's gid as a string. For example:
@@ -1812,197 +2930,199 @@ user 'tim' do
end
```
-## Platform Support Updates
+### Platform Support Updates
-### macOS 10.15 Support
+#### macOS 10.15 Support
Chef Infra Client is now validated against macOS 10.15 (Catalina) with packages now available at [downloads.chef.io](https://downloads.chef.io/) and via the [Omnitruck API](https://docs.chef.io/api_omnitruck/). Additionally, Chef Infra Client will no longer be validated against macOS 10.12.
-### AIX 7.2
+#### AIX 7.2
Chef Infra Client is now validated against AIX 7.2 with packages now available at [downloads.chef.io](https://downloads.chef.io/) and via the [Omnitruck API](https://docs.chef.io/api_omnitruck/).
-## Chef InSpec 4.16
+### Chef InSpec 4.16
Chef InSpec has been updated from 4.10.4 to 4.16.0 with the following changes:
-* A new `postfix_conf` has been added for inspecting Postfix configuration files.
-* A new `plugins` section has been added to the InSpec configuration file which can be used to pass secrets or other configurations into Chef InSpec plugins.
-* The `service` resource now includes a new `startname` property for determining which user is starting the Windows services.
-* The `groups` resource now properly gathers membership information on macOS hosts.
+- A new `postfix_conf` has been added for inspecting Postfix configuration files.
+- A new `plugins` section has been added to the InSpec configuration file which can be used to pass secrets or other configurations into Chef InSpec plugins.
+- The `service` resource now includes a new `startname` property for determining which user is starting the Windows services.
+- The `groups` resource now properly gathers membership information on macOS hosts.
-## Security Updates
+### Security Updates
-### Ruby
+#### Ruby
Ruby has been updated from 2.6.3 to 2.6.4 in order to resolve [CVE-2012-6708](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-6708) and [CVE-2015-9251](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251).
-### openssl
+#### openssl
openssl has been updated from 1.0.2s to 1.0.2t in order to resolve [CVE-2019-1563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563) and [CVE-2019-1547](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1547).
-### nokogiri
+#### nokogiri
nokogiri has been updated from 1.10.2 to 1.10.4 in order to resolve [CVE-2019-5477](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5477)
-# What's New in 15.2
+## What's New in 15.2
-## Updated Resources
+### Updated Resources
-### dnf_package
+#### dnf_package
The `dnf_package` resource has been updated to fully support RHEL 8.
-### kernel_module
+#### kernel_module
The `kernel_module` now supports a `:disable` action. Thanks [@tomdoherty](https://github.com/tomdoherty).
-### rhsm_repo
+#### rhsm_repo
The `rhsm_repo` resource has been updated to support passing a repo name of `*` in the `:disable` action. Thanks for reporting this issue [@erinn](https://github.com/erinn).
-### windows_task
+#### windows_task
The `windows_task` resource has been updated to allow the `day` property to accept an `Integer` value.
-### zypper_package
+#### zypper_package
The `zypper_package` package has been updated to properly upgrade packages if necessary based on the version specified in the resource block. Thanks [@foobarbam](https://github.com/foobarbam) for this fix.
-## Platform Support Updates
+### Platform Support Updates
-### RHEL 8 Support Added
+#### RHEL 8 Support Added
Chef Infra Client 15.2 now includes native packages for RHEL 8 with all builds now validated on RHEL 8 hosts.
-### SLES 11 EOL
+#### SLES 11 EOL
Packages will no longer be built for SUSE Linux Enterprise Server (SLES) 11 as SLES 11 exited the 'General Support' phase on March 31, 2019. See Chef's [Platform End-of-Life Policy](https://docs.chef.io/platforms/#platform-end-of-life-policy) for more information on when Chef ends support for an OS release.
-### Ubuntu 14.04 EOL
+#### Ubuntu 14.04 EOL
Packages will no longer be built for Ubuntu 14.04 as Canonical ended maintenance updates on April 30, 2019. See Chef's [Platform End-of-Life Policy](https://docs.chef.io/platforms/#platform-end-of-life-policy) for more information on when Chef ends support for an OS release.
-## Ohai 15.2
+### Ohai 15.2
Ohai has been updated to 15.2 with the following changes:
- * Improved detection of Openstack including proper detection of Windows nodes running on Openstack when fetching metadata. Thanks [@jjustice6](https://github.com/jjustice6).
- * A new `other_versions` field has been added to the Packages plugin when the node is using RPM. This allows you to see all installed versions of packages, not just the latest version. Thanks [@jjustice6](https://github.com/jjustice6).
- * The Linux Network plugin has been improved to not mark interfaces down if `stp_state` is marked as down. Thanks [@josephmilla](https://github.com/josephmilla).
- * Arch running on ARM processors is now detected as the `arm` platform. Thanks [@BackSlasher](https://github.com/BackSlasher).
-## Chef InSpec 4.10.4
+- Improved detection of Openstack including proper detection of Windows nodes running on Openstack when fetching metadata. Thanks [@jjustice6](https://github.com/jjustice6).
+- A new `other_versions` field has been added to the Packages plugin when the node is using RPM. This allows you to see all installed versions of packages, not just the latest version. Thanks [@jjustice6](https://github.com/jjustice6).
+- The Linux Network plugin has been improved to not mark interfaces down if `stp_state` is marked as down. Thanks [@josephmilla](https://github.com/josephmilla).
+- Arch running on ARM processors is now detected as the `arm` platform. Thanks [@BackSlasher](https://github.com/BackSlasher).
+
+### Chef InSpec 4.10.4
Chef InSpec has been updated from 4.6.4 to 4.10.4 with the following changes:
-* Fix handling multiple triggers in the `windows_task` resource
-* Fix exceptions when resources are used with incompatible transports
-* Un-deprecate the `be_running` matcher on the `service` resource
-* Add resource `sys_info.manufacturer` and `sys_info.model`
-* Add `ip6tables` resource
+- Fix handling multiple triggers in the `windows_task` resource
+- Fix exceptions when resources are used with incompatible transports
+- Un-deprecate the `be_running` matcher on the `service` resource
+- Add resource `sys_info.manufacturer` and `sys_info.model`
+- Add `ip6tables` resource
-## Security Updates
+### Security Updates
-### bzip2
+#### bzip2
bzip2 has been updated from 1.0.6 to 1.0.8 to resolve [CVE-2016-3189](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-3189) and [CVE-2019-12900](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12900).
-# What's New in 15.1
+## What's New in 15.1
-## New Resources
+### New Resources
-### chocolatey_feature
+#### chocolatey_feature
The `chocolatey_feature` resource allows you to enable and disable Chocolatey features. See the [chocolatey_feature documentation](https://docs.chef.io/resources/chocolatey_feature/) for full usage information. Thanks [@gep13](https://github.com/gep13) for this new resource.
-## Updated Resources
+### Updated Resources
-### chocolatey_source
+#### chocolatey_source
The `chocolatey_source` resource has been updated with new `enable` and `disable` actions, as well as `admin_only` and `allow_self_service` properties. Thanks [@gep13](https://github.com/gep13) for this enhancement.
-### launchd
+#### launchd
The `launchd` resource has been updated with a new `launch_events` property, which allows you to specify higher-level event types to be used as launch-on-demand event sources. Thanks [@chilcote](https://github.com/chilcote) for this enhancement.
-### yum_package
+#### yum_package
The `yum_package` resource's helper for interacting with the yum subsystem has been updated to always close out the rpmdb lock, even during failures. This may prevent the rpmdb becoming locked in some rare conditions. Thanks for reporting this issue, [@lytao](https://github.com/lytao).
-### template
+#### template
The `template` resource now provides additional information on failures, which is especially useful in ChefSpec tests. Thanks [@brodock](https://github.com/brodock) for this enhancement.
-## Target Mode Improvements
+### Target Mode Improvements
Our experimental Target Mode received a large number of updates in Chef Infra Client 15.1. Target Mode now reuses the connection to the remote system, which greatly speeds up the remote Chef Infra run. There is also now support for Target Mode in the `systemd_unit`, `log`, `ruby_block`, and `breakpoint` resources. Keep in mind that when using `ruby_block` with Target Mode that the Ruby code in the block will execute locally as there is not necessarily a Ruby runtime on the remote host.
-## Ohai 15.1
+### Ohai 15.1
Ohai has been updated to 15.1 with the following changes:
- * The `Shard` plugin properly uses the machine's `machinename`, `serial`, and `uuid` attributes to generate the shard value. The plugin also no longer throws an exception on macOS hosts. Thanks [@michel-slm](https://github.com/michel-slm) for these fixes.
- * The `Virtualbox` plugin has been enhanced to gather information on running guests, storage, and networks when VirtualBox is installed on a node. Thanks [@freakinhippie](https://github.com/freakinhippie) for this new capability.
- * Ohai no longer fails to gather interface information on Solaris in some rare conditions. Thanks [@devoptimist](https://github.com/devoptimist) for this fix.
+- The `Shard` plugin properly uses the machine's `machinename`, `serial`, and `uuid` attributes to generate the shard value. The plugin also no longer throws an exception on macOS hosts. Thanks [@michel-slm](https://github.com/michel-slm) for these fixes.
+- The `Virtualbox` plugin has been enhanced to gather information on running guests, storage, and networks when VirtualBox is installed on a node. Thanks [@freakinhippie](https://github.com/freakinhippie) for this new capability.
+- Ohai no longer fails to gather interface information on Solaris in some rare conditions. Thanks [@devoptimist](https://github.com/devoptimist) for this fix.
-## Chef InSpec 4.6.4
+### Chef InSpec 4.6.4
Chef InSpec has been updated from 4.3.2 to 4.6.4 with the following changes:
- * InSpec `Attributes` have now been renamed to `Inputs` to avoid confusion with Chef Infra attributes.
- * A new InSpec plugin type of `Input` has been added for defining new input types. See the [InSpec Plugins documentation](https://github.com/inspec/inspec/blob/master/docs/dev/plugins.md#implementing-input-plugins) for more information on writing these plugins.
- * InSpec no longer prints errors to the stdout when passing `--format json`.
- * When fetching profiles from GitHub, the URL can now include periods.
- * The performance of InSpec startup has been improved.
+- InSpec `Attributes` have now been renamed to `Inputs` to avoid confusion with Chef Infra attributes.
+- A new InSpec plugin type of `Input` has been added for defining new input types. See the [InSpec Plugins documentation](https://github.com/inspec/inspec/blob/master/docs/dev/plugins.md#implementing-input-plugins) for more information on writing these plugins.
+- InSpec no longer prints errors to the stdout when passing `--format json`.
+- When fetching profiles from GitHub, the URL can now include periods.
+- The performance of InSpec startup has been improved.
-# What's New in 15.0.300
+## What's New in 15.0.300
This release includes critical bugfixes for the 15.0 release:
- * Fix `knife bootstrap` over SSH when `requiretty` is configured on the host.
- * Added the `--chef-license` CLI flag to `chef-apply` and `chef-solo` commands.
+- Fix `knife bootstrap` over SSH when `requiretty` is configured on the host.
+- Added the `--chef-license` CLI flag to `chef-apply` and `chef-solo` commands.
-# What's New in 15.0.298
+## What's New in 15.0.298
This release includes critical bugfixes for the 15.0 release:
- * Allow accepting the license on non-interactive Windows sessions
- * Resolve license acceptance failures on Windows 2012 R2
- * Improve some `knife` and `chef-client` help text
- * Properly handle session_timeout default value in `knife bootstrap`
- * Avoid failures due to Train::Transports::SSHFailed class not being loaded in `knife bootstrap`
- * Resolve failures using the ca_trust_file option with `knife bootstrap`
-# What's New in 15.0.293
+- Allow accepting the license on non-interactive Windows sessions
+- Resolve license acceptance failures on Windows 2012 R2
+- Improve some `knife` and `chef-client` help text
+- Properly handle session_timeout default value in `knife bootstrap`
+- Avoid failures due to Train::Transports::SSHFailed class not being loaded in `knife bootstrap`
+- Resolve failures using the ca_trust_file option with `knife bootstrap`
-## Chef Client is now Chef Infra Client
+## What's New in 15.0.293
+
+### Chef Client is now Chef Infra Client
Chef Client has a new name, but don't worry, it's the same Chef Client you've grown used to. You'll notice new branding throughout the application, help, and documentation but the command line name of `chef-client` remains the same.
-## Chef EULA
+### Chef EULA
Chef Infra Client requires an EULA to be accepted by users before it can run. Users can accept the EULA in a variety of ways:
-* `chef-client --chef-license accept`
-* `chef-client --chef-license accept-no-persist`
-* `CHEF_LICENSE="accept" chef-client`
-* `CHEF_LICENSE="accept-no-persist" chef-client`
+- `chef-client --chef-license accept`
+- `chef-client --chef-license accept-no-persist`
+- `CHEF_LICENSE="accept" chef-client`
+- `CHEF_LICENSE="accept-no-persist" chef-client`
Finally, if users run `chef-client` without any of these options, they will receive an interactive prompt asking for license acceptance. If the license is accepted, a marker file will be written to the filesystem unless `accept-no-persist` is specified. Once this marker file is persisted, users no longer need to set any of these flags.
See our [Frequently Asked Questions document](https://www.chef.io/bmc-faq/) for more information on the EULA and license acceptance.
-## New Features / Functionality
+### New Features / Functionality
-### Target Mode Prototype
+#### Target Mode Prototype
Chef Infra Client 15 adds a prototype for a new method of executing resources called Target Mode. Target Mode allows a Chef Infra Client run to manage a remote system over SSH or another protocol supported by the Train library. This support includes platforms that we currently support like Ubuntu Linux, but also allows for configuring other architectures and platforms, such as switches that do not have native builds of Chef Infra Client. Target Mode maintains a separate node object for each target and allows you to manage that node using existing patterns that you currently use.
As of this release, only the `execute` resource and guards are supported, but modifying existing resources or writing new resources to support Target Mode is relatively easy. Using Target Mode is as easy as running `chef-client --target hostname`. The authentication credentials should be stored in your local `~/.chef/credentials` file with the hostname of the target node as the profile name. Each key/value pair is passed to Train for authentication.
-### Data Collection Ground-Up Refactor
+#### Data Collection Ground-Up Refactor
Chef Infra Client's Data Collection subsystem is used to report node changes during client runs to Chef Automate or other reporting systems. For Chef Infra Client 15, we performed a ground-up rewrite of this subsystem, which greatly improves the data reported to Chef Automate and ensures data is delivered even in the toughest of failure conditions.
-### copy_properties_from in Custom Resources
+#### copy_properties_from in Custom Resources
A new `copy_properties_from` method for custom resources allows you copy properties from your custom resource into other resources you are calling, so you can avoid unnecessarily repeating code.
@@ -2037,79 +3157,79 @@ directory '/etc/myapp' do
end
```
-### ed25519 SSH key support
+#### ed25519 SSH key support
Our underlying SSH implementation has been updated to support the new ed25519 SSH key format. This means you will be able to use `knife bootstrap` and `knife ssh` on hosts that only support this new key format.
-### Allow Using --delete-entire-chef-repo in Chef Local Mode
+#### Allow Using --delete-entire-chef-repo in Chef Local Mode
Chef Solo's `--delete-entire-chef-repo` option has been extended to work in Local Mode as well. Be warned that this flag does exactly what it states, and when used incorrectly, can result in loss of work.
-## New Resources
+### New Resources
-### archive_file resource
+#### archive_file resource
Use the `archive_file` resource to decompress multiple archive formats without the need for compression tools on the host.
See the [archive_file](https://docs.chef.io/resources/archive_file/) documentation for more information.
-### windows_uac resource
+#### windows_uac resource
Use the `windows_uac` resource to configure UAC settings on Windows hosts.
See the [windows_uac](https://docs.chef.io/resources/windows_uac) documentation for more information.
-### windows_dfs_folder resource
+#### windows_dfs_folder resource
Use the `windows_dfs_folder` resource to create and delete Windows DFS folders.
See the [windows_dfs_folder](https://docs.chef.io/resources/windows_dfs_folder) documentation for more information.
-### windows_dfs_namespace resources
+#### windows_dfs_namespace resources
Use the `windows_dfs_namespace` resource to create and delete Windows DFS namespaces.
See the [windows_dfs_namespace](https://docs.chef.io/resources/windows_dfs_namespace) documentation for more information.
-### windows_dfs_server resources
+#### windows_dfs_server resources
Use the `windows_dfs_server` resource to configure Windows DFS server settings.
See the [windows_dfs_server](https://docs.chef.io/resources/windows_dfs_server) documentation for more information.
-### windows_dns_record resource
+#### windows_dns_record resource
Use the `windows_dns_record` resource to create or delete DNS records.
See the [windows_dns_record](https://docs.chef.io/resources/windows_dns_record) documentation for more information.
-### windows_dns_zone resource
+#### windows_dns_zone resource
Use the `windows_dns_zone` resource to create or delete DNS zones.
See the [windows_dns_zone](https://docs.chef.io/resources/windows_dns_zone) documentation for more information.
-### snap_package resource
+#### snap_package resource
Use the `snap_package` resource to install snap packages on Ubuntu hosts.
See the [snap_package](https://docs.chef.io/resources/snap_package) documentation for more information.
-## Resource Improvements
+### Resource Improvements
-### windows_task
+#### windows_task
The `windows_task` resource now supports the Start When Available option with a new `start_when_available` property.
-### locale
+#### locale
The `locale` resource now allows setting all possible LC_* environmental variables.
-### directory
+#### directory
The `directory` resource now property supports passing `deny_rights :write` on Windows nodes.
-### windows_service
+#### windows_service
The `windows_service` resource has been improved to prevent accidentally reverting a service back to default settings in a subsequent definition.
@@ -2131,47 +3251,47 @@ windows_service 'MyApp' do
end
```
-### Ruby 2.6.3
+#### Ruby 2.6.3
Chef now ships with Ruby 2.6.3. This new version of Ruby improves performance and includes many new features to make more advanced Chef usage easier. See <https://www.rubyguides.com/2018/11/ruby-2-6-new-features/> for a list of some of the new functionality.
-## Ohai Improvements
+### Ohai Improvements
-### Improved Linux Platform / Platform Family Detection
+#### Improved Linux Platform / Platform Family Detection
`Platform` and `platform_family` detection on Linux has been rewritten to utilize the latest config files on modern Linux distributions before falling back to slower and fragile legacy detection methods. Ohai will now begin by parsing the contents of `/etc/os-release` for OS information if available. This feature improves the reliability of detection on modern distros and allows detection of new distros as they are released.
With this change, we now detect `sles_sap` as a member of the `suse` `platform_family`. Additionally, this change corrects our detection of the `platform_version` on Cisco Nexus switches where previously the build number was incorrectly appended to the version string.
-### Improved Virtualization Detection
+#### Improved Virtualization Detection
Hypervisor detection on multiple platforms has been updated to use DMI data and a single set of hypervisors. This greatly improves the detection of hypervisors on Windows, BSD and Solaris platforms. It also means that as new hypervisor detection is added in the future, we will automatically support the majority of platforms.
-### Fix Windows 2016 FQDN Detection
+#### Fix Windows 2016 FQDN Detection
Ohai 14 incorrectly detected a Windows 2016 node's `fqdn` as the node's `hostname`. Ohai 15 now correctly reports the FQDN value.
-### Improved Memory Usage
+#### Improved Memory Usage
Ohai now uses less memory due to internal optimization of how we track plugin information.
-### FIPS Detection Improvements
+#### FIPS Detection Improvements
The FIPS plugin now uses the built-in FIPS detection in Ruby for improved detection.
-## New Deprecations
+### New Deprecations
-### knife cookbook site deprecated in favor of knife supermarket
+#### knife cookbook site deprecated in favor of knife supermarket
The `knife cookbook site` command has been deprecated in favor of the `knife supermarket` command. `knife cookbook site` will now produce a warning message. In Chef Infra Client 16, we will remove the `knife cookbook site` command entirely.
-### locale LC_ALL property
+#### locale LC_ALL property
The `LC_ALL` property in the `locale` resource has been deprecated as the usage of this environmental variable is not recommended by distribution maintainers.
-## Breaking Changes
+### Breaking Changes
-### Knife Bootstrap
+#### Knife Bootstrap
Knife bootstrap has been entirely rewritten. Native support for Windows bootstrapping is now a part of the main `knife bootstrap` command. This marks the deprecation of the `knife-windows` plugin's `bootstrap` behavior. This change also addresses [CVE-2015-8559](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-8559): *The `knife bootstrap` command in chef leaks the validator.pem private RSA key to /var/log/messages*.
@@ -2179,7 +3299,7 @@ Knife bootstrap has been entirely rewritten. Native support for Windows bootstra
In order to accommodate a combined bootstrap that supports both SSH and WinRM, some CLI flags have been added, removed, or changed. Using the changed options will result in deprecation warnings, but `knife bootstrap` will accept those options unless otherwise noted. Using removed options will cause the command to fail.
-#### New Flags
+**New Flags**
| Flag | Description |
|-----:|:------------|
@@ -2191,7 +3311,7 @@ In order to accommodate a combined bootstrap that supports both SSH and WinRM, s
| --connection-port | Port to connect to, regardless of protocol. |
| --ssh-verify-host-key VALUE | Verify host key. Default is 'always'. Valid values are 'accept', 'accept\_new', 'accept\_new\_or\_local\_tunnel', and 'never'. |
-#### Changed Flags
+**Changed Flags**
| Flag | New Option | Notes |
|-----:|:-----------|:------|
@@ -2213,7 +3333,7 @@ In order to accommodate a combined bootstrap that supports both SSH and WinRM, s
[1] These flags do not have an automatic mapping of old flag -> new flag. The new flag must be used.
-#### Removed Flags
+**Removed Flags**
| Flag | Notes |
|-----:|:------|
@@ -2222,18 +3342,18 @@ In order to accommodate a combined bootstrap that supports both SSH and WinRM, s
|--winrm-shell| This option was ignored for bootstrap. |
|--install-as-service| Installing Chef Client as a service is not supported. |
-#### Usage Changes
+**Usage Changes**
Instead of specifying protocol with `-o`, it is also possible to prefix the target hostname with the protocol in URL format. For example:
```
- knife bootstrap example.com -o ssh
- knife bootstrap ssh://example.com
- knife bootstrap example.com -o winrm
- knife bootstrap winrm://example.com
+knife bootstrap example.com -o ssh
+knife bootstrap ssh://example.com
+knife bootstrap example.com -o winrm
+knife bootstrap winrm://example.com
```
-### Chef Infra Client packages remove /opt/chef before installation
+#### Chef Infra Client packages remove /opt/chef before installation
Upon upgrading Chef Infra Client packages, the `/opt/chef` directory is removed. This ensures any `chef_gem` installed gem versions and other modifications to `/opt/chef` will removed to prevent upgrade issues. Due to technical details with rpm script execution order, the implementation involves a a pre-installation script that wipes `/opt/chef` before every install, and is done consistently this way on every package manager.
@@ -2241,11 +3361,11 @@ Users who are properly managing customizations to `/opt/chef` through Chef recip
You will see a warning that the `/opt/chef` directory will be removed during the package installation process.
-### powershell_script now allows overriding the default flags
+#### powershell_script now allows overriding the default flags
We now append `powershell_script` user flags to the default flags rather than the other way around, which made user flags override the defaults. This is the correct behavior, but it may cause scripts to execute differently than in previous Chef Client releases.
-### Package provider allow_downgrade is now true by default
+#### Package provider allow_downgrade is now true by default
We reversed the default behavior to `allow_downgrade true` for our package providers. To override this setting to prevent downgrades, use the `allow_downgrade false` flag. This behavior change will mostly affect users of the rpm and zypper package providers.
@@ -2266,7 +3386,7 @@ package "foo" do
end
```
-### Node Attributes deep merge nil values
+#### Node Attributes deep merge nil values
Writing a `nil` to a precedence level in the node object now acts like any other value and can be used to override values back to `nil`.
@@ -2283,43 +3403,43 @@ chef (15.0.53)> node["foo"]
In prior versions of `chef-client`, the `nil` set in the override level would be completely ignored and the value of `node["foo"]` would have been "bar".
-### http_disable_auth_on_redirect now enabled
+#### http_disable_auth_on_redirect now enabled
The Chef config ``http_disable_auth_on_redirect`` has been changed from `false` to `true`. In Chef Infra Client 16, this config option will be removed altogether and Chef Infra Client will always disable auth on redirect.
-### knife cookbook test removal
+#### knife cookbook test removal
The `knife cookbook test` command has been removed. This command would often report non-functional cookbooks as functional, and has been superseded by functionality in other testing tools such as `cookstyle`, `foodcritic`, and `chefspec`.
-### ohai resource's ohai_name property removal
+#### ohai resource's ohai_name property removal
The `ohai` resource contained a non-functional `ohai_name` property, which has been removed.
-### knife status --hide-healthy flag removal
+#### knife status --hide-healthy flag removal
The `knife status --hide-healthy` flag has been removed. Users should run `knife status --hide-by-mins MINS` instead.
-### Cookbook shadowing in Chef Solo Legacy Mode Removed
+#### Cookbook shadowing in Chef Solo Legacy Mode Removed
Previously, if a user provided multiple cookbook paths to Chef Solo that contained cookbooks with the same name, Chef Solo would combine these into a single cookbook. This merging of two cookbooks often caused unexpected outcomes and has been removed.
-### Removal of unused route resource properties
+#### Removal of unused route resource properties
The `route` resource contained multiple unused properties that have been removed. If you previously set `networking`, `networking_ipv6`, `hostname`, `domainname`, or `domain`, they would be ignored. In Chef Infra Client 15, setting these properties will throw an error.
-### FreeBSD pkg provider removal
+#### FreeBSD pkg provider removal
Support for the FreeBSD `pkg` package system in the `freebsd_package` resource has been removed. FreeBSD 10 replaced the `pkg` system with `pkg-ng` system, so this removal only impacts users of EOL FreeBSD releases.
-### require_recipe removal
+#### require_recipe removal
The legacy `require_recipe` method in recipes has been removed. This method was replaced with `include_recipe` in Chef Client 10, and a FoodCritic rule has been warning to update cookbooks for multiple years.
-### Legacy shell_out methods removed
+#### Legacy shell_out methods removed
In Chef Client 14, many of the more obscure `shell_out` methods used in LWRPs and custom resources were combined into the standard `shell_out` and `shell_out!` methods. The legacy methods were infrequently used and Chef Client 14/Foodcritic both contained deprecation warnings for these methods. The following methods will now throw an error: `shell_out_compact`, `shell_out_compact!`, `shell_out_compact_timeout`, `shell_out_compact_timeout!`, `shell_out_with_systems_locale`, and `shell_out_with_systems_locale!`.
-### knife bootstrap --identity_file removal
+#### knife bootstrap --identity_file removal
The `knife bootstrap --identity_file` flag has been removed. This flag was deprecated in Chef Client 12, and users should now use the `--ssh-identity-file` flag instead.
@@ -2327,11 +3447,11 @@ The `knife bootstrap --identity_file` flag has been removed. This flag was depre
The `knife user` command no longer supports the open source Chef Infra Server version prior to 12.
-### attributes in metadata.rb
+#### attributes in metadata.rb
Chef Infra Client no longer processes attributes in the `metadata.rb` file. Attributes could be defined in the `metadata.rb` file as a form of documentation, which would be shown when running `knife cookbook show COOKBOOK_NAME`. Often, these attribute definitions would become out of sync with the attributes in the actual attributes files. Chef Infra Client 15 will no longer show these attributes when running `knife cookbook show COOKBOOK_NAME` and will instead throw a warning message upon upload. Foodcritic has warned against the use of attributes in the `metadata.rb` file since April 2017.
-### Node attributes array bugfix
+#### Node attributes array bugfix
Chef Infra Client 15 includes a bugfix for incorrect node attribute behavior involving a rare usage of arrays, which may impact users who depend on the incorrect behavior.
@@ -2352,143 +3472,143 @@ node.default["foo"][0][:bar] # does not work due to the sub-Hash not
The new behavior uses a Mash so that the attributes will work as expected.
-### Ohai's system_profile plugin for macOS removed
+#### Ohai's system_profile plugin for macOS removed
We removed the `system_profile` plugin because it incorrectly returned data on modern macOS systems. If you relied on this plugin, you'll want to update recipes to use `node['hardware']` instead, which correctly returns the same data, but in a more easily consumed format. Removing this plugin speeds up Ohai and Chef Infra Client by ~3 seconds, and dramatically reduces the size of the node object on the Chef Infra Server.
-### Ohai's Ohai::Util::Win32::GroupHelper class has been removed
+#### Ohai's Ohai::Util::Win32::GroupHelper class has been removed
We removed the `Ohai::Util::Win32::GroupHelper` helper class from Ohai. This class was intended for use internally in several Windows plugins, but it was never marked private in the codebase. If any of your Ohai plugins rely on this helper class, you will need to update your plugins for Ohai 15.
-### Audit Mode
+#### Audit Mode
Chef Client's Audit mode was introduced in 2015 as a beta that needed to be enabled via `client.rb`. Its functionality has been superseded by Chef InSpec and has been removed.
-### Ohai system_profiler plugin removal
+#### Ohai system_profiler plugin removal
The `system_profiler` plugin, which ran on macOS systems, has been removed. This plugin took longer to run than all other plugins on macOS combined, and no longer produced usable information on modern macOS releases. If you're looking for similar information, it can now be found in the `hardware` plugin.
-### Ohai::Util::Win32::GroupHelper helper removal
+#### Ohai::Util::Win32::GroupHelper helper removal
The deprecated `Ohai::Util::Win32::GroupHelper` helper has been removed from Ohai. Any custom Ohai plugins using this helper will need to be updated.
-### Ohai::System.refresh_plugins method removal
+#### Ohai::System.refresh_plugins method removal
The `refresh_plugins` method in the `Ohai::System` class has been removed as it has been unused for multiple major Ohai releases. If you are programmatically using Ohai in your own Ruby application, you will need to update your code to use the `load_plugins` method instead.
-### Ohai Microsoft VirtualPC / VirtualServer detection removal
+#### Ohai Microsoft VirtualPC / VirtualServer detection removal
The `Virtualization` plugin will no longer detect systems running on the circa ~2005 VirtualPC or VirtualServer hypervisors. These hypervisors were long ago deprecated by Microsoft and support can no longer be tested.
-# What's New in 14.15
+## What's New in 14.15
-## Updated Resources
+### Updated Resources
-### ifconfig
+#### ifconfig
The `ifconfig` resource has been updated to properly support interfaces with a hyphen in their name. This is most commonly encountered with bridge interfaces that are named `br-1234`. Additionally, the `ifconfig` resource now supports the latest ifconfig binaries found in OS releases such as Debian 10.
-### windows_task
+#### windows_task
The `windows_task` resource now supports the Start When Available option with a new `start_when_available` property. Issues that prevented the resource from being idempotent on Windows 2016 and 2019 hosts have also been resolved.
-## Platform Support
+### Platform Support
-### New Platforms
+#### New Platforms
Chef Infra Client is now tested against the following platforms with packages available on [downloads.chef.io](https://downloads.chef.io):
-* Ubuntu 20.04
-* Ubuntu 18.04 aarch64
-* Debian 10
+- Ubuntu 20.04
+- Ubuntu 18.04 aarch64
+- Debian 10
-### Retired Platforms
+#### Retired Platforms
-* Chef Infra Clients packages are no longer produced for Windows 2008 R2 as this release reached its end of life on Jan 14th, 2020.
-* Chef Infra Client packages are no longer produced for RHEL 6 on the s390x platform.
+- Chef Infra Clients packages are no longer produced for Windows 2008 R2 as this release reached its end of life on Jan 14th, 2020.
+- Chef Infra Client packages are no longer produced for RHEL 6 on the s390x platform.
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2u to resolve [CVE-2019-1551](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551)
-### Ruby
+#### Ruby
Ruby has been updated from 2.5.7 to 2.5.8 to resolve the following CVEs:
-* [CVE-2020-16255](https://www.ruby-lang.org/en/news/2020/03/19/json-dos-cve-2020-10663/): Unsafe Object Creation Vulnerability in JSON (Additional fix)
-* [CVE-2020-10933](https://www.ruby-lang.org/en/news/2020/03/31/heap-exposure-in-socket-cve-2020-10933/): Heap exposure vulnerability in the socket library
+- [CVE-2020-16255](https://www.ruby-lang.org/en/news/2020/03/19/json-dos-cve-2020-10663/): Unsafe Object Creation Vulnerability in JSON (Additional fix)
+- [CVE-2020-10933](https://www.ruby-lang.org/en/news/2020/03/31/heap-exposure-in-socket-cve-2020-10933/): Heap exposure vulnerability in the socket library
-# What's New in 14.14.29
+## What's New in 14.14.29
-## Bug Fixes
+### Bug Fixes
-* Fixed an error with the `service` and `systemd_unit` resources which would try to re-enable services with an indirect status.
-* The `systemd_unit` resource now logs at the info level.
-* Fixed knife config when it returned a `TypeError: no implicit conversion of nil into String` error.
+ - Fixed an error with the `service` and `systemd_unit` resources which would try to re-enable services with an indirect status.
+ - The `systemd_unit` resource now logs at the info level.
+ - Fixed knife config when it returned a `TypeError: no implicit conversion of nil into String` error.
-## Security Updates
+### Security Updates
-### libxslt
+#### libxslt
libxslt has been updated to 1.1.34 to resolve [CVE-2019-13118](https://nvd.nist.gov/vuln/detail/CVE-2019-13118).
-# What's New in 14.14.25
+## What's New in 14.14.25
-## Bug Fixes
+### Bug Fixes
-* Resolved a regression introduced in Chef Infra Client 14.14.14 that broke installation of gems in some scenarios
-* Fixed Habitat packaging of `chef-client` artifacts
-* Fixed crash in knife when displaying a missing profile error message
-* Fixed knife subcommand --help not working as intended for some commands
-* Fixed knife ssh interactive mode exit error
-* Fixed for `:day` option not accepting integer value in the `windows_task` resource
-* Fixed for `user` resource not handling a GID if it is specified as a string
-* Fixed the `ifconfig` resource to support interfaces with a `-` in the name
+- Resolved a regression introduced in Chef Infra Client 14.14.14 that broke installation of gems in some scenarios
+- Fixed Habitat packaging of `chef-client` artifacts
+- Fixed crash in knife when displaying a missing profile error message
+- Fixed knife subcommand --help not working as intended for some commands
+- Fixed knife ssh interactive mode exit error
+- Fixed for `:day` option not accepting integer value in the `windows_task` resource
+- Fixed for `user` resource not handling a GID if it is specified as a string
+- Fixed the `ifconfig` resource to support interfaces with a `-` in the name
## What's New in 14.14.14
-## Platform Updates
+### Platform Updates
-### Newly Supported Platforms
+#### Newly Supported Platforms
The following platforms are now packaged and tested for Chef Infra Client:
-* Red Hat 8
-* FreeBSD 12
-* macOS 10.15
-* Windows 2019
-* AIX 7.2
+- Red Hat 8
+- FreeBSD 12
+- macOS 10.15
+- Windows 2019
+- AIX 7.2
-### Deprecated Platforms
+#### Deprecated Platforms
The following platforms have reached EOL status and are no longer packaged or tested for Chef Infra Client:
-* FreeBSD 10
-* macOS 10.12
-* SUSE Linux Enterprise Server (SLES) 11
-* Ubuntu 14.04
+- FreeBSD 10
+- macOS 10.12
+- SUSE Linux Enterprise Server (SLES) 11
+- Ubuntu 14.04
-See Chef's [Platform End-of-Life Policy](https://docs.chef.io/platforms.html#platform-end-of-life-policy) for more information on when Chef ends support for an OS release.
+See Chef's [Platform End-of-Life Policy](/platforms/#platform-end-of-life-policy) for more information on when Chef ends support for an OS release.
-## Updated Resources
+### Updated Resources
-### dnf_package
+#### dnf_package
The `dnf_package` resource has been updated to fully support RHEL 8.
-### zypper_package
+#### zypper_package
The `zypper_package` resource has been updated to properly update packages when using the `:upgrade` action.
-### remote_file
+#### remote_file
The `remote_file` resource now properly shows download progress when the `show_progress` property is set to true.
-## Improvements
+### Improvements
-## Custom Resource Unified Mode
+### Custom Resource Unified Mode
Chef Infra Client 14.14 introduces an exciting new way to easily write custom resources that mix built-in Chef Infra resources with Ruby code. Previously, custom resources would use Chef Infra's standard compile and converge phases, which meant that Ruby would be evaluated first and then the resources would be converged. This often results in confusing and undesirable behavior when you are trying to mix resources with Ruby logic. Many custom resource authors would attempt to get around this by forcing resources to run at compile time so that all the code in their resource would execute during the compile phase.
@@ -2512,311 +3632,311 @@ action :create do
end
```
-### New Options for installing Ruby Gems From metadata.rb
+#### New Options for installing Ruby Gems From metadata.rb
Chef Infra Client allows gems to be specified in the cookbook metadata.rb, which can be problematic in some environments. When a cookbook is running in an airgapped environment, Chef Infra Client attempts to connect to rubygems.org even if the gem is already on the system. There are now two additional configuration options that can be set in your `client.rb` config:
- * `gem_installer_bundler_options`: This allows setting additional bundler options for the install such as --local to install from local cache. Example: ["--local", "--clean"].
- * `skip_gem_metadata_installation`: If set to true skip gem metadata installation if all gems are already installed.
+ - `gem_installer_bundler_options`: This allows setting additional bundler options for the install such as --local to install from local cache. Example: ["--local", "--clean"].
+ - `skip_gem_metadata_installation`: If set to true skip gem metadata installation if all gems are already installed.
-### SLES / openSUSE 15 detection
+#### SLES / openSUSE 15 detection
Ohai now properly detects SLES and openSUSE 15.x. Thanks for this fix [@balasankarc](https://gitlab.com/balasankarc).
-### Performance Improvements
+#### Performance Improvements
We have improved the performance of Chef Infra Client by resolving bundler errors in our packaging.
-### Bootstrapping Chef Infra Client 15 will no fail
+#### Bootstrapping Chef Infra Client 15 will no fail
Knife now fails with a descriptive error message when attempting to bootstrap nodes with Chef Infra Client 15. You will need to bootstrap these nodes using Knife from Chef Infra Client 15.x. We recommend performing this bootstrap from Chef Workstation, which includes the Knife CLI in addition to other useful tools for managing your infrastructure with Chef Infra.
-## Security Updates
+### Security Updates
-### Ruby
+#### Ruby
Ruby has been updated from 2.5.5 to 2.5.7 in order to resolve the following CVEs:
-* [CVE-2012-6708](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-6708)
-* [CVE-2015-9251](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251).
-* [CVE-2019-16201](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-15845).
-* [CVE-2019-15845](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251).
-* [CVE-2019-16254](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16254).
-* [CVE-2019-16255](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16255).
+- [CVE-2012-6708](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-6708)
+- [CVE-2015-9251](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251).
+- [CVE-2019-16201](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-15845).
+- [CVE-2019-15845](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9251).
+- [CVE-2019-16254](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16254).
+- [CVE-2019-16255](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16255).
-### openssl
+#### openssl
openssl has been updated from 1.0.2s to 1.0.2t in order to resolve [CVE-2019-1563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1563) and [CVE-2019-1547](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1547).
-### nokogiri
+#### nokogiri
nokogiri has been updated from 1.10.2 to 1.10.4 in order to resolve [CVE-2019-5477](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5477).
-# What's New in 14.13
+## What's New in 14.13
-## Updated Resources
+### Updated Resources
-### directory
+#### directory
The `directory` has been updated to properly set the `deny_rights` permission on Windows. Thanks [@merlinjim](https://github.com/merlinjim) for reporting this issue.
-### service
+#### service
The `service` resource is now idempotent on SLES 11 systems. Thanks [@gsingla294](https://github.com/gsingla294) for reporting this issue.
-### cron
+#### cron
The `cron` resource has been updated to advise users to use the specify properties rather than passing values in as part of the `environment` property. This avoids a situation where a user could pass the differing values in both locations and receive unexpected results.
-### link
+#### link
The `link` resource includes improved logging upon failure to help you debug what has failed. Thanks [@jaymzh](https://github.com/jaymzh) for this improvement.
-### template
+#### template
The `template` resource now includes additional information when templating failures, which is particularly useful in ChefSpec. Thanks [@brodock](https://github.com/brodock) for this improvement.
-## delete_resource Fix
+### delete_resource Fix
The `delete_resource` helper now works properly when the resource you are attempting to delete has multiple providers. Thanks [@artem-sidorenko](https://github.com/artem-sidorenko) for this fix.
-## Helpers Help Everywhere
+### Helpers Help Everywhere
Various helpers have been moved into Chef Infra Client's `universal` class, which makes them available anywhere in your cookbook, not just recipes. If you've ever been confused why something like `search`, `powershell_out`, or `data_bag_item` didn't work somewhere in your code, that should be resolved now.
-## Deprecations
+### Deprecations
The `CHEF-25` deprecation for resource collisions between cookbooks and resources in Chef Infra Client has been removed. Instead you will see a log warning that a collision has occurred, which advises you to update your run_list or cookbooks.
-## Updated Components
+### Updated Components
-* openssl 1.0.2r -> 1.0.2s (bugfix only release)
-* cacerts 2019-01-23 -> 2019-05-15
+- openssl 1.0.2r -> 1.0.2s (bugfix only release)
+- cacerts 2019-01-23 -> 2019-05-15
-# What's New in 14.12.9
+## What's New in 14.12.9
-## License Acceptance Placeholder Flag
+### License Acceptance Placeholder Flag
In preparation for Chef Infra Client 15.0 we've added a placeholder `--chef-license` flag to the chef-client command. This allows you to use the new `--chef-license` flag on both Chef Infra Client 14.12.9+ and 15+ notes without producing errors on Chef Infra Client 14.
-## Important Bug Fixes
+### Important Bug Fixes
-* Blacklisting and whitelisting default and override level attributes is once again possible.
-* You may now encrypt a previously unencrypted data bag.
-* Resolved a regression introduced in Chef Infra Client 14.12.3 that resulted in errors when managing Windows services
+- Blacklisting and whitelisting default and override level attributes is once again possible.
+- You may now encrypt a previously unencrypted data bag.
+- Resolved a regression introduced in Chef Infra Client 14.12.3 that resulted in errors when managing Windows services
-# What's New in 14.12.3
+## What's New in 14.12.3
-## Updated Resources
+### Updated Resources
-### windows_service
+#### windows_service
The windows_service resource no longer resets credentials on a service when using the :start action without the :configure action. Thanks [@jasonwbarnett](https://github.com/jasonwbarnett) for fixing this.
-### windows_certificate
+#### windows_certificate
The windows_certificate resource now imports nested certificates while importing P7B certs.
-## Updated Components
+### Updated Components
-* nokogiri 1.10.1 -> 1.10.2
-* ruby 2.5.3 -> 2.5.5
-* InSpec 3.7.1 -> 3.9.0
-* The unused windows-api gem is no longer bundled with Chef on Windows hosts
+- nokogiri 1.10.1 -> 1.10.2
+- ruby 2.5.3 -> 2.5.5
+- InSpec 3.7.1 -> 3.9.0
+- The unused windows-api gem is no longer bundled with Chef on Windows hosts
-# What's New in 14.11
+## What's New in 14.11
-## Updated Resources
+### Updated Resources
-### chocolatey_package
+#### chocolatey_package
The chocolatey_package resource now uses the provided options to fetch information on available packages, which allows installation packages from private sources. Thanks [@astoltz](https://github.com/astoltz) for reporting this issue.
-### openssl_dhparam
+#### openssl_dhparam
The openssl_dhparam resource now supports updating the dhparam file's mode on subsequent chef-client runs. Thanks [@anewb](https://github.com/anewb) for the initial work on this fix.
-### mount
+#### mount
The mount resource now properly adds a blank line between entries in fstab to prevent mount failures on AIX.
-### windows_certificate
+#### windows_certificate
The windows_certificate resource now supports importing Base64 encoded CER certificates and nested P7B certificates. Additionally, private keys in PFX certificates are now imported along with the certificate.
-### windows_share
+#### windows_share
The windows_share resource has improved logic to compare the desired share path vs. the current path, which prevents the resource from incorrectly converging during each Chef run. Thanks [@Xorima](https://github.com/xorima) for this fix.
-### windows_task
+#### windows_task
The windows_task resource now properly clears out arguments that are no longer present when updating a task. Thanks [@nmcspadden](https://github.com/nmcspadden) for reporting this.
-## InSpec 3.7.1
+### InSpec 3.7.1
InSpec has been updated from 3.4.1 to 3.7.1. This new release contains improvements to the plugin system, a new config file system, and improvements to multiple resources. Additionally, profile attributes have also been renamed to inputs to prevent confusion with Chef attributes, which weren't actually related in any way.
-## Updated Components
+### Updated Components
-* bundler 1.16.1 -> 1.17.3
-* libxml2 2.9.7 -> 2.9.9
-* ca-certs updated to 2019-01-22 for new roots
+- bundler 1.16.1 -> 1.17.3
+- libxml2 2.9.7 -> 2.9.9
+- ca-certs updated to 2019-01-22 for new roots
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2r in order to resolve [CVE-2019-1559](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559)
-### RubyGems
+#### RubyGems
RubyGems has been updated to 2.7.9 in order to resolve the following CVEs:
-* [CVE-2019-8320](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8320): Delete directory using symlink when decompressing tar
-* [CVE-2019-8321](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8321): Escape sequence injection vulnerability in verbose
-* [CVE-2019-8322](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8322): Escape sequence injection vulnerability in gem owner
-* [CVE-2019-8323](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8323): Escape sequence injection vulnerability in API response handling
-* [CVE-2019-8324](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8324): Installing a malicious gem may lead to arbitrary code execution
-* [CVE-2019-8325](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8325): Escape sequence injection vulnerability in errors
+- [CVE-2019-8320](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8320): Delete directory using symlink when decompressing tar
+- [CVE-2019-8321](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8321): Escape sequence injection vulnerability in verbose
+- [CVE-2019-8322](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8322): Escape sequence injection vulnerability in gem owner
+- [CVE-2019-8323](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8323): Escape sequence injection vulnerability in API response handling
+- [CVE-2019-8324](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8324): Installing a malicious gem may lead to arbitrary code execution
+- [CVE-2019-8325](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8325): Escape sequence injection vulnerability in errors
-# What's New in 14.10
+## What's New in 14.10
-## Updated Resources
+### Updated Resources
-### windows_certificate
+#### windows_certificate
The windows_certificate resource is now fully idempotent and properly imports private keys. Thanks [@Xorima](https://github.com/Xorima) for reporting these issues.
-### apt_repository
+#### apt_repository
The apt_repository resource no longer creates .gpg directory in the user's home directory owned by root when installing repository keys. Thanks [@omry](http://github.com/omry) for reporting this issue.
-### git
+#### git
The git resource no longer displays the URL of the repository if the `sensitive` property is set.
-## InSpec 3.4.1
+### InSpec 3.4.1
InSpec has been updated from 3.2.6 to 3.4.1. This new release adds new `aws_billing_report` / `aws_billing_reports` resources, resolves multiple bugs, and includes tons of under the hood improvements.
-## New Deprecations
+### New Deprecations
-### knife cookbook site
+#### knife cookbook site
Since Chef 13, `knife cookbook site` has actually called the `knife supermarket` command under the hood. In Chef 16 (April 2020), we will remove the `knife cookbook site` command in favor of `knife supermarket`.
-### Audit Mode
+#### Audit Mode
Chef's Audit mode was introduced in 2015 as a beta that needed to be enabled via client.rb. Its functionality has been superseded by InSpec and we will be removing this beta feature in Chef Infra Client 15 (April 2019).
-### Cookbook Shadowing
+#### Cookbook Shadowing
Cookbook shadowing was deprecated in 0.10 and will be removed in Chef Infra Client 15 (April 2019). Cookbook shadowing allowed combining cookbooks within a mono-repo, so long as the cookbooks in question had the same name and were present in both the cookbooks directory and the site-cookbooks directory.
-# What's New in 14.9
+## What's New in 14.9
-## Updated Resources
+### Updated Resources
-### group
+#### group
On Windows hosts, the group resource now supports setting the comment field via a new `comment` property.
-### homebrew_cask
+#### homebrew_cask
Two issues, which caused homebrew_cask to converge on each Chef run, have been resolved. Thanks [@jeroenj](https://github.com/jeroenj) for this fix. Additionally, the resource will no longer fail if the `cask_name` property is specified.
-### homebrew_tap
+#### homebrew_tap
The homebrew_tap resource no longer fails if the `tap_name` property is specified.
-### openssl_x509_request
+#### openssl_x509_request
The openssl_x509_request resource now properly writes out the CSR file if the `path` property is specified. Thank you [@cpjones](https://github.com/cpjones) for reporting this issue.
-### powershell_package_source
+#### powershell_package_source
powershell_package_source now suppresses warnings, which prevented properly loading the resource state, and resolves idempotency issues when both the `name` and `source_name` properties were specified. Thanks [@Happycoil](https://github.com/Happycoil) for this fix.
-### sysctl
+#### sysctl
The sysctl resource now allows slashes in the key or block name. This allows keys such as `net/ipv4/conf/ens256.401/rp_filter` to be used with this resource.
-### windows_ad_join
+#### windows_ad_join
Errors joining the domain are now properly suppressed from the console and logs if the `sensitive` property is set to true. Thanks [@Happycoil](https://github.com/Happycoil) for this improvement.
-### windows_certificate
+#### windows_certificate
The delete action now longer fails if a certificate does not exist on the system. Additionally, certificates with special characters in their passwords will no longer fail. Thank you for reporting this [@chadmccune](https://github.com/chadmccune).
-### windows_printer
+#### windows_printer
The windows_printer resource no longer fails when creating or deleting a printer if the `device_id` property is specified.
-### windows_task
+#### windows_task
Non-system users can now run tasks without a password being specified.
-## Minimal Ohai Improvements
+### Minimal Ohai Improvements
The ohai `init_package` plugin is now included as part of the `minimal_ohai` plugins set, which allows resources such as timezone to continue to function if Chef is running with the minimal number of ohai plugins.
-## Ruby 2.6 Support
+### Ruby 2.6 Support
Chef 14.9 now supports Ruby 2.6.
-## InSpec 3.2.6
+### InSpec 3.2.6
InSpec has been updated from 3.0.64 to 3.2.6 with improved resources for auditing. See the [InSpec changelog](https://github.com/inspec/inspec/blob/master/CHANGELOG.md#v326-2018-12-20) for additional details on this new version.
-## powershell_exec Runtimes Bundled
+### powershell_exec Runtimes Bundled
The necessary VC++ runtimes for the powershell_exec helper are now bundled with Chef to prevent failures on hosts that lacked the runtimes.
-# What's New in 14.8
+## What's New in 14.8
-## Updated Resources
+### Updated Resources
-### apt_package
+#### apt_package
The apt_package resource now supports using the `allow_downgrade` property to enable downgrading of packages on a node in order to meet a specified version. Thank you [@whiteley](https://github.com/whiteley) for requesting this enhancement.
-### apt_repository
+#### apt_repository
An issue was resolved in the apt_repository resource that caused the resource to fail when importing GPG keys on newer Debian releases. Thank you [@EugenMayer](https://github.com/EugenMayer) for this fix.
-### dnf_package / yum_package
+#### dnf_package / yum_package
Initial support has been added for Red Hat Enterprise Linux 8. Thank you [@pixdrift](https://github.com/pixdrift) for this fix.
-### gem_package
+#### gem_package
gem_package now supports installing gems into Ruby 2.6 or later installations.
-### windows_ad_join
+#### windows_ad_join
windows_ad_join now uses the UPN format for usernames, which prevents some failures authenticating to the domain.
-### windows_certificate
+#### windows_certificate
An issue was resolved in the :acl_add action of the windows_certificate resource, which caused the resource to fail. Thank you [@shoekstra](https://github.com/shoekstra) for reporting this issue.
-### windows_feature
+#### windows_feature
The windows_feature resource now allows for the installation of DISM features that have been fully removed from a system. Thank you [@zanecodes](https://github.com/zanecodes) for requesting this enhancement.
-### windows_share
+#### windows_share
Multiple issues were resolved in windows_share, which caused the resource to either fail or update the share state on every Chef Client run. Thank you [@chadmccune](https://github.com/chadmccune) for reporting several of these issues and [@derekgroh](https://github.com/derekgroh) for one of the fixes.
-### windows_task
+#### windows_task
A regression was resolved that prevented ChefSpec from testing the windows_task resource in Chef Client 14.7. Thank you [@jjustice6](https://github.com/jjustice6) for reporting this issue.
-## Ohai 14.8
+### Ohai 14.8
-### Improved Virtualization Detection
+#### Improved Virtualization Detection
-#### Hyper-V Hypervisor Detection
+**Hyper-V Hypervisor Detection**
Detection of Linux guests running on Hyper-V has been improved. In addition, Linux guests on Hyper-V hypervisors will also now detect their hypervisor's hostname. Thank you [@safematix](https://github.com/safematix) for contributing this enhancement.
@@ -2833,37 +3953,37 @@ Example `node['virtualization']` data:
}
```
-#### LXC / LXD Detection
+**LXC / LXD Detection**
On Linux systems running lxc or lxd containers, the lxc/lxd virtualization system will now properly populate the `node['virtualization']['systems']` attribute.
-#### BSD Hypervisor Detection
+**BSD Hypervisor Detection**
BSD-based systems can now detect guests running on KVM and Amazon's hypervisor without the need for the dmidecode package.
-### New Platform Support
+#### New Platform Support
-* Ohai now properly detects the openSUSE 15.X platform. Thank you [@megamorf](https://github.com/megamorf) for reporting this issue.
-* SUSE Linux Enterprise Desktop now identified as platform_family 'suse'
-* XCP-NG is now identified as platform 'xcp' and platform_family 'rhel'. Thank you [@heyjodom](http://github.com/heyjodom) for submitting this enhancement.
-* Mangeia Linux is now identified as platform 'mangeia' and platform_family 'mandriva'
-* Antergos Linux now identified as platform_family 'arch'
-* Manjaro Linux now identified as platform_family 'arch'
+- Ohai now properly detects the openSUSE 15.X platform. Thank you [@megamorf](https://github.com/megamorf) for reporting this issue.
+- SUSE Linux Enterprise Desktop now identified as platform_family 'suse'
+- XCP-NG is now identified as platform 'xcp' and platform_family 'rhel'. Thank you [@heyjodom](http://github.com/heyjodom) for submitting this enhancement.
+- Mangeia Linux is now identified as platform 'mangeia' and platform_family 'mandriva'
+- Antergos Linux now identified as platform_family 'arch'
+- Manjaro Linux now identified as platform_family 'arch'
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2q in order to resolve:
-* Microarchitecture timing vulnerability in ECC scalar multiplication [CVE-2018-5407](https://nvd.nist.gov/vuln/detail/CVE-2018-5407)
-* Timing vulnerability in DSA signature generation ([CVE-2018-0734](https://nvd.nist.gov/vuln/detail/CVE-2018-0734))
+- Microarchitecture timing vulnerability in ECC scalar multiplication [CVE-2018-5407](https://nvd.nist.gov/vuln/detail/CVE-2018-5407)
+- Timing vulnerability in DSA signature generation ([CVE-2018-0734](https://nvd.nist.gov/vuln/detail/CVE-2018-0734))
-# What's New in 14.7
+## What's New in 14.7
-## New Resources
+### New Resources
-### windows_firewall_rule
+#### windows_firewall_rule
Use the `windows_firewall_rule` resource create or delete Windows Firewall rules.
@@ -2871,49 +3991,49 @@ See the [windows_firewall_rule](https://docs.chef.io/resources/windows_firewall_
Thank you [Schuberg Philis](https://schubergphilis.com/) for transferring us the [windows_firewall cookbook](https://supermarket.chef.io/cookbooks/windows_firewall) and to [@Happycoil](https://github.com/Happycoil) for porting it to chef-client with a significant refactoring.
-### windows_share
+#### windows_share
Use the `windows_share` resource create or delete Windows file shares.
See the [windows_share](https://docs.chef.io/resources/windows_share) documentation for more information.
-### windows_certificate
+#### windows_certificate
Use the `windows_certificate` resource add, remove, or verify certificates in the system or user certificate stores.
See the [windows_certificate](https://docs.chef.io/resources/windows_certificate) documentation for more information.
-## Updated Resources
+### Updated Resources
-### dmg_package
+#### dmg_package
The dmg_package resource has been refactored to improve idempotency and properly support accepting a DMG's EULA with the `accept_eula` property.
-### kernel_module
+#### kernel_module
Kernel_module now only runs the `initramfs` update once per Chef run to greatly speed up chef-client runs when multiple kernel_module resources are used. Thank you [@tomdoherty](https://github.com/tomdoherty) for this improvement.
-### mount
+#### mount
The `supports` property once again allows passing supports data as an array. This matches the behavior present in Chef 12.
-### timezone
+#### timezone
macOS support has been added to the timezone resource.
-### windows_task
+#### windows_task
A regression in Chef 14.6's windows_task resource which resulted in tasks being created with the "Run only when user is logged on" option being set when created with a specific user other than SYSTEM, has been resolved.
-# What's New in 14.6
+## What's New in 14.6
-## Smaller Package and Install Size
+### Smaller Package and Install Size
Both Chef packages and on disk installations have been greatly reduced in size by trimming unnecessary installation files. This has reduced our package size on macOS/Linux by ~50% and Windows by ~12%. With this change Chef 14 is now smaller than a legacy Chef 10 package.
-## New Resources
+### New Resources
-### Timezone
+#### timezone
Chef now includes the `timezone` resource from [@dragonsmith](http://github.com/dragonsmith)'s `timezone_lwrp` cookbook. This resource supports setting a Linux node's timezone. Thank you [@dragonsmith](http://github.com/dragonsmith) for allowing us to include this out of the box in Chef.
@@ -2923,25 +4043,25 @@ Example:
timezone 'UTC'
```
-## Updated Resources
+### Updated Resources
-### windows_task
+#### windows_task
The `windows_task` resource has been updated to support localized system users and groups on non-English nodes. Thanks [@jugatsu](http://github.com/jugatsu) for making this possible.
-### user
+#### user
The `user` resource now includes a new `full_name` property for Windows hosts, which allows specifying a user's full name.
Example:
```ruby
- user 'jdoe' do
- full_name 'John Doe'
- end
+user 'jdoe' do
+ full_name 'John Doe'
+end
```
-### zypper_package
+#### zypper_package
The `zypper_package` resource now includes a new `global_options` property. This property can be used to specify one or more options for the zypper command line that are global in context.
@@ -2953,69 +4073,69 @@ package 'sssd' do
end
```
-## InSpec 3.0
+### InSpec 3.0
Inspec has been updated to version 3.0 with addition resources, exception handling, and a new plugin system. See <https://blog.chef.io/2018/10/16/announcing-inspec-3-0/> for details.
-## macOS Mojave (10.14)
+### macOS Mojave (10.14)
Chef is now tested against macOS Mojave, and packages are now available at downloads.chef.io.
-## Important Bugfixes
+### Important Bugfixes
-* Multiple bugfixes in Chef Vault have been resolved by updating chef-vault to 3.4.2
-* Invalid yum package names now gracefully fail
-* `windows_ad_join` now properly executes. Thank you [@cpjones01](https://github.com/cpjones01) for reporting this.
-* `rhsm_errata_level` now properly executes. Thank you [@freakinhippie](https://github.com/freakinhippie) for this fix.
-* `registry_key` now properly writes out the correct value when `sensitive` is specified. Thank you [@josh-barker](https://github.com/josh-barker) for this fix.
-* `locale` now properly executes on RHEL 6 and Amazon Linux 201X.
+- Multiple bugfixes in Chef Vault have been resolved by updating chef-vault to 3.4.2
+- Invalid yum package names now gracefully fail
+- `windows_ad_join` now properly executes. Thank you [@cpjones01](https://github.com/cpjones01) for reporting this.
+- `rhsm_errata_level` now properly executes. Thank you [@freakinhippie](https://github.com/freakinhippie) for this fix.
+- `registry_key` now properly writes out the correct value when `sensitive` is specified. Thank you [@josh-barker](https://github.com/josh-barker) for this fix.
+- `locale` now properly executes on RHEL 6 and Amazon Linux 201X.
-## Ohai 14.6
+### Ohai 14.6
-### Filesystem Plugin on AIX and Solaris
+#### Filesystem Plugin on AIX and Solaris
AIX and Solaris now ship with a filesystem2 plugin that updates the filesystem data to match that of Linux, macOS, and BSD hosts. This new data structure makes accessing filesystem data in recipes easier and especially improves the layout and depth of data on ZFS filesystems. In Chef Infra Client 15 (April 2019) we will begin writing this same format of data to the existing `node['filesystem']` namespace. In Chef 16 (April 2020) we will remove the `node['filesystem2']` namespace, completing the transition to the new format. Thank you [@jaymzh](https://github.com/jaymzh) for continuing the updates to our filesystem plugins with this change.
-### macOS Improvements
+#### macOS Improvements
The system_profile plugin has been improved to skip over unnecessary data, which reduces macOS node sizes on the Chef Server. Additionally the CPU plugin has been updated to limit what sysctl values it polls, which prevents hanging on some system configurations.
-### SLES 15 Detection
+#### SLES 15 Detection
SLES 15 is now correctly detected as the platform "suse" instead of "sles". This matches the behavior of SLES 11 and 12 hosts.
-## New Deprecations
+### New Deprecations
-### system_profile Ohai plugin removal
+#### system_profile Ohai plugin removal
The system_profile plugin will be removed from Chef/Ohai 15 in April 2019. This plugin does not correctly return data on modern Mac systems. Additionally the same data is provided by the hardware plugin, which has a format that is simpler to consume. Removing this plugin will reduce Ohai return by ~3 seconds and greatly reduce the size of the node object on the Chef server.
-## Security Updates
+### Security Updates
-### Ruby 2.5.3
+#### Ruby 2.5.3
Ruby has been updated to from 2.5.1 to 2.5.3 to resolve multiple CVEs and bugs:
-* [CVE-2018-16396](https://www.ruby-lang.org/en/news/2018/10/17/not-propagated-taint-flag-in-some-formats-of-pack-cve-2018-16396/)
-* [CVE-2018-16395](https://www.ruby-lang.org/en/news/2018/10/17/openssl-x509-name-equality-check-does-not-work-correctly-cve-2018-16395/)
+- [CVE-2018-16396](https://www.ruby-lang.org/en/news/2018/10/17/not-propagated-taint-flag-in-some-formats-of-pack-cve-2018-16396/)
+- [CVE-2018-16395](https://www.ruby-lang.org/en/news/2018/10/17/openssl-x509-name-equality-check-does-not-work-correctly-cve-2018-16395/)
-# What's New in 14.5.33
+## What's New in 14.5.33
This release resolves a regression that caused the ``windows_ad_join`` resource to fail to run. It also makes the following additional fixes:
-* The ``ohai`` resource's unused ``ohai_name`` property has been deprecated. This will be removed in Chef Infra Client 15.0.
-* Error messages in the ``windows_feature`` resources have been improved.
-* The ``windows_service`` resource will no longer log potentially sensitive information if the ``sensitive`` property is used.
+ - The ``ohai`` resource's unused ``ohai_name`` property has been deprecated. This will be removed in Chef Infra Client 15.0.
+ - Error messages in the ``windows_feature`` resources have been improved.
+ - The ``windows_service`` resource will no longer log potentially sensitive information if the ``sensitive`` property is used.
Thanks to @cpjones01, @kitforbes, and @dgreeninger for their help with this release.
-# What's New in 14.5.27
+## What's New in 14.5.27
-## New Resources
+### New Resources
We've added new resources to Chef 14.5. Cookbooks using these resources will continue to take precedent until the Chef Infra Client 15.0 release
-### windows_workgroup
+#### windows_workgroup
Use the `windows_workgroup` resource to join or change a Windows host workgroup.
@@ -3023,7 +4143,7 @@ See the [windows_workgroup](https://docs.chef.io/resources/windows_workgroup) do
Thanks [@derekgroh](https://github.com/derekgroh) for contributing this new resource.
-### locale
+#### locale
Use the `locale` resource to set the system's locale.
@@ -3031,47 +4151,47 @@ See the [locale](https://docs.chef.io/resources/locale) documentation for more i
Thanks [@vincentaubert](https://github.com/vincentaubert) for contributing this new resource.
-## Updated Resources
+### Updated Resources
-### windows_ad_join
+#### windows_ad_join
`windows_ad_join` now includes a `new_hostname` property for setting the hostname for the node upon joining the domain.
Thanks [@derekgroh](https://github.com/derekgroh) for contributing this new property.
-## InSpec 2.2.102
+### InSpec 2.2.102
InSpec has been updated from 2.2.70 to 2.2.102. This new version includes the following improvements:
-* Support for using ERB templating within the .yml files
-* HTTP basic auth support for fetching dependent profiles
-* A new global attributes concept
-* Better error handling with Automate reporting
-* Vendor command now vendors profiles when using path://
+ - Support for using ERB templating within the .yml files
+ - HTTP basic auth support for fetching dependent profiles
+ - A new global attributes concept
+ - Better error handling with Automate reporting
+ - Vendor command now vendors profiles when using path://
-## Ohai 14.5
+### Ohai 14.5
-### Windows Improvements
+#### Windows Improvements
Detection for the `root_group` attribute on Windows has been simplified and improved to properly support non-English systems. With this change, we've also deprecated the `Ohai::Util::Win32::GroupHelper` helper, which is no longer necessary. Thanks to [@jugatsu](https://github.com/jugatsu) for putting this together.
We've also added a new `encryption_status` attribute to volumes on Windows. Thanks to [@kmf](https://github.com/kmf) for suggesting this new feature.
-### Configuration Improvements
+#### Configuration Improvements
The timeout period for communicating with OpenStack metadata servers can now be configured with the `openstack_metadata_timeout` config option. Thanks to [@sawanoboly](https://github.com/sawanoboly) for this improvement.
Ohai now properly handles relative paths to config files when running on the command line. This means commands like `ohai -c ../client.rb` will now properly use your config values.
-## Security updates
+### Security updates
-### Rubyzip
+#### Rubyzip
The rubyzip gem has been updated to 1.2.2 to resolve [CVE-2018-1000544](https://www.cvedetails.com/cve/CVE-2018-1000544/)
-# What's New in 14.4
+## What's New in 14.4
-## Knife configuration profile management commands
+### Knife configuration profile management commands
Several new commands have been added under `knife config` to help manage multiple
profiles in your `credentials` file.
@@ -3099,90 +4219,90 @@ $ knife config list-profiles
Thank you [@coderanger](https://github.com/coderanger) for this contribution.
-## New Resources
+### New Resources
The following new previous resources were added to Chef 14.4. Cookbooks with the same resources will continue to take precedent until the Chef Infra Client 15.0 release
-### Cron_d
+#### cron_d
Use the [cron_d](https://docs.chef.io/resources/cron_d) resource to manage cron definitions in /etc/cron.d. This is similar to the `cron` resource, but it does not use the monolithic `/etc/crontab`. file.
-### Cron_access
+#### cron_access
Use the [cron_access](https://docs.chef.io/resources/cron_access) resource to manage the `/etc/cron.allow` and `/etc/cron.deny` files. This resource previously shipped in the `cron` community cookbook and has fully backwards compatibility with the previous `cron_manage` definition in that cookbook.
-### openssl_x509_certificate
+#### openssl_x509_certificate
Use the [openssl_x509_certificate](https://docs.chef.io/resources/openssl_x509_certificate) resource to generate signed or self-signed, PEM-formatted x509 certificates. If no existing key is specified, the resource automatically generates a passwordless key with the certificate. If a CA private key and certificate are provided, the certificate will be signed with them. This resource previously shipped in the `openssl` cookbook as `openssl_x509` and is fully backwards compatible with the legacy resource name.
Thank you [@juju482](https://github.com/juju482) for updating this resource!
-### openssl_x509_request
+#### openssl_x509_request
Use the [openssl_x509_request](https://docs.chef.io/resources/openssl_x509_request) resource to generate PEM-formatted x509 certificates requests. If no existing key is specified, the resource automatically generates a passwordless key with the certificate.
Thank you [@juju482](https://github.com/juju482) for contributing this resource.
-### openssl_x509_crl
+#### openssl_x509_crl
Use the [openssl_x509_crl](https://docs.chef.io/resources/openssl_x509_crl)l resource to generate PEM-formatted x509 certificate revocation list (CRL) files.
Thank you [@juju482](https://github.com/juju482) for contributing this resource.
-### openssl_ec_private_key
+#### openssl_ec_private_key
Use the [openssl_ec_private_key](https://docs.chef.io/resources/openssl_ec_private_key) resource to generate ec private key files. If a valid ec key file can be opened at the specified location, no new file will be created.
Thank you [@juju482](https://github.com/juju482) for contributing this resource.
-### openssl_ec_public_key
+#### openssl_ec_public_key
Use the [openssl_ec_public_key](https://docs.chef.io/resources/openssl_ec_public_key) resource to generate ec public key files given a private key.
Thank you [@juju482](https://github.com/juju482) for contributing this resource.
-## Resource improvements
+### Resource improvements
-### windows_package
+#### windows_package
The windows_package resource now supports setting the `sensitive` property to avoid showing errors if a package install fails.
-### sysctl
+#### sysctl
The sysctl resource will now update the on-disk `sysctl.d` file even if the current sysctl value matches the desired value.
-### windows_task
+#### windows_task
The windows_task resource now supports setting the task priority of the scheduled task with a new `priority` property. Additionally windows_task now supports managing the behavior of task execution when a system is on battery using new `disallow_start_if_on_batteries` and `stop_if_going_on_batteries` properties.
-### ifconfig
+#### ifconfig
The ifconfig resource now supports setting the interface's VLAN via a new `vlan` property on RHEL `platform_family` and setting the interface's gateway via a new `gateway` property on RHEL/Debian `platform_family`.
Thank you [@tomdoherty](https://github.com/tomdoherty) for this contribution.
-### route
+#### route
The route resource now supports additional RHEL platform_family systems as well as Amazon Linux.
-### systemd_unit
+#### systemd_unit
The [systemd_unit](https://docs.chef.io/resources/systemd_unit) resource now supports specifying options multiple times in the content hash. Instead of setting the value to a string you can now set it to an array of strings.
Thank you [@dbresson](https://github.com/dbresson) for this contribution.
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL updated to 1.0.2p to resolve:
-* Client DoS due to large DH parameter ([CVE-2018-0732](https://nvd.nist.gov/vuln/detail/CVE-2018-0732))
-* Cache timing vulnerability in RSA Key Generation ([CVE-2018-0737](https://nvd.nist.gov/vuln/detail/CVE-2018-0737))
+- Client DoS due to large DH parameter ([CVE-2018-0732](https://nvd.nist.gov/vuln/detail/CVE-2018-0732))
+- Cache timing vulnerability in RSA Key Generation ([CVE-2018-0737](https://nvd.nist.gov/vuln/detail/CVE-2018-0737))
-# What's New in 14.3
+## What's New in 14.3
-## New Preview Resources Concept
+### New Preview Resources Concept
This release of Chef introduces the concept of Preview Resources. Preview resources behave the same as a standard resource built into Chef, except Chef will load a resource with the same name from a cookbook instead of the built-in preview resource.
@@ -3190,98 +4310,98 @@ What does this mean for you? It means we can introduce new resources in Chef wit
Then when we perform our yearly major release we'll remove the preview designation from all resources, and the built in resources will take precedence over resources with the same names in cookbooks.
-## New Resources
+### New Resources
-### chocolatey_config
+#### chocolatey_config
Use the chocolatey_config resource to add or remove Chocolatey configuration keys."
-#### Actions
+**Actions**
-* `set` * Sets a Chocolatey config value.
-* `unset` * Unsets a Chocolatey config value.
+- `set` - Sets a Chocolatey config value.
+- `unset` - Unsets a Chocolatey config value.
-#### Properties
+**Properties**
-* `config_key` * The name of the config. We'll use the resource's name if this isn't provided.
-* `value` * The value to set.
+- `config_key` - The name of the config. We'll use the resource's name if this isn't provided.
+- `value` - The value to set.
-### chocolatey_source
+#### chocolatey_source
Use the chocolatey_source resource to add or remove Chocolatey sources.
-#### Actions
+**Actions**
-* `add` * Adds a Chocolatey source.
-* `remove` * Removes a Chocolatey source.
+- `add` - Adds a Chocolatey source.
+- `remove` - Removes a Chocolatey source.
-#### Properties
+**Properties**
-* `source_name` * The name of the source to add. We'll use the resource's name if this isn't provided.
-* `source` * The source URL.
-* `bypass_proxy` * Whether or not to bypass the system's proxy settings to access the source.
-* `priority` * The priority level of the source.
+- `source_name` - The name of the source to add. We'll use the resource's name if this isn't provided.
+- `source` - The source URL.
+- `bypass_proxy` - Whether or not to bypass the system's proxy settings to access the source.
+- `priority` - The priority level of the source.
-### powershell_package_source
+#### powershell_package_source
Use the `powershell_package_source` resource to register a PowerShell package repository.
-### Actions
+#### Actions
-* `register` * Registers and updates the PowerShell package source.
-* `unregister` * Unregisters the PowerShell package source.
+- `register` - Registers and updates the PowerShell package source.
+- `unregister` - Unregisters the PowerShell package source.
-#### Properties
+**Properties**
-* `source_name` * The name of the package source.
-* `url` * The URL to the package source.
-* `trusted` * Whether or not to trust packages from this source.
-* `provider_name` * The package management provider for the source. It supports the following providers: 'Programs', 'msi', 'NuGet', 'msu', 'PowerShellGet', 'psl' and 'chocolatey'.
-* `publish_location` * The URL where modules will be published to for this source. Only valid if the provider is 'PowerShellGet'.
-* `script_source_location` * The URL where scripts are located for this source. Only valid if the provider is 'PowerShellGet'.
-* `script_publish_location` * The location where scripts will be published to for this source. Only valid if the provider is 'PowerShellGet'.
+- `source_name` - The name of the package source.
+- `url` - The url to the package source.
+- `trusted` - Whether or not to trust packages from this source.
+- `provider_name` - The package management provider for the source. It supports the following providers: 'Programs', 'msi', 'NuGet', 'msu', 'PowerShellGet', 'psl' and 'chocolatey'.
+- `publish_location` - The url where modules will be published to for this source. Only valid if the provider is 'PowerShellGet'.
+- `script_source_location` - The url where scripts are located for this source. Only valid if the provider is 'PowerShellGet'.
+- `script_publish_location` - The location where scripts will be published to for this source. Only valid if the provider is 'PowerShellGet'.
-### kernel_module
+#### kernel_module
Use the kernel_module resource to manage kernel modules on Linux systems. This resource can load, unload, blacklist, install, and uninstall modules.
-#### Actions
+**Actions**
-* `install` * Load kernel module, and ensure it loads on reboot.
-* `uninstall` * Unload a kernel module and remove module config, so it doesn't load on reboot.
-* `blacklist` * Blacklist a kernel module.
-* `load` * Load a kernel module.
-* `unload` * Unload kernel module
+- `install` - Load kernel module, and ensure it loads on reboot.
+- `uninstall` - Unload a kernel module and remove module config, so it doesn't load on reboot.
+- `blacklist` - Blacklist a kernel module.
+- `load` - Load a kernel module.
+- `unload` - Unload kernel module
-#### Properties
+**Properties**
-* `modname` * The name of the kernel module.
-* `load_dir` * The directory to load modules from.
-* `unload_dir` * The modprobe.d directory.
+- `modname` - The name of the kernel module.
+- `load_dir` - The directory to load modules from.
+- `unload_dir` - The modprobe.d directory.
-### ssh_known_hosts_entry
+#### ssh_known_hosts_entry
Use the ssh_known_hosts_entry resource to add an entry for the specified host in /etc/ssh/ssh_known_hosts or a user's known hosts file if specified.
-#### Actions
+**Actions**
-* `create` * Create an entry in the ssh_known_hosts file.
-* `flush` * Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the Chef run so all entries can be accumulated before writing the file out.
+- `create` - Create an entry in the ssh_known_hosts file.
+- `flush` - Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the Chef run so all entries can be accumulated before writing the file out.
-#### Properties
+**Properties**
-* `host` * The host to add to the known hosts file.
-* `key` * An optional key for the host. If not provided this will be automatically determined.
-* `key_type` * The type of key to store.
-* `port` * The server port that the ssh-keyscan command will use to gather the public key.
-* `timeout` * The timeout in seconds for ssh-keyscan.
-* `mode` * The file mode for the ssh_known_hosts file.
-* `owner`* The file owner for the ssh_known_hosts file.
-* `group` * The file group for the ssh_known_hosts file.
-* `hash_entries` * Hash the hostname and addresses in the ssh_known_hosts file for privacy.
-* `file_location` * The location of the ssh known hosts file. Change this to set a known host file for a particular user.
+- `host` - The host to add to the known hosts file.
+- `key` - An optional key for the host. If not provided this will be automatically determined.
+- `key_type` - The type of key to store.
+- `port` - The server port that the ssh-keyscan command will use to gather the public key.
+- `timeout` - The timeout in seconds for ssh-keyscan.
+- `mode` - The file mode for the ssh_known_hosts file.
+- `owner`- The file owner for the ssh_known_hosts file.
+- `group` - The file group for the ssh_known_hosts file.
+- `hash_entries` - Hash the hostname and addresses in the ssh_known_hosts file for privacy.
+- `file_location` - The location of the ssh known hosts file. Change this to set a known host file for a particular user.
-## New `knife config get` command
+### New `knife config get` command
The `knife config get` command has been added to help with debugging configuration issues with `knife` and other tools that use the `knife.rb` file.
@@ -3301,18 +4421,18 @@ validation_key:
You can also pass specific keys to only display those `knife config get node_name client_key`, or use `--all` to display everything (including options that are using the default value).
-## Simplification of `shell_out` APIs
+### Simplification of `shell_out` APIs
The following helper methods have been deprecated in favor of the single shell_out helper:
-* `shell_out_with_systems_locale`
-* `shell_out_with_timeout`
-* `shell_out_compact`
-* `shell_out_compact_timeout`
-* `shell_out_with_systems_locale!`
-* `shell_out_with_timeout!`
-* `shell_out_compact!`
-* `shell_out_compact_timeout!`
+- `shell_out_with_systems_locale`
+- `shell_out_with_timeout`
+- `shell_out_compact`
+- `shell_out_compact_timeout`
+- `shell_out_with_systems_locale!`
+- `shell_out_with_timeout!`
+- `shell_out_compact!`
+- `shell_out_compact_timeout!`
The functionality of `shell_out_with_systems_locale` has been implemented using the `default_env: false` option that removes the PATH and locale mangling that has been the default behavior of `shell_out`.
@@ -3320,7 +4440,7 @@ The functionality of `shell_out_compact` has been folded into `shell_out`. The `
The functionality of `shell_out*timeout` has also been folded into `shell_out`. Users writing Custom Resources should be explicit for Chef-14: `shell_out!("whatever", timeout: new_resource.timeout)` which will become automatic in Chef-15.
-## Silencing deprecation warnings
+### Silencing deprecation warnings
While deprecation warnings have been great for the Chef community to ensure cookbooks are kept up-to-date and to prepare for major version upgrades, sometimes you just can't fix a deprecation right now. This is often compounded by the recommendation to enable `treat_deprecation_warnings_as_errors` mode in your Test Kitchen integration tests, which doesn't understand the difference between deprecations from community cookbooks and those from your own code.
@@ -3342,9 +4462,9 @@ provisioner:
solo_rb:
treat_deprecation_warnings_as_errors: true
silence_deprecation_warnings:
- * deploy_resource
- * chef-23
- * recipes/install.rb:22
+ - deploy_resource
+ - chef-23
+ - recipes/install.rb:22
```
You can also silence deprecations using a comment on the line that is raising the warning:
@@ -3355,30 +4475,30 @@ erl_call 'something' do # chef:silence_deprecation
We advise caution in the use of this feature, as excessive or prolonged silencing can lead to difficulty upgrading when the next major release of Chef comes out.
-## Misc Windows improvements
+### Misc Windows improvements
-* A new `skip_publisher_check` property has been added to the `powershell_package` resource
-* `windows_feature_powershell` now supports Windows 2008 R2
-* The `mount` resource now supports the `mount_point` property on Windows
-* `windows_feature_dism` no longer errors when specifying the source
-* Resolved idempotency issues in the `windows_task` resource and prevented setting up a task with bad credentials
-* `windows_service` no longer throws Ruby deprecation warnings
+- A new `skip_publisher_check` property has been added to the `powershell_package` resource
+- `windows_feature_powershell` now supports Windows 2008 R2
+- The `mount` resource now supports the `mount_point` property on Windows
+- `windows_feature_dism` no longer errors when specifying the source
+- Resolved idempotency issues in the `windows_task` resource and prevented setting up a task with bad credentials
+- `windows_service` no longer throws Ruby deprecation warnings
-## Newly Introduced Deprecations
+### Newly Introduced Deprecations
-### CHEF-26: Deprecation of old shell_out APIs
+#### CHEF-26: Deprecation of old shell_out APIs
As noted above, this release of Chef unifies our shell_out helpers into just shell_out and shell_out!. Previous helpers are now deprecated and will be removed in Chef Infra Client 15.
See [CHEF-26 Deprecation Page](https://docs.chef.io/deprecations_shell_out) for details.
-### Legacy FreeBSD pkg provider
+#### Legacy FreeBSD pkg provider
Chef Infra Client 15 will remove support for the legacy FreeBSD pkg format. We will continue to support the pkgng format introduced in FreeBSD 10.
-# What's New in 14.2:
+## What's New in 14.2
-## `ssh-agent` support for user keys
+### `ssh-agent` support for user keys
You can now use `ssh-agent` to hold your user key when using knife. This allows storing your user key in an encrypted form as well as using `ssh -A` agent forwarding for running knife commands from remote devices.
@@ -3393,221 +4513,221 @@ chmod 600 user_enc.pem
This will prompt you for a passphrase for to use to encrypt the key. You can then load the key into your `ssh-agent` by running `ssh-add user_enc.pem`. Make sure you add the `ssh_agent_signing` to your configuration, and update your `client_key` to point at the new, encrypted key (and once you've verified things are working, remember to delete your unencrypted key file).
-## default_env Property in Execute Resource
+### default_env Property in Execute Resource
The shell_out helper has been extended with a new option `default_env` to allow disabling Chef from modifying PATH and LOCALE environmental variables as it shells out. This new option defaults to true (modify the env), preserving the previous behavior of the helper.
The execute resource has also been updated with a new property `default_env` that allows utilizing this the ENV sanity functionality in shell_out. The new property defaults to false, but it can be set to true in order to ensure a sane PATH and LOCALE when shelling out. If you find that binaries cannot be found when using the execute resource, `default_env` set to true may resolve those issues.
-## Small Size on Disk
+### Small Size on Disk
Chef now bundles the inspec-core and train-core gems, which omit many cloud dependencies not needed within the Chef client. This change reduces the install size of a typical system by ~22% and the number of files within that installation by ~20% compared to Chef 14.1\. Enjoy the extra disk space.
-## Virtualization detection on AWS
+### Virtualization detection on AWS
Ohai now detects the virtualization hypervisor `amazonec2` when running on Amazon's new C5/M5 instances.
-# What's New in 14.1.12
+## What's New in 14.1.12
This release resolves a number of regressions in 14.1.1:
-* `git` resource: don't use `--prune-tags` as it's really new.
-* `rhsm_repo` resource: now works
-* `apt_repository` resource: use the `repo_name` property to name files
-* `windows_task` resource: properly handle commands with arguments
-* `windows_task` resource: handle creating tasks as the SYSTEM user
-* `remote_directory` resource: restore the default for the `overwrite` property
+- `git` resource: don't use `--prune-tags` as it's really new.
+- `rhsm_repo` resource: now works
+- `apt_repository` resource: use the `repo_name` property to name files
+- `windows_task` resource: properly handle commands with arguments
+- `windows_task` resource: handle creating tasks as the SYSTEM user
+- `remote_directory` resource: restore the default for the `overwrite` property
-## Ohai 14.1.3
+### Ohai 14.1.3
-* Properly detect FIPS environments
-* `shard` plugin: work in FIPS compliant environments
-* `filesystem` plugin: Handle BSD platforms
+- Properly detect FIPS environments
+- `shard` plugin: work in FIPS compliant environments
+- `filesystem` plugin: Handle BSD platforms
-# What's New in 14.1.1
+## What's New in 14.1.1
-## Platform Additions
+### Platform Additions
Enable Ubuntu-18.04 and Debian-9 tested chef-client packages.
-# What's New in 14.1
+## What's New in 14.1
-## Windows Task
+### Windows Task
The `windows_task` resource has been entirely rewritten. This resolves a large number of bugs, including being able to correctly set the start time of tasks, proper creation and deletion of tasks, and improves Chef's validation of tasks. The rewrite will also solve the idempotency problems that users have reported.
-## build_essential
+### build_essential
The `build_essential` resource no longer requires a name, similar to the `apt_update` resource.
-## Ignore Failure
+### Ignore Failure
The `ignore_failure` property takes a new argument, `:quiet`, to suppress the error output when the resource does in fact fail.
-## This release of Chef Client 14 resolves a number of regressions in 14.0
+### This release of Chef Client 14 resolves a number of regressions in 14.0
-* On Windows, the installer now correctly re-extracts files during repair mode
-* Fix a number of issues relating to use with Red Hat Satellite
-* Git fetch now prunes remotes before running
-* Fix locking and unlocking packages with apt and zypper
-* Ensure we don't request every remote file when running with lazy loading enabled
-* The sysctl resource correctly handles missing keys when used with `ignore_error`
-* --recipe-url apparently never worked on Windows. Now it does.
+- On Windows, the installer now correctly re-extracts files during repair mode
+- Fix a number of issues relating to use with Red Hat Satellite
+- Git fetch now prunes remotes before running
+- Fix locking and unlocking packages with apt and zypper
+- Ensure we don't request every remote file when running with lazy loading enabled
+- The sysctl resource correctly handles missing keys when used with `ignore_error`
+- --recipe-url apparently never worked on Windows. Now it does.
-## Security Updates
+### Security Updates
-### ffi Gem
+#### ffi Gem
-* CVE-2018-1000201: DLL loading issue which can be hijacked on Windows OS
+- CVE-2018-1000201: DLL loading issue which can be hijacked on Windows OS
-# Ohai Release Notes 14.1:
+## Ohai Release Notes 14.1
-## Configurable DMI Whitelist
+### Configurable DMI Whitelist
The whitelist of DMI IDs is now user configurable using the `additional_dmi_ids` configuration setting, which takes an Array.
-## Shard plugin
+### Shard plugin
The Shard plugin has been returned to a default plugin rather than an optional one. To ensure we work in FIPS environments, the plugin will use SHA256 rather than MD5 in those environments.
-## SCSI plugin
+### SCSI plugin
A new plugin to enumerate SCSI devices has been added. This plugin is optional.
-# What's New in 14.0.202
+## What's New in 14.0.202
This release of Chef 14 resolves several regressions in the Chef 14.0 release.
-* Resources contained in cookbooks would be used instead of built-in Chef client resources causing older resources to run
-* Resources failed due to a missing `property_is_set?` and `resources` methods
-* `yum_package` changed the order of `disablerepo` and `enablerepo` options
-* Depsolving large numbers of cookbooks with chef zero/local took a very long time
+- Resources contained in cookbooks would be used instead of built-in Chef client resources causing older resources to run
+- Resources failed due to a missing `property_is_set?` and `resources` methods
+- `yum_package` changed the order of `disablerepo` and `enablerepo` options
+- Depsolving large numbers of cookbooks with chef zero/local took a very long time
-# What's New in 14.0
+## What's New in 14.0
-## New Resources
+### New Resources
Chef 14 includes a large number of resources ported from community cookbooks. These resources have been tested, improved, and had their functionality expanded. With these new resources in the Chef Client itself, the need for external cookbook dependencies and dependency management has been greatly reduced.
-### build_essential
+#### build_essential
Use the build_essential resource to install packages required for compiling C software from source. This resource was ported from the `build-essential` community cookbook.
`Note`: This resource no longer configures msys2 on Windows systems.
-### chef_handler
+#### chef_handler
Use the chef_handler resource to install or uninstall Chef reporting/exception handlers. This resource was ported from the `chef_handler` community cookbook.
-### dmg_package
+#### dmg_package
Use the dmg_package resource to install a dmg 'package'. The resource will retrieve the dmg file from a remote URL, mount it using hdiutil, copy the application (.app directory) to the specified destination (/Applications), and detach the image using hdiutil. The dmg file will be stored in the Chef::Config[:file_cache_path]. This resource was ported from the `dmg` community cookbook.
-### homebrew_cask
+#### homebrew_cask
Use the homebrew_cask resource to install binaries distributed via the Homebrew package manager. This resource was ported from the `homebrew` community cookbook.
-### homebrew_tap
+#### homebrew_tap
Use the homebrew_tap resource to add additional formula repositories to the Homebrew package manager. This resource was ported from the `homebrew` community cookbook.
-### hostname
+#### hostname
Use the hostname resource to set the system's hostname, configure hostname and hosts config file, and re-run the Ohai hostname plugin so the hostname will be available in subsequent cookbooks. This resource was ported from the `chef_hostname` community cookbook.
-### macos_userdefaults
+#### macos_userdefaults
Use the macos_userdefaults resource to manage the macOS user defaults system. The properties of this resource are passed to the defaults command, and the parameters follow the convention of that command. See the defaults(1) man page for details on how the tool works. This resource was ported from the `mac_os_x` community cookbook.
-### ohai_hint
+#### ohai_hint
Use the ohai_hint resource to pass hint data to Ohai to aid in configuration detection. This resource was ported from the `ohai` community cookbook.
-### openssl_dhparam
+#### openssl_dhparam
Use the openssl_dhparam resource to generate dhparam.pem files. If a valid dhparam.pem file is found at the specified location, no new file will be created. If a file is found at the specified location but it is not a valid dhparam file, it will be overwritten. This resource was ported from the `openssl` community cookbook.
-### openssl_rsa_private_key
+#### openssl_rsa_private_key
Use the openssl_rsa_private_key resource to generate RSA private key files. If a valid RSA key file can be opened at the specified location, no new file will be created. If the RSA key file cannot be opened, either because it does not exist or because the password to the RSA key file does not match the password in the recipe, it will be overwritten. This resource was ported from the `openssl` community cookbook.
-### openssl_rsa_public_key
+#### openssl_rsa_public_key
Use the openssl_rsa_public_key resource to generate RSA public key files given a RSA private key. This resource was ported from the `openssl` community cookbook.
-### rhsm_errata
+#### rhsm_errata
Use the rhsm_errata resource to install packages associated with a given Red Hat Subscription Manager Errata ID. This is helpful if packages to mitigate a single vulnerability must be installed on your hosts. This resource was ported from the `redhat_subscription_manager` community cookbook.
-### rhsm_errata_level
+#### rhsm_errata_level
Use the rhsm_errata_level resource to install all packages of a specified errata level from the Red Hat Subscription Manager. For example, you can ensure that all packages associated with errata marked at a 'Critical' security level are installed. This resource was ported from the `redhat_subscription_manager` community cookbook.
-### rhsm_register
+#### rhsm_register
Use the rhsm_register resource to register a node with the Red Hat Subscription Manager or a local Red Hat Satellite server. This resource was ported from the `redhat_subscription_manager` community cookbook.
-### rhsm_repo
+#### rhsm_repo
Use the rhsm_repo resource to enable or disable Red Hat Subscription Manager repositories that are made available via attached subscriptions. This resource was ported from the `redhat_subscription_manager` community cookbook.
-### rhsm_subscription
+#### rhsm_subscription
Use the rhsm_subscription resource to add or remove Red Hat Subscription Manager subscriptions for your host. This can be used when a host's activation_key does not attach all necessary subscriptions to your host. This resource was ported from the `redhat_subscription_manager` community cookbook.
-### sudo
+#### sudo
Use the sudo resource to add or remove individual sudo entries using `sudoers.d` files. Sudo version 1.7.2 or newer is required to use the sudo resource, as it relies on the `#includedir` directive introduced in version 1.7.2\. This resource does not enforce installation of the required sudo version. Supported releases of Ubuntu, Debian, SuSE, and RHEL (6+) all support this feature. This resource was ported from the `sudo` community cookbook.
-### swap_file
+#### swap_file
Use the swap_file resource to create or delete swap files on Linux systems, and optionally to manage the swappiness configuration for a host. This resource was ported from the `swap` community cookbook.
-### sysctl
+#### sysctl
Use the sysctl resource to set or remove kernel parameters using the sysctl command line tool and configuration files in the system's `sysctl.d` directory. Configuration files managed by this resource are named 99-chef-KEYNAME.conf. If an existing value was already set for the value it will be backed up to the node and restored if the :remove action is used later. This resource was ported from the `sysctl` community cookbook.
`Note`: This resource no longer backs up existing key values to the node when changing values as we have done in the sysctl cookbook previously. The resource has also been renamed from `sysctl_param` to `sysctl` with backwards compatibility for the previous name.
-### windows_ad_join
+#### windows_ad_join
Use the windows_ad_join resource to join a Windows Active Directory domain and reboot the node. This resource is based on the `win_ad_client` resource in the `win_ad` community cookbook, but is not backwards compatible with that resource.
-### windows_auto_run
+#### windows_auto_run
Use the windows_auto_run resource to set applications to run at logon. This resource was ported from the `windows` community cookbook.
-### windows_feature
+#### windows_feature
Use the windows_feature resource to add, remove or delete Windows features and roles. This resource calls the `windows_feature_dism` or `windows_feature_powershell` resources depending on the specified installation method and defaults to dism, which is available on both Workstation and Server editions of Windows. This resource was ported from the `windows` community cookbook.
`Note`: These resources received significant refactoring in the 4.0 version of the windows cookbook (March 2018). windows_feature resources now fail if the installation of invalid features is requested and support for installation via server `servermanagercmd.exe` has been removed. If you are using a windows cookbook version less than 4.0 you may need to update cookbooks for Chef 14.
-### windows_font
+#### windows_font
Use the windows_font resource to install or remove font files on Windows. By default, the font is sourced from the cookbook using the resource, but a URI source can be specified as well. This resource was ported from the `windows` community cookbook.
-### windows_printer
+#### windows_printer
Use the windows_printer resource to setup Windows printers. Note that this doesn't currently install a printer driver. You must already have the driver installed on the system. This resource was ported from the `windows` community cookbook.
-### windows_printer_port
+#### windows_printer_port
Use the windows_printer_port resource to create and delete TCP/IPv4 printer ports on Windows. This resource was ported from the `windows` community cookbook.
-### windows_shortcut
+#### windows_shortcut
Use the windows_shortcut resource to create shortcut files on Windows. This resource was ported from the `windows` community cookbook.
-### windows_workgroup
+#### windows_workgroup
Use the windows_workgroup resource to join a Windows Workgroup and reboot the node. This resource is based on the `windows_ad_join` resource.
-## Custom Resource Improvements
+### Custom Resource Improvements
We've expanded the DSL for custom resources with new functionality to better document your resources and help users with errors and upgrades. Many resources in Chef itself are now using this new functionality, and you'll see more updated to take advantage of this it in the future.
-### Deprecations in Cookbook Resources
+#### Deprecations in Cookbook Resources
Chef 14 provides new primitives that allow you to deprecate resources or properties with the same functionality used for deprecations in Chef Client resources. This allows you make breaking changes to enterprise or community cookbooks with friendly notifications to downstream cookbook consumers directly in the Chef run.
@@ -3635,11 +4755,11 @@ Rename a property with a deprecation warning for users of the old property name
deprecated_property_alias 'thing2', 'the_second_thing', 'The thing2 property was renamed the_second_thing in the 2.0 release of this cookbook. Please update your cookbooks to use the new property name.'
```
-### Platform Deprecations
+#### Platform Deprecations
chef-client no longer is built or tested on OS X 10.10 in accordance with Chef's EOL policy.
-### validation_message
+#### validation_message
Validation messages allow you give the user a friendly error message when any validation on a property fails.
@@ -3649,7 +4769,7 @@ Provide a friendly message when a regex fails:
property :repo_name, String, regex: [/^[^\/]+$/], validation_message: "The repo_name property cannot contain a forward slash '/'",
```
-### Resource Documentation
+#### Resource Documentation
You can now include documentation that describes how a resource is to be used. Expect this data to be consumed by Chef and other tooling in future releases.
@@ -3671,95 +4791,95 @@ action :add do
end
```
-## Improved Resources
+### Improved Resources
Many existing resources now include new actions and properties that expand their functionality.
-### apt_package
+#### apt_package
`apt_package` includes a new `overwrite_config_files` property. Setting this new property to true is equivalent to passing `-o Dpkg::Options::="--force-confnew"` to apt, and allows you to install packages that prompt the user to overwrite config files. Thanks @ccope for this new property.
-### env
+#### env
The `env` resource has been renamed to `windows_env` as it only supports the Windows platform. Existing cookbooks using `env` will continue to function, but should be updated to use the new name.
-### ifconfig
+#### ifconfig
`ifconfig` includes a new `family` property for setting the network family on Debian systems. Thanks @martinisoft for this new property.
-### registry_key
+#### registry_key
The `sensitive` property can now be used in `registry_key` to suppress the output of the key's data from logs and error messages. Thanks @shoekstra for implementing this.
-### powershell_package
+#### powershell_package
`powershell_package` includes a new `source` property to allow specifying the source of the package. Thanks @Happycoil for this new property.
-### systemd_unit
+#### systemd_unit
`systemd_unit` includes the following new actions:
-* `preset` * Restore the preset enable/disable configuration for a unit
-* `revert` * Revert to a vendor's version of a unit file
-* `reenable` * Reenable a unit file
+- `preset` - Restore the preset enable/disable configuration for a unit
+- `revert` - Revert to a vendor's version of a unit file
+- `reenable` - Reenable a unit file
Thanks @nathwill for these new actions.
-### windows_service
+#### windows_service
`windows_service` now includes actions for fully managing services on Windows, in addition to the previous actions for starting/stopping/enabling services.
-* `create` * Create a new service
-* `delete` * Delete an existing service
-* `configure` * Reconfigure an existing service
+- `create` - Create a new service
+- `delete` - Delete an existing service
+- `configure` - Reconfigure an existing service
Thanks @jasonwbarnett for these new actions
-### route
+#### route
`route` includes a new `comment` property.
Thanks Thomas Doherty for adding this new property.
-## Expanded Configuration Detection
+### Expanded Configuration Detection
Ohai has been expanded to collect more information than ever. This should make writing cross-platform and cross cloud cookbooks simpler.
-### Windows Kernel information
+#### Windows Kernel information
The kernel plugin now reports the following information on Windows:
-* `node['kernel']['product_type']` * Workstation vs. Server editions of Windows
-* `node['kernel']['system_type']` * What kind of hardware are we installed on (Desktop, Mobile, Workstation, Enterprise Server, etc.)
-* `node['kernel']['server_core']` * Are we on Windows Server Core edition?
+- `node['kernel']['product_type']` - Workstation vs. Server editions of Windows
+- `node['kernel']['system_type']` - What kind of hardware are we installed on (Desktop, Mobile, Workstation, Enterprise Server, etc.)
+- `node['kernel']['server_core']` - Are we on Windows Server Core edition?
-### Cloud Detection
+#### Cloud Detection
Ohai now detects the Scaleway cloud and provides additional configuration information for systems running on Azure.
-### Virtualization / Container Detection
+#### Virtualization / Container Detection
In addition to detecting if a system is a Docker host, we now provide a large amount of Docker configuration information available at `node['docker']`. This includes the release of Docker, installed plugins, network config, and the number of running containers.
Ohai also now properly detects LXD containers and macOS guests running on VirtualBox / VMware. This data is available in `node['virtualization']['systems']`.
-### Optional Ohai Plugins
+#### Optional Ohai Plugins
Ohai now includes the ability to mark plugins as optional, which skips those plugins by default. This allows us to ship additional plugins, which some users may find useful, but not all users want that data collected in the node object on a Chef server. The change introduces two new configuration options; `run_all_plugins` which runs everything including optional plugins, and `optional_plugins` which allows you to run plugins marked as optional.
By default we will now be marking the `lspci`, `sessions` `shard` and `passwd` plugins as optional. Passwd has been particularly problematic for nodes attached to LDAP or AD where it attempts to write the entire directory's contents to the node. If you previously disabled this plugin via Ohai config, you no longer need to. Hurray!
-## Other Changes
+### Other Changes
-### Ruby 2.5
+#### Ruby 2.5
Ruby has been updated to version 2.5 bringing a 10% performance improvement and improved functionality.
-### InSpec 2.0
+#### InSpec 2.0
-InSpec has been updated to the 2.0 release. InSpec 2.0 brings compliance automation to the cloud, with new resource types specifically built for AWS and Azure clouds. Along with these changes are major speed improvements and quality of life updates. Please visit <https://www.inspec.io/> for more information.
+InSpec has been updated to the 2.0 release. InSpec 2.0 brings compliance automation to the cloud, with new resource types specifically built for AWS and Azure clouds. Along with these changes are major speed improvements and quality of life updates. Please visit <https://docs.chef.io/inspec/> for more information.
-### Policyfile Hoisting
+#### Policyfile Hoisting
Many users of Policyfiles rely on "hoisting" to provide group specific attributes. This approach was formalized in the poise-hoist extension, and is now included in Chef 14.
@@ -3777,7 +4897,7 @@ node['myapp']['title']
The correct attribute is then provided based on the policy_group of the node, so with a policy_group of staging the attribute would contain "My Staging App".
-### yum_package rewrite
+#### yum_package rewrite
yum_package received a ground up rewrite that greatly improves both the performance and functionality while also resolving a dozen existing issues. It introduces a new caching method that runs for the duration of the chef-client process. This caching method speeds up each package install and takes 1/2 the memory of the previous `yum-dump.py` process.
@@ -3795,103 +4915,103 @@ Installing a package via what it provides:
yum_package "perl(Git)"
```
-### powershell_exec Mixin
+#### powershell_exec Mixin
Since our supported Windows platforms can all run .NET Framework 4.0 and PowerShell 4.0 we have taken time to add a new helper that will allow for faster and safer interactions with the system PowerShell. You will be able to use the powershell_exec mixin in most places where you would have previously used powershell_out. For comparison, a basic benchmark test to return the $PSVersionTable 100 times completed 7.3X faster compared to the powershell_out method. The majority of the time difference is because of less time spent in invocation. So we believe it has big future potential where multiple calls to PowerShell are required inside (for example) a custom resource. Many core Chef resources will be updated to use this new mixin in future releases.
-### Logging Improvements
+#### Logging Improvements
Chef now includes a new log level of `:trace` in addition to the existing `:info`, `:warn`, and `:debug` levels. With the introduction of `trace` level logging we've moved a large amount of logging that is more useful for Chef developers from `debug` to `trace`. This makes it easier for Chef Cookbook developers to use `debug` level to get useful information.
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2o to resolve [CVE-2018-0739](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0739)
-### Ruby
+#### Ruby
Ruby has been updated to 2.5.1 to resolve the following vulnerabilities:
-* [cve-2017-17742](https://www.ruby-lang.org/en/news/2018/03/28/http-response-splitting-in-webrick-cve-2017-17742/)
-* [cve-2018-6914](https://www.ruby-lang.org/en/news/2018/03/28/unintentional-file-and-directory-creation-with-directory-traversal-cve-2018-6914/)
-* [cve-2018-8777](https://www.ruby-lang.org/en/news/2018/03/28/large-request-dos-in-webrick-cve-2018-8777/)
-* [cve-2018-8778](https://www.ruby-lang.org/en/news/2018/03/28/buffer-under-read-unpack-cve-2018-8778/)
-* [cve-2018-8779](https://www.ruby-lang.org/en/news/2018/03/28/poisoned-nul-byte-unixsocket-cve-2018-8779/)
-* [cve-2018-8780](https://www.ruby-lang.org/en/news/2018/03/28/poisoned-nul-byte-dir-cve-2018-8780/)
-* [Multiple vulnerabilities in rubygems](https://www.ruby-lang.org/en/news/2018/02/17/multiple-vulnerabilities-in-rubygems/)
+- [CVE-2017-17742](https://www.ruby-lang.org/en/news/2018/03/28/http-response-splitting-in-webrick-cve-2017-17742/)
+- [CVE-2018-6914](https://www.ruby-lang.org/en/news/2018/03/28/unintentional-file-and-directory-creation-with-directory-traversal-cve-2018-6914/)
+- [CVE-2018-8777](https://www.ruby-lang.org/en/news/2018/03/28/large-request-dos-in-webrick-cve-2018-8777/)
+- [CVE-2018-8778](https://www.ruby-lang.org/en/news/2018/03/28/buffer-under-read-unpack-cve-2018-8778/)
+- [CVE-2018-8779](https://www.ruby-lang.org/en/news/2018/03/28/poisoned-nul-byte-unixsocket-cve-2018-8779/)
+- [CVE-2018-8780](https://www.ruby-lang.org/en/news/2018/03/28/poisoned-nul-byte-dir-cve-2018-8780/)
+- [Multiple vulnerabilities in rubygems](https://www.ruby-lang.org/en/news/2018/02/17/multiple-vulnerabilities-in-rubygems/)
-## Breaking Changes
+### Breaking Changes
This release completes the deprecation process for many of the deprecations that were warnings throughout the Chef 12 and Chef 13 releases.
-### erl_call Resource
+#### erl_call Resource
The erl_call resource was deprecated in Chef 13.7 and has been removed.
-### deploy Resource
+#### deploy Resource
The deploy resource was deprecated in Chef 13.6 and been removed. If you still require this resource, it is available in the new `deploy_resource` cookbook at <https://supermarket.chef.io/cookbooks/deploy_resource>
-### Windows 2003 Support
+#### Windows 2003 Support
Support for Windows 2003 has been removed from both Chef and Ohai, improving the performance of Chef on Windows hosts.
-### knife deprecations
+#### knife deprecations
-* `knife bootstrap` options `--distro` and `--template_file` flags were deprecated in Chef 12 and have now been removed.
-* `knife help` functionality that read legacy Chef manpages has been removed as the manpages had not been updated and were often quite wrong. Running knife help will now simply show the help menu.
-* `knife index rebuild` has been removed as reindexing Chef Server was only necessary on releases prior to Chef Server 11.
-* The `knife ssh --identity-file` flag was deprecated and has been removed. Users should use the `--ssh_identity_file` flag instead.
-* `knife ssh csshx` was deprecated in Chef 10 and has been removed. Users should use `knife ssh cssh` instead.
+- `knife bootstrap` options `--distro` and `--template_file` flags were deprecated in Chef 12 and have now been removed.
+- `knife help` functionality that read legacy Chef manpages has been removed as the manpages had not been updated and were often quite wrong. Running knife help will now simply show the help menu.
+- `knife index rebuild` has been removed as reindexing Chef Server was only necessary on releases prior to Chef Server 11.
+- The `knife ssh --identity-file` flag was deprecated and has been removed. Users should use the `--ssh_identity_file` flag instead.
+- `knife ssh csshx` was deprecated in Chef 10 and has been removed. Users should use `knife ssh cssh` instead.
-### Chef Solo `-r` flag
+#### Chef Solo `-r` flag
The Chef Solo `-r` flag has been removed as it was deprecated and replaced with the `--recipe-url` flag in Chef 12.
-### node.set and node.set_unless attribute levels removal
+#### node.set and node.set_unless attribute levels removal
`node.set` and `node.set_unless` were deprecated in Chef 12 and have been removed in Chef 14\. To replicate this same functionality users should use `node.normal` and `node.normal_unless`, although we highly recommend reading our [attribute documentation](https://docs.chef.io/attributes) to make sure `normal` is in fact the your desired attribute level.
-### chocolatey_package :uninstall Action
+#### chocolatey_package :uninstall Action
The chocolatey_package resource in the chocolatey cookbook supported an `:uninstall` action. When this resource was moved into the Chef Client we allowed this action with a deprecation warning. This action is now removed.
-### Property names not using new_resource.NAME
+#### Property names not using new_resource.NAME
Previously if a user wrote a custom resource with a property named `foo` they could reference it throughout the resource using the name `foo`. This caused multiple edge cases where the property name could conflict with resources or methods in Chef. Properties now must be referenced as `new_resource.foo`. This was already the case when writing LWRPs.
-### epic_fail
+#### epic_fail
The original name for the `ignore_failure` property in resource was `epic_fail`. The legacy name has been removed.
-### Legacy Mixins
+#### Legacy Mixins
Several legacy mixins mostly used in older HWRPs have been removed. Usage of these mixins has resulted in deprecation warnings for several years and they are rarely used in cookbooks available on the Supermarket.
-* Chef::Mixin::LanguageIncludeAttribute
-* Chef::Mixin::RecipeDefinitionDSLCore
-* Chef::Mixin::LanguageIncludeRecipe
-* Chef::Mixin::Language
-* Chef::DSL::Recipe::FullDSL
+- Chef::Mixin::LanguageIncludeAttribute
+- Chef::Mixin::RecipeDefinitionDSLCore
+- Chef::Mixin::LanguageIncludeRecipe
+- Chef::Mixin::Language
+- Chef::DSL::Recipe::FullDSL
-### cloud_v2 and filesystem2 Ohai Plugins
+#### cloud_v2 and filesystem2 Ohai Plugins
In Chef 13 the `cloud_v2` plugin replaced data at `node['cloud']` and `filesystem2` replaced data at `node['filesystem']`. For compatibility with cookbooks that were previously using the "v2" data we continued to write data to both locations (ie: both node['filesystem'] and node['filesystem2']). We now no longer write data to the "v2" locations which greatly reduces the amount of data we need to store on the Chef server.
-### Ipscopes Ohai Plugin Removed
+#### Ipscopes Ohai Plugin Removed
The ipscopes plugin has been removed as it duplicated data already present in the network plugins and required the user to install an additional gem into the Chef installation.
-### Ohai libvirt attributes moved
+#### Ohai libvirt attributes moved
The libvirt Ohai plugin now writes data to `node['libvirt']` instead of writing to various locations in `node['virtualization']`. This plugin required installing an additional gem into the Chef installation and thus was infrequently used.
-### Ohai Plugin V6 Support Removed
+#### Ohai Plugin V6 Support Removed
In 2014 we introduced Ohai v7 with a greatly improved plugin format. With Chef 14 we no longer support loading of the legacy "v6" plugin format.
-### Newly-disabled Ohai Plugins
+#### Newly-disabled Ohai Plugins
As mentioned above we now support an `optional` flag for Ohai plugins and have marked the `sessions`, `lspci`, and `passwd` plugins as optional, which disables them by default. If you need one of these plugins you can include them using `optional_plugins`.
@@ -3901,186 +5021,188 @@ optional_plugins in the client.rb file:
optional_plugins [ "lspci", "passwd" ]
```
-# What's New in 13.12.14
+## What's New in 13.12.14
-## Bugfixes
+### Bugfixes
-* The mount provider now properly adds blank lines between fstab entries on AIX
-* Ohai now reports itself as Ohai well communicating with GCE metadata endpoints
-* Property deprecations in custom resources no longer result in an error. Thanks for reporting this [martinisoft](https://github.com/martinisoft)
-* mixlib-archive has been updated to prevent corruption of archives on Windows systems
+- The mount provider now properly adds blank lines between fstab entries on AIX
+- Ohai now reports itself as Ohai well communicating with GCE metadata endpoints
+- Property deprecations in custom resources no longer result in an error. Thanks for reporting this [martinisoft](https://github.com/martinisoft)
+- mixlib-archive has been updated to prevent corruption of archives on Windows systems
-## Updated Components
+### Updated Components
-* libxml2 2.9.7 -> 2.9.9
-* ca-certs updated to 2019-01-22 for new roots
-* nokogiri 1.8.5 -> 1.10.1
+- libxml2 2.9.7 -> 2.9.9
+- ca-certs updated to 2019-01-22 for new roots
+- nokogiri 1.8.5 -> 1.10.1
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
OpenSSL has been updated to 1.0.2r in order to resolve [CVE-2019-1559](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559) and [CVE-2018-5407](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-5407)
-### RubyGems
+#### RubyGems
RubyGems has been updated to 2.7.9 in order to resolve the following CVEs:
- * [CVE-2019-8320](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8320): Delete directory using symlink when decompressing tar
- * [CVE-2019-8321](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8321): Escape sequence injection vulnerability in verbose
- * [CVE-2019-8322](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8322): Escape sequence injection vulnerability in gem owner
- * [CVE-2019-8323](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8323): Escape sequence injection vulnerability in API response handling
- * [CVE-2019-8324](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8324): Installing a malicious gem may lead to arbitrary code execution
- * [CVE-2019-8325](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8325): Escape sequence injection vulnerability in errors
-# What's New in 13.12.3
+- [CVE-2019-8320](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8320): Delete directory using symlink when decompressing tar
+- [CVE-2019-8321](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8321): Escape sequence injection vulnerability in verbose
+- [CVE-2019-8322](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8322): Escape sequence injection vulnerability in gem owner
+- [CVE-2019-8323](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8323): Escape sequence injection vulnerability in API response handling
+- [CVE-2019-8324](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8324): Installing a malicious gem may lead to arbitrary code execution
+- [CVE-2019-8325](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-8325): Escape sequence injection vulnerability in errors
-## Smaller Package and Install Size
+## What's New in 13.12.3
+
+### Smaller Package and Install Size
We trimmed unnecessary installation files, greatly reducing the sizes of both Chef packages and on disk installations. MacOS/Linux/FreeBSD packages are ~50% smaller and Windows are ~12% smaller. Chef 13 is now smaller than a legacy Chef 10 package.
-## macOS Mojave (10.14)
+### macOS Mojave (10.14)
Chef is now tested against macOS Mojave and packages are now available at downloads.chef.io.
-## SUSE Linux Enterprise Server 15
+### SUSE Linux Enterprise Server 15
-* Ohai now properly detects SLES 15
-* The Chef package will no longer remove symlinks to chef-client and ohai when upgrading on SLES 15
+- Ohai now properly detects SLES 15
+- The Chef package will no longer remove symlinks to chef-client and ohai when upgrading on SLES 15
-## Updated Chef-Vault
+### Updated Chef-Vault
Updating chef-vault to 3.4.2 resolved multiple bugs.
-## Faster Windows Installations
+### Faster Windows Installations
Improved Windows installation speed by skipping unnecessary steps when Windows Installer 5.0 or later is available.
-## Ohai Release Notes 13.12
+### Ohai Release Notes 13.12
-### macOS Improvements
+#### macOS Improvements
-* sysctl commands have been modified to gather only the bare minimum required data, which prevents sysctl hanging in some scenarios
-* Extra data has been removed from the system_profile plugin, reducing the amount of data stored on the chef-server for each node
+- sysctl commands have been modified to gather only the bare minimum required data, which prevents sysctl hanging in some scenarios
+- Extra data has been removed from the system_profile plugin, reducing the amount of data stored on the chef-server for each node
-## New Deprecations
+### New Deprecations
-### system_profile Ohai plugin removal
+#### system_profile Ohai plugin removal
The system_profile plugin will be removed from Chef/Ohai 15 in April, 2019. This plugin incorrectly returns data on modern Mac systems. Further, the hardware plugin returns the same data in a more readily consumable format. Removing this plugin reduces the speed of the Ohai return by ~3 seconds and also greatly reduces the node object size on the Chef server
-### ohai_name property in ohai resource
+#### ohai_name property in ohai resource
The ``ohai`` resource's unused ``ohai_name`` property has been deprecated. This will be removed in Chef Infra Client 15.0.
-## Security Updates
+### Security Updates
-### Ruby 2.4.5
+#### Ruby 2.4.5
Ruby has been updated to from 2.4.4 to 2.4.5 to resolve multiple CVEs as well as bugs:
-* [CVE-2018-16396](https://www.ruby-lang.org/en/news/2018/10/17/not-propagated-taint-flag-in-some-formats-of-pack-cve-2018-16396/)
-* [CVE-2018-16395](https://www.ruby-lang.org/en/news/2018/10/17/openssl-x509-name-equality-check-does-not-work-correctly-cve-2018-16395/)
-# What's New in 13.11
+- [CVE-2018-16396](https://www.ruby-lang.org/en/news/2018/10/17/not-propagated-taint-flag-in-some-formats-of-pack-cve-2018-16396/)
+- [CVE-2018-16395](https://www.ruby-lang.org/en/news/2018/10/17/openssl-x509-name-equality-check-does-not-work-correctly-cve-2018-16395/)
+
+## What's New in 13.11
### Sensitive Properties on Windows
-* `windows_service` no longer logs potentially sensitive information when a service is setup
-* `windows_package` now respects the `sensitive` property to avoid logging sensitive data in the event of a package installation failure
+- `windows_service` no longer logs potentially sensitive information when a service is setup
+- `windows_package` now respects the `sensitive` property to avoid logging sensitive data in the event of a package installation failure
### Other Fixes
-* `remote_directory` now properly loads files in the root of a cookbook's `files` directory
-* `osx_profile` now uses the full path the profiles CLI tool to avoid running other binaries of the same name in a users path
-* `package` resources that don't support the `allow_downgrade` property will no longer fail
-* `knife bootstrap windows` error messages have been improved
+- `remote_directory` now properly loads files in the root of a cookbook's `files` directory
+- `osx_profile` now uses the full path the profiles CLI tool to avoid running other binaries of the same name in a users path
+- `package` resources that don't support the `allow_downgrade` property will no longer fail
+- `knife bootstrap windows` error messages have been improved
-## Security Updates
+### Security Updates
-### OpenSSL
+#### OpenSSL
-* OpenSSL has been updated to 1.0.2p to resolve [CVE-2018-0732](https://nvd.nist.gov/vuln/detail/CVE-2018-0732) and [CVE-2018-0737](https://nvd.nist.gov/vuln/detail/CVE-2018-0737)
+- OpenSSL has been updated to 1.0.2p to resolve [CVE-2018-0732](https://nvd.nist.gov/vuln/detail/CVE-2018-0732) and [CVE-2018-0737](https://nvd.nist.gov/vuln/detail/CVE-2018-0737)
-### Rubyzip
+#### Rubyzip
-* Updated Rubyzip to 1.2.2 to resolve [CVE-2018-1000544](https://nvd.nist.gov/vuln/detail/CVE-2018-1000544)
+- Updated Rubyzip to 1.2.2 to resolve [CVE-2018-1000544](https://nvd.nist.gov/vuln/detail/CVE-2018-1000544)
-# What's New in 13.10
+## What's New in 13.10
-## Bugfixes
+### Bugfixes
-* Resolves a duplicate logging getting created when redirecting stdout
-* Using --recipe-url with a local file on Windows no longer fails
-* Service resource no longer throws Ruby deprecation warnings on Windows
+- Resolves a duplicate logging getting created when redirecting stdout
+- Using --recipe-url with a local file on Windows no longer fails
+- Service resource no longer throws Ruby deprecation warnings on Windows
-## Ohai 13.10 Improvements
+### Ohai 13.10 Improvements
-* Correctly identify the platform_version on the final release of Amazon Linux 2.0
-* Detect nodes with the DMI data of "OpenStack Compute" as being OpenStack nodes
+- Correctly identify the platform_version on the final release of Amazon Linux 2.0
+- Detect nodes with the DMI data of "OpenStack Compute" as being OpenStack nodes
-## Security Updates
+### Security Updates
-### ffi Gem
+#### ffi Gem
-* CVE-2018-1000201: DLL loading issue which can be hijacked on Windows OS
+- CVE-2018-1000201: DLL loading issue which can be hijacked on Windows OS
-# What's New in 13.9.X:
+## What's New in 13.9.4
-## Security Updates
+### Platform Updates
-Ruby has been updated to 2.4.4
+As Debian 7 is now end of life we will no longer produce Debian 7 chef-client packages.
-* CVE-2017-17742: HTTP response splitting in WEBrick
-* CVE-2018-6914: Unintentional file and directory creation with directory traversal in tempfile and tmpdir
-* CVE-2018-8777: DoS by large request in WEBrick
-* CVE-2018-8778: Buffer under-read in String#unpack
-* CVE-2018-8779: Unintentional socket creation by poisoned NUL byte in UNIXServer and UNIXSocket
-* CVE-2018-8780: Unintentional directory traversal by poisoned NUL byte in Dir
-* Multiple vulnerabilities in RubyGems
+### Ifconfig on Ubuntu 18.04
-Nokogiri has been updated to 1.8.2
+Incompatibilities with Ubuntu 18.04 in the ifconfig resource have been resolved.
-* [MRI] Behavior in libxml2 has been reverted which caused CVE-2018-8048 (loofah gem), CVE-2018-3740 (sanitize gem), and CVE-2018-3741 (rails-html-sanitizer gem).
+### Ohai Updated to 13.9.2
-OpenSSL has been updated to 1.0.2o
+#### Virtualization detection on AWS
-* CVE-2018-0739: Constructed ASN.1 types with a recursive definition could exceed the stack.
+Ohai now detects the virtualization hypervisor `amazonec2` when running on Amazon's new C5/M5 instances.
-## Platform Updates
+#### Configurable DMI Whitelist
-As Debian 7 is now end of life we will no longer produce Debian 7 chef-client packages.
+The whitelist of DMI IDs is now user configurable using the `additional_dmi_ids` configuration setting, which takes an Array.
-## Ifconfig on Ubuntu 18.04
+#### Filesystem2 on BSD
-Incompatibilities with Ubuntu 18.04 in the ifconfig resource have been resolved.
+The Filesystem2 functionality has been backported to BSD systems to provide a consistent filesystem format.
-## Ohai Updated to 13.9.2
+### Security Updates
-### Virtualization detection on AWS
+#### Ruby updated to 2.4.4
-Ohai now detects the virtualization hypervisor `amazonec2` when running on Amazon's new C5/M5 instances.
+- [CVE-2017-17742](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-17742/): HTTP response splitting in WEBrick
+- [CVE-2018-6914](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-6914/): Unintentional file and directory creation with directory traversal in tempfile and tmpdir
+- [CVE-2018-8777](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8777/): DoS by large request in WEBrick
+- [CVE-2018-8778](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8778/): Buffer under-read in String#unpack
+- [CVE-2018-8779](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8779/): Unintentional socket creation by poisoned NUL byte in UNIXServer and UNIXSocket
+- [CVE-2018-8780](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8780/): Unintentional directory traversal by poisoned NUL byte in Dir
+- Multiple vulnerabilities in RubyGems
-### Configurable DMI Whitelist
+#### Nokogiri updated to 1.8.2
-The whitelist of DMI IDs is now user configurable using the `additional_dmi_ids` configuration setting, which takes an Array.
+- Behavior in libxml2 has been reverted which caused CVE-2018-8048 (loofah gem), CVE-2018-3740 (sanitize gem), and CVE-2018-3741 (rails-html-sanitizer gem).
-### Filesystem2 on BSD
+#### OpenSSL updated to 1.0.2o
-The Filesystem2 functionality has been backported to BSD systems to provide a consistent filesystem format.
+- CVE-2018-0739: Constructed ASN.1 types with a recursive definition could exceed the stack.
-# What's New in 13.9.1:
+## What's New in 13.9.1
## Platform Additions
Enable Ubuntu-18.04 and Debian-9 tested chef-client packages.
-# What's New in 13.9:
+## What's New in 13.9.0
-* On Windows, the installer now correctly re-extracts files during repair mode
-* The mount resource will now not create duplicate entries when the device type differs
-* Ensure we don't request every remote file when running with lazy loading enabled
-* Don't crash when getting the access rights for Windows system accounts
+- On Windows, the installer now correctly re-extracts files during repair mode
+- The mount resource will now not create duplicate entries when the device type differs
+- Ensure we don't request every remote file when running with lazy loading enabled
+- Don't crash when getting the access rights for Windows system accounts
-## Custom Resource Improvements
+### Custom Resource Improvements
We've expanded the DSL for custom resources with new functionality to better document your resources and help users with errors and upgrades. Many resources in Chef itself are now using this new functionality, and you'll see more updated to take advantage of this it in the future.
@@ -4130,174 +5252,183 @@ A resource which includes description and introduced values in the resource, act
```ruby
description 'The apparmor_policy resource is used to add or remove policy files from a cookbook file'
-introduced '14.1'
+ introduced '14.1'
-property :source_cookbook, String,
+ property :source_cookbook, String,
description: 'The cookbook to source the policy file from'
-property :source_filename, String,
+ property :source_filename, String,
description: 'The name of the source file if it differs from the apparmor.d file being created'
-action :add do
- description 'Adds an apparmor policy'
+ action :add do
+ description 'Adds an apparmor policy'
- # you'd probably have some actual chef code here
-end
+ # you'd probably have some actual chef code here
+ end
```
-# Ohai Release Notes 13.9:
+### Ohai Improvements
+
+- Fix uptime parsing on AIX
+- Fix Softlayer cloud detection
+- Use the current Azure metadata endpoint
+- Correctly detect macOS guests on VMware and VirtualBox
+- Please see the [Ohai Changelog](https://github.com/chef/ohai/blob/master/CHANGELOG.md) for the complete list of changes.
+
+## What's New in 13.8.5
-* Fix uptime parsing on AIX
-* Fix Softlayer cloud detection
-* Use the current Azure metadata endpoint
-* Correctly detect macOS guests on VMware and VirtualBox
+This is a small bug fix release to resolve two issues we found in the
+13.8 release:
-# What's New in 13.8:
+- chef-client run failures due to a failure in a newer version of the FFI gem on RHEL 6.x and 7.x
+- knife failures when running `knife cookbook site install` to install a deprecated cookbook that has no replacement
-## Revert attributes changes from 13.7
+## What's New in 13.8.3
+
+This is a small bug fix release that updates Ohai to properly detect and
+poll SoftLayer metadata now that SoftLayer no longer supports TLS
+1.0/1.1. This update is only necessary if you're running on Softlayer.
+
+## What's New in 13.8.0
+
+### Revert attributes changes from 13.7
Per <https://discourse.chef.io/t/regression-in-chef-client-13-7-16/12518/1> , there was a regression in how arrays and hashes were handled in 13.7\. In 13.8, we've reverted to the same code as 13.6.
-## Continuing work on `windows_task`
+### Continuing work on `windows_task`
13.8 has better validation for the `idle_time` property, when using the `on_idle` frequency.
-## Security Updates
+### Security Updates
-* Updated libxml2 to 2.9.7; fixes: CVE-2017-15412
+- Updated libxml2 to 2.9.7; fixes: [CVE-2017-15412](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412)
-# What's New in 13.7:
+## What's New in 13.7.16
-## The `windows_task` Resource should be better behaved
+### The `windows_task` Resource should be better behaved
We've spent a considerable amount of time testing and fixing the `windows_task` resource to ensure that it is properly idempotent and correct in more situations.
-## Credentials handling
+### Credentials handling
-Previously, chef on the workstation used `knife.rb` or `config.rb` to handle credentials. This didn't do a great job when interacting with multiple Chef servers, leading to the need for tools like `knife_block`. We've added support for a credentials file that can contain configuration for many Chef servers (or organizations), and we've made it easy to indicate which account you mean to use.
+Previously, the `knife` CLI used `knife.rb` or `config.rb` to handle credentials. This didn't do a great job when interacting with multiple Chef servers, leading to the need for tools like `knife_block`. We've added support for a credentials file that can contain configuration for many Chef servers (or organizations), and we've made it easy to indicate which account you mean to use.
-## New deprecations
+### New deprecations
-### `erl_call` Resource
+#### `erl_call` Resource
-We introduced `erl_call` to help us to manage CouchDB servers back in the olden times of Chef. Since then, we've noticed that no-one uses it, and so `erl_call` will be removed in Chef 14\. Foodcritic rule FC105 has been introduced to detect usage of erl_call.
+We introduced `erl_call` to help us to manage CouchDB servers back in the olden times of Chef. Since then, we've noticed that no-one uses it, and so `erl_call` will be removed in Chef 14. Foodcritic rule [FC105(http://www.foodcritic.io/#FC105) has been introduced to detect usage of erl_call.
-### epic_fail
+#### epic_fail
-The original name for the ignore_failure property in resources was epic_fail. Our documentation hasn't referred to epic_fail for years and out of the 3500 cookbooks on the Supermarket only one uses epic_fail. In Chef 14 we will remove the epic_fail property entirely. Foodcritic rule FC107 has been introduced to detect usage of epic_fail.
+The original name for the ignore_failure property in resources was epic_fail. Our documentation hasn't referred to epic_fail for years and out of the 3500 cookbooks on the Supermarket only one uses epic_fail. In Chef 14 we will remove the epic_fail property entirely. Foodcritic rule [FC107](http://www.foodcritic.io/#FC107) has been introduced to detect usage of epic_fail.
-### Legacy Mixins
+#### Legacy Mixins
-In Chef 14 several legacy legacy mixins will be removed. Usage of these mixins has resulted in deprecation warnings for several years. They were traditionally used in some HWRPs, but are rarely found in code available on the Supermarket. Foodcritic rules FC097, FC098, FC099, FC100, and FC102 have been introduced to detect these mixins.
+In Chef 14 several legacy mixins will be removed. Usage of these mixins has resulted in deprecation warnings for several years. They were traditionally used in some HWRPs, but are rarely found in code available on the Supermarket. Foodcritic rules [FC097](http://www.foodcritic.io/#FC097), [FC098](http://www.foodcritic.io/#FC098), [FC099](http://www.foodcritic.io/#FC099), [FC100](http://www.foodcritic.io/#FC100), and [FC102](http://www.foodcritic.io/#FC102) have been introduced to detect these mixins:
-* Chef::Mixin::LanguageIncludeAttribute
-* Chef::Mixin::RecipeDefinitionDSLCore
-* Chef::Mixin::LanguageIncludeRecipe
-* Chef::Mixin::Language
-* Chef::DSL::Recipe::FullDSL
+- `Chef::Mixin::LanguageIncludeAttribute`
+- `Chef::Mixin::RecipeDefinitionDSLCore`
+- `Chef::Mixin::LanguageIncludeRecipe`
+- `Chef::Mixin::Language`
+- `Chef::DSL::Recipe::FullDSL`
### :uninstall action in chocolatey_package
-The chocolatey cookbook's chocolatey_package resource originally contained an :uninstall action. When chocolatey_package was moved into core Chef we made :uninstall an alias for :remove. In Chef 14 :uninstall will no longer be a valid action. Foodcritic rule FC103 has been introduced to detect the usage of the :uninstall action.
+The chocolatey cookbook's chocolatey_package resource originally contained an :uninstall action. When chocolatey_package was moved into core Chef we made :uninstall an alias for :remove. In Chef 14 :uninstall will no longer be a valid action. Foodcritic rule [FC103](http://www.foodcritic.io/#FC103) has been introduced to detect the usage of the :uninstall action.
## Bugfixes
-* Resolved a bug where knife commands that prompted on Windows would never display the prompt
-* Fixed hiding of sensitive resources when converge_if_changed was used
-* Fixed scenarios where services would fail to start on Solaris
+- Resolved a bug where knife commands that prompted on Windows would never display the prompt
+- Fixed hiding of sensitive resources when converge_if_changed was used
+- Fixed scenarios where services would fail to start on Solaris
-## Security Updates
+### Security Updates
-* OpenSSL has been upgraded to 1.0.2n to resolve CVE-2017-3738, CVE-2017-3737, CVE-2017-3736, and CVE-2017-3735.
-* Ruby has been upgraded to 2.4.3 to resolve CVE-2017-17405
+- OpenSSL has been upgraded to 1.0.2n to resolve [CVE-2017-3738](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3738), [CVE-2017-3737](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3737), [CVE-2017-3736](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3736), and [CVE-2017-3735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3735).
+- Ruby has been upgraded to 2.4.3 to resolve [CVE-2017-17405](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-17405)
-## Ohai 13.7 Release Notes:
+### Ohai 13.7
-### Network Tunnel Information
+#### Network Tunnel Information
The Network plugin on Linux hosts now gathers additional information on tunnels
-### LsPci Plugin
+#### LsPci Plugin
The new LsPci plugin provides a `node[:pci]` hash with information about the PCI bus based on `lspci`. Only runs on Linux.
-### EC2 C5 Detection
+#### EC2 C5 Detection
The EC2 plugin has been updated to properly detect the new AWS hypervisor used in the C5 instance types
-### mdadm
+#### mdadm
The mdadm plugin has been updated to properly handle arrays with more than 10 disks and to properly handle journal and spare drives in the disk counts
-# What's New in 13.6.4:
+## What's New in 13.6.4
-## Bugfixes
+### Bugfixes
-* Resolved a regression in 13.6.0 that prevented upgrading packages on Debian/Ubuntu when the package name contained a tilde.
+- Resolved a regression in 13.6.0 that prevented upgrading packages on Debian/Ubuntu when the package name contained a tilde.
-## Security Updates
+### Security Updates
-* OpenSSL has been upgraded to 1.0.2m to resolve CVE-2017-3735 and CVE-2017-3736
-* RubyGems has been upgraded to 2.6.14 to resolve CVE-2017-0903
+- OpenSSL has been upgraded to 1.0.2m to resolve [CVE-2017-3735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3735) and [CVE-2017-3736](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3736)
+- RubyGems has been upgraded to 2.6.14 to resolve [CVE-2017-0903](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0903)
-# What's New in 13.6:
+## What's New in 13.6.0
-## `deploy` Resource Is Deprecated
+### `deploy` Resource Is Deprecated
-The `deploy` resource (and its alter ego `deploy_revision`) have been deprecated, to be removed in Chef 14\. This is being done because this resource is considered overcomplicated and error-prone in the modern Chef ecosystem. A compatibility cookbook will be available to help users migrate during the Chef 14 release cycle. See [the deprecation documentation](https://docs.chef.io/deprecations_deploy_resource) for more information.
+The `deploy` resource (and its alter ego `deploy_revision`) have been deprecated, to be removed in Chef 14. This is being done because this resource is considered overcomplicated and error-prone in the modern Chef ecosystem. A compatibility cookbook will be available to help users migrate during the Chef 14 release cycle. See [the deprecation documentation](https://docs.chef.io/deprecations_deploy_resource) for more information.
-## zypper_package supports package downgrades
+### zypper_package supports package downgrades
`zypper_package` now supports downgrading installed packages with the `allow_downgrade` property.
-## InSpec updated to 1.42.3
+### InSpec updated to 1.42.3
-## Reserve certain Data Bag names
+### Reserve certain Data Bag names
It's no longer possible to create data bags named `node`, `role`, `client`, or `environment`. Existing data bags will continue to work as before.
-## Properly use yum on RHEL 7
+### Properly use yum on RHEL 7
If both dnf and yum were installed, in some circumstances the yum provider might choose to run dnf, which is not what we intended it to do. It now properly runs yum, all the time.
-## Ohai 13.6 Release Notes:
+### Ohai 13.6
-### Critical Plugins
+#### Critical Plugins
Users can now specify a list of plugins which are `critical`. Critical plugins will cause Ohai to fail if they do not run successfully (and thus cause a Chef run using Ohai to fail). The syntax for this is:
-```
+```ruby
ohai.critical_plugins << :Filesystem
```
-### Filesystem now has a `allow_partial_data` configuration option
+#### Filesystem now has a `allow_partial_data` configuration option
The Filesystem plugin now has a `allow_partial_data` configuration option. If set, the filesystem will return whatever data it can even if some commands it ran failed.
-### Rackspace detection on Windows
+#### Rackspace detection on Windows
Windows nodes running on Rackspace will now properly detect themselves as running on Rackspace without a hint file.
-### Package data on Amazon Linux
+#### Package data on Amazon Linux
The Packages plugin now supports gathering packages data on Amazon Linux
-### Deprecation updates
+#### Deprecation updates
In Ohai 13 we replaced the filesystem and cloud plugins with the filesystem2 and cloud_v2 plugins. To maintain compatibility with users of the previous V2 plugins we write data to both locations. We had originally planned to continue writing data to both locations until Chef Infra Client 15. Instead due to the large amount of duplicate node data this introduces we are updating OHAI-11 and OHAI-12 deprecations to remove node['cloud_v2'] and node['filesystem2'] with the release of Chef 14 in April 2018.
-# What's New in 13.5:
-
-## Mount's password property is now marked as sensitive
-
-This means that passwords passed to mount won't show up in logs.
-
-## The `windows_task` resource now correctly handles `start_day`
-
-Previously, the resource would accept any date that was formatted correctly in the local locale, unlike the Windows cookbook and Windows itself. We now only support the `MM/DD/YYYY` format, in common with the Windows cookbook.
+## What's New in 13.5
-## InSpec updated to 1.39.1
+- **The mount resource's password property is now marked as **sensitive** Passwords passed to mount won't show up in logs.
+- **The windows_task resource now correctly handles start_day** Previously, the resource would accept any date that was formatted correctly in the local locale, unlike the Windows cookbook and Windows itself. We now support only the MM/DD/YYYY format, in keeping with the Windows cookbook.
+- **InSpec updated to 1.39.1**
-## Ohai 13.5 Release Notes:
+### Ohai 13.5
### Correctly detect IPv6 routes ending in ::
@@ -4307,75 +5438,77 @@ Previously we would ignore routes that ended `::`, and now we properly detect th
Debug logs will show the length of time each plugin takes to run, making debugging of long ohai runs easier.
-# What's New in 13.4:
+## What's New in 13.4.24
-## Security release of Ruby
+### Security
-Chef Client 13.4 includes Ruby 2.4.2 to fix the following CVEs:
+This release includes Ruby 2.4.2 to fix the following CVEs:
-* CVE-2017-0898
-* CVE-2017-10784
-* CVE-2017-14033
-* CVE-2017-14064
+- [CVE-2017-0898](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0898)
+- [CVE-2017-10784](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-10784)
+- [CVE-2017-14033](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-14033)
+- [CVE-2017-14064](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-14064)
-## Security release of RubyGems
+## What's New in 13.4.19
+
+### Security release of RubyGems
Chef Client 13.4 includes RubyGems 2.6.13 to fix the following CVEs:
-* CVE-2017-0899
-* CVE-2017-0900
-* CVE-2017-0901
-* CVE-2017-0902
+- [CVE-2017-0899](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0899)
+- [CVE-2017-0900](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0900)
+- [CVE-2017-0901](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0901)
+- [CVE-2017-0902](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0902)
-## Ifconfig provider on Red Hat now supports additional properties
+### Ifconfig provider on Red Hat now supports additional properties
It is now possible to set `ETHTOOL_OPTS`, `BONDING_OPTS`, `MASTER` and `SLAVE` properties on interfaces on Red Hat compatible systems. See <https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/s1-networkscripts-interfaces.html> for further information
-### Properties
+#### Properties
-* `ethtool_opts`<br>
+- `ethtool_opts`<br>
**Ruby types:** String<br>
- **Platforms:** Fedora, RHEL, Amazon Linux A string containing arguments to ethtool. The string will be wrapped in double quotes, so ensure that any needed quotes in the property are surrounded by single quotes
+ **Platforms:*- Fedora, RHEL, Amazon Linux A string containing arguments to ethtool. The string will be wrapped in double quotes, so ensure that any needed quotes in the property are surrounded by single quotes
-* `bonding_opts`<br>
+- `bonding_opts`<br>
**Ruby types:** String<br>
- **Platforms:** Fedora, RHEL, Amazon Linux A string containing configuration parameters for the bonding device.
+ **Platforms:*- Fedora, RHEL, Amazon Linux A string containing configuration parameters for the bonding device.
-* `master`<br>
+- `master`<br>
**Ruby types:** String<br>
- **Platforms:** Fedora, RHEL, Amazon Linux The channel bonding interface that this interface is linked to.
+ **Platforms:*- Fedora, RHEL, Amazon Linux The channel bonding interface that this interface is linked to.
-* `slave`<br>
+- `slave`<br>
**Ruby types:** String<br>
- **Platforms:** Fedora, RHEL, Amazon Linux Whether the interface is controlled by the channel bonding interface defined by `master`, above.
+ **Platforms:*- Fedora, RHEL, Amazon Linux Whether the interface is controlled by the channel bonding interface defined by `master`, above.
-## Chef Vault is now included
+### Chef Vault is now included
Chef Client 13.4 now includes the `chef-vault` gem, making it easier for users of chef-vault to use their encrypted items.
-## Windows `remote_file` resource with alternate credentials
+### Windows `remote_file` resource with alternate credentials
The `remote_file` resource now supports the use of credentials on Windows when accessing a remote UNC path on Windows such as `\\myserver\myshare\mydirectory\myfile.txt`. This allows access to the file at that path location even if the Chef client process identity does not have permission to access the file. The new properties `remote_user`, `remote_domain`, and `remote_password` may be used to specify credentials with access to the remote file so that it may be read.
**Note**: This feature is mainly used for accessing files between two nodes in different domains and having different user accounts. In case the two nodes are in same domain, `remote_file` resource does not need `remote_user` and `remote_password` specified because the user has the same access on both systems through the domain.
-### Properties
+#### Properties
The following properties are new for the `remote_file` resource:
-* `remote_user`<br>
+- `remote_user`<br>
**Ruby types:** String<br>
- _Windows only:_ The user name of a user with access to the remote file specified by the `source` property. Default value: `nil`. The user name may optionally be specified with a domain, i.e. `domain\user` or `user@my.dns.domain.com` via Universal Principal Name (UPN) format. It can also be specified without a domain simply as `user` if the domain is instead specified using the `remote_domain` attribute. Note that this property is ignored if `source` is not a UNC path. If this property is specified, the `remote_password` property **must** be specified.
+ _Windows only:_ The user name of a user with access to the remote file specified by the `source` property. Default value: `nil`. The user name may optionally be specified with a domain, i.e. `domain\user` or `user@my.dns.domain.com` via Universal Principal Name (UPN) format. It can also be specified without a domain simply as `user` if the domain is instead specified using the `remote_domain` attribute. Note that this property is ignored if `source` is not a UNC path. If this property is specified, the `remote_password` property **must*- be specified.
-* `remote_password`<br>
- **Ruby types** String<br>
+- `remote_password`<br>
+ **Ruby types*- String<br>
_Windows only:_ The password of the user specified by the `remote_user` property. Default value: `nil`. This property is mandatory if `remote_user` is specified and may only be specified if `remote_user` is specified. The `sensitive` property for this resource will automatically be set to `true` if `remote_password` is specified.
-* `remote_domain`<br>
- **Ruby types** String<br>
+- `remote_domain`<br>
+ **Ruby types*- String<br>
_Windows only:_ The domain of the user user specified by the `remote_user` property. Default value: `nil`. If not specified, the user and password properties specified by the `remote_user` and `remote_password` properties will be used to authenticate that user against the domain in which the system hosting the UNC path specified via `source` is joined, or if that system is not joined to a domain it will authenticate the user as a local account on that system. An alternative way to specify the domain is to leave this property unspecified and specify the domain as part of the `remote_user` property.
-### Examples
+#### Examples
Accessing file from a (different) domain account
@@ -4419,20 +5552,20 @@ remote_file "E://domain_test.txt" do
end
```
-## windows_path resource
+### windows_path resource
`windows_path` resource has been moved to core chef from windows cookbook. Use the `windows_path` resource to manage the path environment variable on Microsoft Windows.
-### Actions
+#### Actions
-* `:add` * Add an item to the system path
-* `:remove` * Remove an item from the system path
+- `:add` - Add an item to the system path
+- `:remove` - Remove an item from the system path
-### Properties
+#### Properties
-* `path` * Name attribute. The name of the value to add to the system path
+- `path` - Name attribute. The name of the value to add to the system path
-### Examples
+#### Examples
Add Sysinternals to the system path
@@ -4450,13 +5583,13 @@ windows_path 'C:\7-Zip' do
end
```
-## Ohai Release Notes 13.4
+### Ohai 13.4
-### Windows EC2 Detection
+#### Windows EC2 Detection
Detection of nodes running in EC2 has been greatly improved and should now detect nodes 100% of the time including nodes that have been migrated to EC2 or were built with custom AMIs.
-### Azure Metadata Endpoint Detection
+#### Azure Metadata Endpoint Detection
Ohai now polls the new Azure metadata endpoint, giving us additional configuration details on nodes running in Azure
@@ -4519,55 +5652,55 @@ Sample data now available under azure:
}
```
-### Package Plugin Supports Arch Linux
+#### Package Plugin Supports Arch Linux
The Packages plugin has been updated to include package information on Arch Linux systems.
-# What's New in 13.3:
+## What's New in 13.3
-## Unprivileged Symlink Creation on Windows
+### Unprivileged Symlink Creation on Windows
Chef can now create symlinks without privilege escalation, which allows for the creation of symlinks on Windows 10 Creator Update.
-## nokogiri Gem
+### nokogiri Gem
The nokogiri gem is once again bundled with the omnibus install of Chef
-## zypper_package Options
+### zypper_package Options
It is now possible to pass additional options to the zypper in the zypper_package resource. This can be used to pass any zypper CLI option
-### Example:
+#### Example:
```ruby
zypper_package 'foo' do
options '--user-provided'
end
-```
+ ```
-## windows_task Improvements
+### windows_task Improvements
The `windows_task` resource now properly allows updating the configuration of a scheduled task when using the `:create` action. Additionally the previous `:change` action from the windows cookbook has been aliased to `:create` to provide backwards compatibility.
-## apt_preference Resource
+### apt_preference Resource
The apt_preference resource has been ported from the apt cookbook. This resource allows for the creation of APT preference files controlling which packages take priority during installation.
Further information regarding apt-pinning is available via <https://wiki.debian.org/AptPreferences> and <https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html>
-### Actions
+#### Actions
-* `:add`: creates a preferences file under /etc/apt/preferences.d
-* `:remove`: Removes the file, therefore unpin the package
+- `:add`: creates a preferences file under /etc/apt/preferences.d
+- `:remove`: Removes the file, therefore unpin the package
-### Properties
+#### Properties
-* `package_name`: name attribute. The name of the package
-* `glob`: Pin by glob() expression or regexp surrounded by /.
-* `pin`: The package version/repository to pin
-* `pin_priority`: The pinning priority aka "the highest package version wins"
+- `package_name`: name attribute. The name of the package
+- `glob`: Pin by glob() expression or regexp surrounded by /.
+- `pin`: The package version/repository to pin
+- `pin_priority`: The pinning priority aka "the highest package version wins"
-### Examples
+#### Examples
Pin libmysqlclient16 to version 5.1.49-3:
@@ -4596,33 +5729,33 @@ apt_preference 'dotdeb' do
end
```
-## zypper_repository Resource
+### zypper_repository Resource
The zypper_repository resource allows for the creation of Zypper package repositories on SUSE Enterprise Linux and openSUSE systems. This resource maintains full compatibility with the resource in the existing [zypper](https://supermarket.chef.io/cookbooks/zypper) cookbooks
-### Actions
-
-* `:add` * adds a repo
-* `:delete` * removes a repo
+#### Actions
-### Properties
+- `:add` - adds a repo
+- `:delete` - removes a repo
-* `repo_name` * repository name if different from the resource name (name property)
-* `type` * the repository type. default: 'NONE'
-* `description` * the description of the repo that will be shown in `zypper repos`
-* `baseurl` * the base url of the repo
-* `path` * the relative path from the `baseurl`
-* `mirrorlist` * the url to the mirrorlist to use
-* `gpgcheck` * should we gpg check the repo (true/false). default: true
-* `gpgkey` * location of repo key to import
-* `priority` * priority of the repo. default: 99
-* `autorefresh` * should the repository be automatically refreshed (true/false). default: true
-* `keeppackages` * should packages be saved (true/false). default: false
-* `refresh_cache` * should package cache be refreshed (true/false). default: true
-* `enabled` * should this repository be enabled (true/false). default: true
-* `mode` * the file mode of the repository file. default: "0644"
+#### Properties
-### Examples
+- `repo_name` - repository name if different from the resource name (name property)
+- `type` - the repository type. default: 'NONE'
+- `description` - the description of the repo that will be shown in `zypper repos`
+- `baseurl` - the base url of the repo
+- `path` - the relative path from the `baseurl`
+- `mirrorlist` - the url to the mirrorlist to use
+- `gpgcheck` - should we gpg check the repo (true/false). default: true
+- `gpgkey` - location of repo key to import
+- `priority` - priority of the repo. default: 99
+- `autorefresh` - should the repository be automatically refreshed (true/false). default: true
+- `keeppackages` - should packages be saved (true/false). default: false
+- `refresh_cache` - should package cache be refreshed (true/false). default: true
+- `enabled` - should this repository be enabled (true/false). default: true
+- `mode` - the file mode of the repository file. default: "0644"
+
+#### Examples
Add the Apache repository for openSUSE Leap 42.2
@@ -4635,22 +5768,22 @@ zypper_repository 'apache' do
end
```
-## Ohai Release Notes 13.3:
+### Ohai 13.3
-### Additional Platform Support
+#### Additional Platform Support
Ohai now properly detects the [F5 Big-IP](https://www.f5.com/) platform and platform_version.
-* platform: bigip
-* platform_family: rhel
+- platform: bigip
+- platform_family: rhel
-# What's New in 13.2:
+## What's New in 13.2
-## Properly send policyfile data
+### Properly send policyfile data
When sending events back to the Chef Server, we now correctly expand the run_list for nodes that use Policyfiles. This allows Automate to correctly report the node.
-## Reconfigure between runs when daemonized
+### Reconfigure between runs when daemonized
When Chef performs a reconfigure, it re-reads the configuration files. It also re-opens its log files, which facilitates log file rotation.
@@ -4658,7 +5791,7 @@ Chef normally will reconfigure when sent a HUP signal. As of this release if you
Additionally, Chef now always performs a reconfigure after every run when daemonized.
-## New Deprecations
+### New deprecations
### Explicit property methods
@@ -4666,51 +5799,51 @@ Additionally, Chef now always performs a reconfigure after every run when daemon
In Chef 14, custom resources will no longer assume property methods are being called on `new_resource`, and instead require the resource author to be explicit.
-# Ohai Release Notes 13.2:
+### Ohai 13.2
Ohai 13.2 has been a fantastic release in terms of community involvement with new plugins, platform support, and critical bug fixes coming from community members. A huge thank you to msgarbossa, albertomurillo, jaymzh, and davide125 for their work.
-## New Features
+#### New Features
-### Systemd Paths Plugin
+##### Systemd Paths Plugin
A new plugin has been added to expose system and user paths from systemd-path (see <https://www.freedesktop.org/software/systemd/man/systemd-path.html> for details).
-### Linux Network, Filesystem, and Mdadm Plugin Resilience
+##### Linux Network, Filesystem, and Mdadm Plugin Resilience
The Network, Filesystem, and Mdadm plugins have been improved to greatly reduce failures to collect data. The Network plugin now better finds the binaries it requires for shelling out, filesystem plugin utilizes data from multiple sources, and mdadm handles arrays in bad states.
-### Zpool Plugin Platform Expansion
+##### Zpool Plugin Platform Expansion
The Zpool plugin has been updated to support BSD and Linux in addition to Solaris.
-### RPM version parsing on AIX
+##### RPM version parsing on AIX
The packages plugin now correctly parses RPM package name / version information on AIX systems.
-### Additional Platform Support
+##### Additional Platform Support
Ohai now properly detects the [Clear](https://clearlinux.org/) and [ClearOS](https://www.clearos.com/) Linux distributions.
-#### Clear Linux
+**Clear Linux**
-* platform: clearlinux
-* platform_family: clearlinux
+- platform: clearlinux
+- platform_family: clearlinux
-#### ClearOS
+**ClearOS**
-* platform: clearos
-* platform_family: rhel
+- platform: clearos
+- platform_family: rhel
-## New Deprecations
+#### New Deprecations
-### Removal of IpScopes plugin. (OHAI-13)
+##### Removal of IpScopes plugin. (OHAI-13)
<https://docs.chef.io/deprecations_ohai_ipscopes>
In Chef/Ohai 14 (April 2018) we will remove the IpScopes plugin. The data returned by this plugin is nearly identical to information already returned by individual network plugins and this plugin required the installation of an additional gem into the Chef installation. We believe that few users were installing the gem and users would be better served by the data returned from the network plugins.
-# 13.1
+# What's New in 13.1
## Socketless local mode by default
@@ -4726,7 +5859,7 @@ If you use Chef Provisioning with Local Mode, you may need to pass `--listen` to
In Chef/Ohai 14 (April 2018) we will remove support for loading Ohai v6 plugins, which we deprecated in Ohai 7/Chef 11.12.
-# 13.0
+# What's New in 13.0
## Rubygems provider sources behavior changed.
@@ -4752,7 +5885,7 @@ This can be used by any other resource by just overriding the name property and
property :name, String, default: ""
```
-Notifications to resources with empty strings as their name is also supported via either the bare resource name (`apt_update` -* matches what the user types in the DSL) or with empty brackets (`apt_update[]` -* matches the resource notification pattern).
+Notifications to resources with empty strings as their name is also supported via either the bare resource name (`apt_update` -- matches what the user types in the DSL) or with empty brackets (`apt_update[]` -- matches the resource notification pattern).
## The knife ssh command applies the same fuzzifier as knife search node
@@ -4908,7 +6041,7 @@ The core of chef hasn't used this to implement the Recipe DSL since 12.5.1 and i
### Simplify Recipe DSL wiring
-Support for actions with spaces and hyphens in the action name has been dropped. Resources and property names with spaces and hyphens most likely never worked in Chef-12\. UTF-8 characters have always been supported and still are.
+Support for actions with spaces and hyphens in the action name has been dropped. Resources and property names with spaces and hyphens most likely never worked in Chef-12. UTF-8 characters have always been supported and still are.
### `easy_install` resource has been removed
@@ -5008,16 +6141,16 @@ The PATH changes have also been tweaked so that the ruby bindir and gemdir PATHS
Some examples of changes:
-* `which ruby` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus
-* `which ruby` in 13.x will return any system ruby and will not find the embedded ruby if using omnibus
-* `shell_out_with_systems_locale("which ruby")` behaves the same as `which ruby` above
-* `shell_out("which ruby")` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus
-* `shell_out("which ruby")` in 13.x will always return the omnibus ruby first (but will find the system ruby if not using omnibus)
+- `which ruby` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus
+- `which ruby` in 13.x will return any system ruby and will not find the embedded ruby if using omnibus
+- `shell_out_with_systems_locale("which ruby")` behaves the same as `which ruby` above
+- `shell_out("which ruby")` in 12.x will return any system ruby and fall back to the embedded ruby if using omnibus
+- `shell_out("which ruby")` in 13.x will always return the omnibus ruby first (but will find the system ruby if not using omnibus)
The PATH in `shell_out` can also be overridden:
-* `shell_out("which ruby", env: { "PATH" => nil })` * behaves like shell_out_with_systems_locale()
-* `shell_out("which ruby", env: { "PATH" => [...include PATH string here...] })` * set it arbitrarily however you need
+- `shell_out("which ruby", env: { "PATH" => nil })` - behaves like shell_out_with_systems_locale()
+- `shell_out("which ruby", env: { "PATH" => [...include PATH string here...] })` - set it arbitrarily however you need
Since most providers which launch custom user commands use `shell_out_with_systems_locale` (service, execute, script, etc) the behavior will be that those commands that used to be having embedded omnibus paths injected into them no longer will. Generally this will fix more problems than it solves, but may causes issues for some use cases.
@@ -5027,7 +6160,7 @@ The implementation switched to `shell_out_with_systems_locale` to match `execute
### Chef Client will now exit using the RFC062 defined exit codes
-Chef Client will only exit with exit codes defined in RFC 062\. This allows other tooling to respond to how a Chef run completes. Attempting to exit Chef Client with an unsupported exit code (either via `Chef::Application.fatal!` or `Chef::Application.exit!`) will result in an exit code of 1 (GENERIC_FAILURE) and a warning in the event log.
+Chef Client will only exit with exit codes defined in RFC 062. This allows other tooling to respond to how a Chef run completes. Attempting to exit Chef Client with an unsupported exit code (either via `Chef::Application.fatal!` or `Chef::Application.exit!`) will result in an exit code of 1 (GENERIC_FAILURE) and a warning in the event log.
When Chef Client is running as a forked process on unix systems, the standardized exit codes are used by the child process. To actually have Chef Client return the standard exit code, `client_fork false` will need to be set in Chef Client's configuration file.
@@ -5063,32 +6196,32 @@ The virtualization plugin has been updated to properly detect when running on Do
This release of Chef Client contains Ruby 2.3.5, fixing 4 CVEs:
- * CVE-2017-0898
- * CVE-2017-10784
- * CVE-2017-14033
- * CVE-2017-14064
+ - CVE-2017-0898
+ - CVE-2017-10784
+ - CVE-2017-14033
+ - CVE-2017-14064
It also contains a new version of Rubygems, fixing 4 CVEs:
- * CVE-2017-0899
- * CVE-2017-0900
- * CVE-2017-0901
- * CVE-2017-0902
+ - CVE-2017-0899
+ - CVE-2017-0900
+ - CVE-2017-0901
+ - CVE-2017-0902
This release also contains a new version of zlib, fixing 4
CVEs:
- * [CVE-2016-9840](https://www.cvedetails.com/cve/CVE-2016-9840/)
- * [CVE-2016-9841](https://www.cvedetails.com/cve/CVE-2016-9841/)
- * [CVE-2016-9842](https://www.cvedetails.com/cve/CVE-2016-9842/)
- * [CVE-2016-9843](https://www.cvedetails.com/cve/CVE-2016-9843/)
+ - [CVE-2016-9840](https://www.cvedetails.com/cve/CVE-2016-9840/)
+ - [CVE-2016-9841](https://www.cvedetails.com/cve/CVE-2016-9841/)
+ - [CVE-2016-9842](https://www.cvedetails.com/cve/CVE-2016-9842/)
+ - [CVE-2016-9843](https://www.cvedetails.com/cve/CVE-2016-9843/)
-## On Debian based systems, correctly prefer Systemd to Upstart
+## On Debian prefer Systemd to Upstart
On Debian systems, packages that support systemd will often ship both an
old style init script and a systemd unit file. When this happened, Chef
would incorrectly choose Upstart rather than Systemd as the service
-provider. We now pick Systemd.
+provider. Chef will now prefer systemd where available.
## Handle the supports pseudo-property more gracefully
@@ -5097,10 +6230,10 @@ many cookbooks also have a property named support, and Chef 12 was
incorrectly giving a deprecation notice in that case, preventing users
from properly testing their cookbooks for upgrades.
-## Don't crash when we downgrade from Chef 13 to Chef 12
+## Don't crash if downgrading from Chef 13 to 12
On systems where Chef 13 had been run, Chef 12 would crash as the
-on disk cookbook format has changed. Chef 12 now correctly ignores the
+on-disk cookbook format has changed. Chef 12 now correctly ignores the
unexpected files.
## Provide better system information when Chef crashes
@@ -5113,9 +6246,9 @@ detail from the off.
## Highlighted enhancements for this release:
-* Systemd unit files are now verified before being installed.
-* Added support for windows alternate user identity in execute resources.
-* Added ed25519 key support for for ssh connections.
+- Systemd unit files are now verified before being installed.
+- Added support for windows alternate user identity in execute resources.
+- Added ed25519 key support for for ssh connections.
### Windows alternate user identity execute support
@@ -5137,16 +6270,16 @@ Chef::ReservedNames::Win32::Security.get_account_right('<user>').include?('SeAss
The following properties are new or updated for the `execute`, `script`, `batch`, and `powershell_script` resources and any resources derived from them:
-* `user`<br>
+- `user`<br>
**Ruby types:** String<br>
- The user name of the user identity with which to launch the new process. Default value: `nil`. The user name may optionally be specified with a domain, i.e. `domain\user` or `user@my.dns.domain.com` via Universal Principal Name (UPN) format. It can also be specified without a domain simply as `user` if the domain is instead specified using the `domain` attribute. On Windows only, if this property is specified, the `password` property **must** be specified.
+ The user name of the user identity with which to launch the new process. Default value: `nil`. The user name may optionally be specified with a domain, i.e. `domain\user` or `user@my.dns.domain.com` via Universal Principal Name (UPN) format. It can also be specified without a domain simply as `user` if the domain is instead specified using the `domain` attribute. On Windows only, if this property is specified, the `password` property **must*- be specified.
-* `password`<br>
- **Ruby types** String<br>
+- `password`<br>
+ **Ruby types*- String<br>
_Windows only:_ The password of the user specified by the `user` property. Default value: `nil`. This property is mandatory if `user` is specified on Windows and may only be specified if `user` is specified. The `sensitive` property for this resource will automatically be set to `true` if `password` is specified.
-* `domain`<br>
- **Ruby types** String<br>
+- `domain`<br>
+ **Ruby types*- String<br>
_Windows only:_ The domain of the user user specified by the `user` property. Default value: `nil`. If not specified, the user name and password specified by the `user` and `password` properties will be used to resolve that user against the domain in which the system running Chef client is joined, or if that system is not joined to a domain it will resolve the user as a local account on that system. An alternative way to specify the domain is to leave this property unspecified and specify the domain as part of the `user` property.
#### Usage
@@ -5189,12 +6322,12 @@ end
## Highlighted bug fixes for this release:
-* Ensure that the Windows Administrator group can access the chef-solo nodes directory
-* When loading a cookbook in Chef Solo, use `metadata.json` in preference to `metadata.rb`
+- Ensure that the Windows Administrator group can access the chef-solo nodes directory
+- When loading a cookbook in Chef Solo, use `metadata.json` in preference to `metadata.rb`
## Deprecation Notice
-* As of version 12.19, chef client will no longer be build or tested on the Cisco NX-OS and IOS XR platforms.
+- As of version 12.19, chef client will no longer be build or tested on the Cisco NX-OS and IOS XR platforms.
# Ohai Release Notes 8.23:
@@ -5224,36 +6357,36 @@ GCC detection has been improved to collect additional information, and to not pr
### Ohai::Config removed
-* **Deprecation ID**: OHAI-1
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_legacy_config>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-1
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_legacy_config>
+- **Expected Removal**: Ohai 13 (April 2017)
### sigar gem based plugins removed
-* **Deprecation ID**: OHAI-2
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_sigar_plugins>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-2
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_sigar_plugins>
+- **Expected Removal**: Ohai 13 (April 2017)
### run_command and popen4 helper methods removed
-* **Deprecation ID**: OHAI-3
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_run_command_helpers>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-3
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_run_command_helpers>
+- **Expected Removal**: Ohai 13 (April 2017)
### libvirt plugin attributes moved
-* **Deprecation ID**: OHAI-4
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_libvirt_plugin>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-4
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_libvirt_plugin>
+- **Expected Removal**: Ohai 13 (April 2017)
### Windows CPU plugin attribute changes
-* **Deprecation ID**: OHAI-5
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_windows_cpu>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-5
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_windows_cpu>
+- **Expected Removal**: Ohai 13 (April 2017)
### DigitalOcean plugin attribute changes
-* **Deprecation ID**: OHAI-6
-* **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_digitalocean/>
-* **Expected Removal**: Ohai 13 (April 2017)
+- **Deprecation ID**: OHAI-6
+- **Remediation Docs**: <https://docs.chef.io/deprecations_ohai_digitalocean/>
+- **Expected Removal**: Ohai 13 (April 2017)
diff --git a/Rakefile b/Rakefile
index 09c8d5e131..b0dfb63b26 100644
--- a/Rakefile
+++ b/Rakefile
@@ -74,6 +74,17 @@ task :install do
end
end
+namespace :install do
+ task local: "pre_install:all"
+
+ task :local do
+ chef_bin_path = ::File.join(::File.dirname(__FILE__), "chef-bin")
+ Dir.chdir(chef_bin_path) do
+ sh("rake install:local")
+ end
+ end
+end
+
task :pedant, :chef_zero_spec
task :build_eventlog do
@@ -97,8 +108,8 @@ task :update_chef_exec_dll do
sh("hab pkg install chef/chef-powershell-shim")
sh("hab pkg install chef/chef-powershell-shim-x86")
- x64 = `hab pkg path chef/chef-powershell-shim`.chomp.gsub(/\\/, "/")
- x86 = `hab pkg path chef/chef-powershell-shim-x86`.chomp.gsub(/\\/, "/")
+ x64 = `hab pkg path chef/chef-powershell-shim`.chomp.tr("\\", "/")
+ x86 = `hab pkg path chef/chef-powershell-shim-x86`.chomp.tr("\\", "/")
FileUtils.rm_rf(Dir["distro/ruby_bin_folder/AMD64/*"])
FileUtils.rm_rf(Dir["distro/ruby_bin_folder/x86/*"])
puts "Copying #{x64}/bin/* to distro/ruby_bin_folder/AMD64"
diff --git a/VERSION b/VERSION
index e8d663113e..c15795bf69 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-16.7.68 \ No newline at end of file
+17.3.0 \ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index c87781b1e1..0000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,91 +0,0 @@
-# End-to-End Test of Chef in macOS
-
-variables:
- FORCE_FFI_YAJL: 'ext'
- CHEF_LICENSE: 'accept-no-persist'
-
-trigger:
-- master
-# TODO: 20190528 - should we be testing end_to_end on chef-15?
-#- chef-15
-
-pool:
- vmImage: $(imageName)
-
-jobs:
- - job:
- strategy:
- matrix:
- mac_kitchen_tests:
- imageName: 'macos-latest'
-
- steps:
- - script: |
- curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -c current
- /opt/chef/bin/chef-client -v
- /opt/chef/bin/ohai -v
- /opt/chef/embedded/bin/rake --version
- /opt/chef/embedded/bin/bundle -v
- displayName: 'Install Chef/Ohai from Omnitruck'
-
- - script: |
- OHAI_VERSION=$(sed -n '/ohai .[0-9]/{s/.*(//;s/)//;p;}' Gemfile.lock)
- sudo /opt/chef/embedded/bin/gem install appbundler appbundle-updater --no-doc
- sudo /opt/chef/embedded/bin/appbundle-updater chef ohai v${OHAI_VERSION} --tarball --github chef/ohai
- sudo /opt/chef/embedded/bin/appbundle-updater chef chef $BUILD_SOURCEVERSION --tarball --github chef/chef
- echo "Installed Chef / Ohai release:"
- /opt/chef/bin/chef-client -v
- /opt/chef/bin/ohai -v
- displayName: 'Upgrade Chef/Ohai via Appbundler'
-
- - script: |
- cd kitchen-tests
- sudo /opt/chef/embedded/bin/bundle config set without 'omnibus_package ruby_prof'
- sudo /opt/chef/embedded/bin/bundle install --jobs=3 --retry=3 --path=vendor/bundle
- sudo /opt/chef/embedded/bin/gem install berkshelf --no-doc
- sudo /opt/chef/embedded/bin/berks vendor cookbooks
- sudo /opt/chef/bin/chef-client -z -o end_to_end --chef-license accept-no-persist
- displayName: 'Run end_to_end::default recipe'
-
- - job:
- strategy:
- matrix:
- windows_kitchen_tests:
- imageName: 'windows-latest'
-
- steps:
- - powershell: |
- . { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; Install-Project -project chef -channel current
- $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
- chef-client -v
- ohai -v
- rake --version
- bundle -v
- displayName: 'Install Chef/Ohai from Omnitruck'
-
- - powershell: |
- $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
- $env:OHAI_VERSION = ( Select-String -Path .\Gemfile.lock -Pattern '(?<=ohai \()\d.*(?=\))' | ForEach-Object { $_.Matches[0].Value } )
- gem install appbundler appbundle-updater --no-doc
- appbundle-updater chef ohai v$env:OHAI_VERSION --tarball --github chef/ohai
- appbundle-updater chef chef $env:BUILD_SOURCEVERSION --tarball --github chef/chef
- Write-Output "Installed Chef / Ohai release:"
- chef-client -v
- ohai -v
- displayName: 'Upgrade Chef/Ohai via Appbundler'
-
- - powershell: |
- cd kitchen-tests
- $env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;" + $env:PATH
- bundle config set without 'omnibus_package ruby_prof'
- bundle install --jobs=3 --retry=3 --path=vendor/bundle
- gem install berkshelf --no-doc
- # berks emits a ruby warning when it loads net/http due to a previously
- # defined constant. Even though it is just a warning, powershell immediately
- # exits 1. I'm not sure why but this just suppresses the warnings.
- $env:RUBYOPT="-W0"
- berks vendor cookbooks
- # restore the default warning level
- $env:RUBYOPT="-W1"
- chef-client -z -o end_to_end --chef-license accept-no-persist
- displayName: 'Run end_to_end::default recipe'
diff --git a/bin/knife b/bin/knife
index 85ac3b91e9..aebc0f72d7 100755
--- a/bin/knife
+++ b/bin/knife
@@ -3,7 +3,7 @@
# ./knife - Chef CLI interface
#
# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright 2009-2018, Chef Software Inc.
+# Copyright:: Copyright Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/chef-bin/bin/chef-service-manager b/chef-bin/bin/chef-service-manager
index dcaae80141..336b1c4715 100755
--- a/chef-bin/bin/chef-service-manager
+++ b/chef-bin/bin/chef-service-manager
@@ -1,38 +1,3 @@
#!/usr/bin/env ruby
#
-# ./chef-service-manager - Control chef-service on Windows platforms.
-#
-# Author:: Serdar Sutay (serdar@chef.io)
-# Copyright:: Copyright 2013-2018, 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.
-
-$:.unshift(File.join(__dir__, "..", "lib"))
-require "chef"
-require "chef/application/windows_service_manager"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-if Chef::Platform.windows?
- chef_client_service = {
- service_name: ChefUtils::Dist::Infra::CLIENT,
- service_display_name: "#{ChefUtils::Dist::Infra::PRODUCT} Service",
- service_description: "Runs #{ChefUtils::Dist::Infra::PRODUCT} on regular, configurable intervals.",
- service_file_path: File.expand_path("../chef-windows-service", $PROGRAM_NAME),
- delayed_start: true,
- dependencies: ["Winmgmt"],
- }
- Chef::Application::WindowsServiceManager.new(chef_client_service).run
-else
- puts "chef-service-manager is only available on Windows platforms."
-end
+puts "As of Chef Infra Client 17 the chef-service-manager is no longer available. Please run Chef Infra Client as a scheduled task instead for more reliable operation with lower memory and CPU overhead."
diff --git a/chef-bin/bin/chef-windows-service b/chef-bin/bin/chef-windows-service
index ce1a30baae..e84cbe2e7a 100755
--- a/chef-bin/bin/chef-windows-service
+++ b/chef-bin/bin/chef-windows-service
@@ -1,34 +1,3 @@
#!/usr/bin/env ruby
#
-# Author:: Jay Mundrawala (<jdm@chef.io>)
-#
-# Copyright:: 2014-2018, Chef Software, Inc.
-#
-# 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.
-#
-
-# Note:
-# The file is used by appbundler to generate a ruby file that
-# we can execute using the correct gems. The batch file we
-# generate will call that file, and will be registered as
-# a windows service.
-
-$:.unshift(File.join(__dir__, "..", "lib"))
-require "chef"
-require "chef/application/windows_service"
-
-if Chef::Platform.windows?
- Chef::Application::WindowsService.mainloop
-else
- puts "chef-windows-service is only available on Windows platforms."
-end
+puts "As of Chef Infra Client 17 the chef-windows-service is no longer available. Please run Chef Infra Client as a scheduled task instead for more reliable operation with lower memory and CPU overhead."
diff --git a/chef-bin/lib/chef-bin/version.rb b/chef-bin/lib/chef-bin/version.rb
index 61ef5c77e1..5911d614f7 100644
--- a/chef-bin/lib/chef-bin/version.rb
+++ b/chef-bin/lib/chef-bin/version.rb
@@ -21,7 +21,7 @@
module ChefBin
CHEFBIN_ROOT = File.expand_path("..", __dir__)
- VERSION = "16.7.68".freeze
+ VERSION = "17.3.0".freeze
end
#
diff --git a/chef-config/chef-config.gemspec b/chef-config/chef-config.gemspec
index 08086ff25b..cf83b360df 100644
--- a/chef-config/chef-config.gemspec
+++ b/chef-config/chef-config.gemspec
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/chef/chef"
spec.license = "Apache-2.0"
- spec.required_ruby_version = ">= 2.6.0"
+ spec.required_ruby_version = ">= 2.6"
spec.metadata = {
"bug_tracker_uri" => "https://github.com/chef/chef/issues",
diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb
index 2f261b45a7..633378d6ba 100644
--- a/chef-config/lib/chef-config/config.rb
+++ b/chef-config/lib/chef-config/config.rb
@@ -71,8 +71,8 @@ module ChefConfig
# Some installations will be on different drives so use the drive that
# the expanded path to __FILE__ is found.
drive = windows_installation_drive
- if drive && path[0] == '\\' && path.split('\\')[2] == "chef"
- path = PathHelper.join(drive, path.split('\\', 3)[2])
+ if drive && path[0] == "\\" && path.split("\\")[2] == "chef"
+ path = PathHelper.join(drive, path.split("\\", 3)[2])
end
end
path
@@ -1007,7 +1007,7 @@ module ChefConfig
default :blocked_normal_attributes, nil
default :blocked_override_attributes, nil
- # deprecated config options that will be removed in Chef Infra Client 17
+ # deprecated config options that will be removed in Chef Infra Client 18
default :automatic_attribute_blacklist, nil
default :default_attribute_blacklist, nil
default :normal_attribute_blacklist, nil
diff --git a/chef-config/lib/chef-config/path_helper.rb b/chef-config/lib/chef-config/path_helper.rb
index 8fe45febfc..adacade391 100644
--- a/chef-config/lib/chef-config/path_helper.rb
+++ b/chef-config/lib/chef-config/path_helper.rb
@@ -45,7 +45,7 @@ module ChefConfig
end
end
- BACKSLASH = '\\'.freeze
+ BACKSLASH = "\\".freeze
def self.path_separator(windows: ChefUtils.windows?)
if windows
diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb
index 73ae49ba5f..4ed887de75 100644
--- a/chef-config/lib/chef-config/version.rb
+++ b/chef-config/lib/chef-config/version.rb
@@ -15,5 +15,5 @@
module ChefConfig
CHEFCONFIG_ROOT = File.expand_path("..", __dir__)
- VERSION = "16.7.68".freeze
+ VERSION = "17.3.0".freeze
end
diff --git a/chef-config/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb
index 3d7365927d..658acca615 100644
--- a/chef-config/spec/unit/config_spec.rb
+++ b/chef-config/spec/unit/config_spec.rb
@@ -225,7 +225,7 @@ RSpec.describe ChefConfig::Config do
end
describe "#var_chef_path" do
- let (:dirname) { ChefUtils::Dist::Infra::DIR_SUFFIX }
+ let(:dirname) { ChefUtils::Dist::Infra::DIR_SUFFIX }
context "on unix", :unix_only do
it "var_chef_dir is /var/chef" do
diff --git a/chef-config/spec/unit/path_helper_spec.rb b/chef-config/spec/unit/path_helper_spec.rb
index 3eedb8bbf5..de45de2a62 100644
--- a/chef-config/spec/unit/path_helper_spec.rb
+++ b/chef-config/spec/unit/path_helper_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe ChefConfig::PathHelper do
context "forcing windows/non-windows" do
context "forcing windows" do
it "path_separator is \\" do
- expect(path_helper.path_separator(windows: true)).to eq('\\')
+ expect(path_helper.path_separator(windows: true)).to eq("\\")
end
context "platform-specific #join behavior" do
@@ -133,7 +133,7 @@ RSpec.describe ChefConfig::PathHelper do
end
it "path_separator is \\" do
- expect(path_helper.path_separator).to eq('\\')
+ expect(path_helper.path_separator).to eq("\\")
end
context "platform-specific #join behavior" do
@@ -355,14 +355,14 @@ RSpec.describe ChefConfig::PathHelper do
end
context "on windows" do
- let (:is_windows) { true }
+ let(:is_windows) { true }
end
context "on unix" do
- let (:is_windows) { false }
+ let(:is_windows) { false }
context "when HOME is not set" do
- let (:env) { {} }
+ let(:env) { {} }
it "returns an empty array" do
expect(path_helper.all_homes).to eq([])
end
diff --git a/chef-universal-mingw32.gemspec b/chef-universal-mingw32.gemspec
index fa95de76f5..329b4f9d6b 100644
--- a/chef-universal-mingw32.gemspec
+++ b/chef-universal-mingw32.gemspec
@@ -14,7 +14,7 @@ gemspec.add_dependency "win32-service", ">= 2.1.5", "< 3.0"
gemspec.add_dependency "wmi-lite", "~> 1.0"
gemspec.add_dependency "win32-taskscheduler", "~> 2.0"
gemspec.add_dependency "iso8601", ">= 0.12.1", "< 0.14" # validate 0.14 when it comes out
-gemspec.add_dependency "win32-certstore", "~> 0.3"
+gemspec.add_dependency "win32-certstore", "~> 0.6.2" # 0.5+ required for specifying user vs. system store
gemspec.extensions << "ext/win32-eventlog/Rakefile"
gemspec.files += Dir.glob("{distro,ext}/**/*")
diff --git a/chef-utils/README.md b/chef-utils/README.md
index d3088a7bc2..9dcbd9b9ea 100644
--- a/chef-utils/README.md
+++ b/chef-utils/README.md
@@ -27,6 +27,7 @@ The Platform Family helpers provide an alternative to comparing values from `nod
* `freebsd?`
* `gentoo?`
* `macos?`
+* `macos_ruby?` - this is always true if the ruby VM is running on a mac host and is not stubbed by ChefSpec
* `netbsd?`
* `openbsd?`
* `rhel?` - includes redhat, centos, scientific, oracle, and clearos platforms
@@ -120,6 +121,7 @@ Architecture Helpers allow you to determine the processor architecture of your n
### Cloud Helpers
* `cloud?` - if the node is running in any cloud, including internal ones
+* `alibaba?` - if the node is running in alibaba cloud
* `ec2?` - if the node is running in ec2
* `gce?` - if the node is running in gce
* `rackspace?` - if the node is running in rackspace
diff --git a/chef-utils/chef-utils.gemspec b/chef-utils/chef-utils.gemspec
index 836fef1ff9..8cbae405e3 100644
--- a/chef-utils/chef-utils.gemspec
+++ b/chef-utils/chef-utils.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/chef/chef/tree/master/chef-utils"
spec.license = "Apache-2.0"
- spec.required_ruby_version = ">= 2.6.0"
+ spec.required_ruby_version = ">= 2.6"
spec.metadata = {
"bug_tracker_uri" => "https://github.com/chef/chef/issues",
@@ -41,6 +41,10 @@ Gem::Specification.new do |spec|
# ABSOLUTELY NO EXCEPTIONS
#
+ # concurrent-ruby is: 1. lightweight, 2. has zero deps, 3. is external to chef
+ # this is used for the parallel_map enumerable extension for lightweight threading
+ spec.add_dependency "concurrent-ruby"
+
spec.files = %w{Rakefile LICENSE} + Dir.glob("*.gemspec") +
Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
end
diff --git a/chef-utils/lib/chef-utils/dist.rb b/chef-utils/lib/chef-utils/dist.rb
index c4fe72960d..eb9bb5a881 100644
--- a/chef-utils/lib/chef-utils/dist.rb
+++ b/chef-utils/lib/chef-utils/dist.rb
@@ -15,6 +15,25 @@ module ChefUtils
PRODUCT = "Chef Automate"
end
+ class Cli
+ # the chef-cli product name
+ PRODUCT = "Chef CLI"
+
+ # the chef-cli gem
+ GEM = "chef-cli"
+ end
+
+ class Habitat
+ # name of the Habitat product
+ PRODUCT = "Chef Habitat"
+
+ # A short designation for the product
+ SHORT = "habitat"
+
+ # The hab cli binary
+ EXEC = "hab"
+ end
+
class Infra
# When referencing a product directly, like Chef (Now Chef Infra)
PRODUCT = "Chef Infra Client"
@@ -42,6 +61,17 @@ module ChefUtils
# The suffix for Chef's /etc/chef, /var/chef and C:\\Chef directories
# "chef" => /etc/cinc, /var/cinc, C:\\cinc
DIR_SUFFIX = "chef"
+
+ # The client's gem
+ GEM = "chef"
+ end
+
+ class Inspec
+ # The InSpec product name
+ PRODUCT = "Chef InSpec"
+
+ # The inspec binary
+ EXEC = "inspec"
end
class Org
@@ -63,6 +93,9 @@ module ChefUtils
# knife documentation page
KNIFE_DOCS = "https://docs.chef.io/workstation/knife/"
+
+ # the name of the overall infra product
+ PRODUCT = "Chef Infra"
end
class Server
@@ -87,6 +120,17 @@ module ChefUtils
EXEC = "chef-solo"
end
+ class Workstation
+ # The full marketing name of the product
+ PRODUCT = "Chef Workstation"
+
+ # The suffix for Chef Workstation's /opt/chef-workstation or C:\\opscode\chef-workstation
+ DIR_SUFFIX = "chef-workstation"
+
+ # Workstation banner/help text
+ DOCS = "https://docs.chef.io/workstation/"
+ end
+
class Zero
# chef-zero executable
PRODUCT = "Chef Infra Zero"
diff --git a/chef-utils/lib/chef-utils/dsl/cloud.rb b/chef-utils/lib/chef-utils/dsl/cloud.rb
index 9d4de55656..6e8fc520a5 100644
--- a/chef-utils/lib/chef-utils/dsl/cloud.rb
+++ b/chef-utils/lib/chef-utils/dsl/cloud.rb
@@ -23,7 +23,7 @@ module ChefUtils
module Cloud
include Internal
- # Determine if the current node is "in the cloud".
+ # Determine if the current node is running in a known cloud.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -35,7 +35,18 @@ module ChefUtils
!node["cloud"].nil?
end
- # Return true if the current current node is in EC2.
+ # Determine if the current node is running in Alibaba Cloud
+ #
+ # @param [Chef::Node] node the node to check
+ # @since 17.0
+ #
+ # @return [Boolean]
+ #
+ def alibaba?(node = __getnode)
+ node.key?("alibaba")
+ end
+
+ # Determine if the current node is running in AWS EC2.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -46,7 +57,7 @@ module ChefUtils
node.key?("ec2")
end
- # Return true if the current current node is in GCE.
+ # Determine if the current node running in Google Compute Engine (GCE).
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -57,7 +68,7 @@ module ChefUtils
node.key?("gce")
end
- # Return true if the current current node is in Rackspace.
+ # Determine if the current node is running in Rackspace.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -68,7 +79,7 @@ module ChefUtils
node.key?("rackspace")
end
- # Return true if the current current node is in Eucalyptus.
+ # Determine if the current node is running in Eucalyptus.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -81,7 +92,7 @@ module ChefUtils
# chef-sugar backcompat method
alias_method :euca?, :eucalyptus?
- # Return true if the current current node is in Linode.
+ # Determine if the current node is running in Linode.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -92,7 +103,7 @@ module ChefUtils
node.key?("linode")
end
- # Return true if the current current node is in OpenStack.
+ # Determine if the current node is running in OpenStack.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -103,7 +114,7 @@ module ChefUtils
node.key?("openstack")
end
- # Return true if the current current node is in Azure.
+ # Determine if the current node is running in Microsoft Azure.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -114,7 +125,7 @@ module ChefUtils
node.key?("azure")
end
- # Return true if the current current node is in DigitalOcean.
+ # Determine if the current node is running in DigitalOcean.
#
# @param [Chef::Node] node the node to check
# @since 15.8
@@ -127,7 +138,7 @@ module ChefUtils
# chef-sugar backcompat method
alias_method :digitalocean?, :digital_ocean?
- # Return true if the current current node is in SoftLayer.
+ # Determine if the current node is running in SoftLayer (IBM Cloud).
#
# @param [Chef::Node] node the node to check
# @since 15.8
diff --git a/chef-utils/lib/chef-utils/dsl/introspection.rb b/chef-utils/lib/chef-utils/dsl/introspection.rb
index 4edfb8d139..eff75727a9 100644
--- a/chef-utils/lib/chef-utils/dsl/introspection.rb
+++ b/chef-utils/lib/chef-utils/dsl/introspection.rb
@@ -29,6 +29,17 @@ module ChefUtils
module Introspection
include TrainHelpers
+ # Determine if the node is using the Chef Effortless pattern in which the Chef Infra Client is packaged using Chef Habitat
+ #
+ # @param [Chef::Node] node the node to check
+ # @since 17.0
+ #
+ # @return [Boolean]
+ #
+ def effortless?(node = __getnode)
+ !!(node && node.read("chef_packages", "chef", "chef_effortless"))
+ end
+
# Determine if the node is a docker container.
#
# @param [Chef::Node] node the node to check
diff --git a/chef-utils/lib/chef-utils/dsl/platform.rb b/chef-utils/lib/chef-utils/dsl/platform.rb
index f44cb811bb..9dd336b690 100644
--- a/chef-utils/lib/chef-utils/dsl/platform.rb
+++ b/chef-utils/lib/chef-utils/dsl/platform.rb
@@ -123,6 +123,21 @@ module ChefUtils
# chef-sugar backcompat method
alias_method :centos?, :centos_platform?
+ # Determine if the current node is CentOS Stream.
+ #
+ # @param [Chef::Node] node the node to check
+ # @since 17.0
+ #
+ # @return [Boolean]
+ #
+ def centos_stream_platform?(node = __getnode)
+ if node["os_release"]
+ node.dig("os_release", "name") == "CentOS Stream"
+ else
+ node.dig("lsb", "id") == "CentOSStream"
+ end
+ end
+
# Determine if the current node is Oracle Linux.
#
# @param [Chef::Node] node the node to check
diff --git a/chef-utils/lib/chef-utils/dsl/platform_family.rb b/chef-utils/lib/chef-utils/dsl/platform_family.rb
index 10d7b35901..dd3c589e77 100644
--- a/chef-utils/lib/chef-utils/dsl/platform_family.rb
+++ b/chef-utils/lib/chef-utils/dsl/platform_family.rb
@@ -77,7 +77,7 @@ module ChefUtils
# @return [Boolean]
#
def macos?(node = __getnode)
- node["platform_family"] == "mac_os_x"
+ node ? node["platform_family"] == "mac_os_x" : macos_ruby?
end
# chef-sugar backcompat method
alias_method :osx?, :macos?
@@ -86,6 +86,17 @@ module ChefUtils
# chef-sugar backcompat method
alias_method :mac_os_x?, :macos?
+ # Determine if the Ruby VM is currently running on a Mac node (This is useful primarily for internal use
+ # by Chef Infra Client before the node object exists).
+ #
+ # @since 17.3
+ #
+ # @return [Boolean]
+ #
+ def macos_ruby?
+ !!(RUBY_PLATFORM =~ /darwin/)
+ end
+
# Determine if the current node is a member of the 'rhel' platform family (Red Hat, CentOS, Oracle or Scientific Linux, but NOT Amazon Linux or Fedora).
#
# @param [Chef::Node] node the node to check
diff --git a/chef-utils/lib/chef-utils/dsl/windows.rb b/chef-utils/lib/chef-utils/dsl/windows.rb
index dc8cd4ebc0..be6a5c4b83 100644
--- a/chef-utils/lib/chef-utils/dsl/windows.rb
+++ b/chef-utils/lib/chef-utils/dsl/windows.rb
@@ -58,7 +58,7 @@ module ChefUtils
node["kernel"]["product_type"] == "Server"
end
- # Determine the current Windows NT version. The NT version often differs from the marketing version, but offers a good way to find desktop and server releases that are based on the same codebase. IE: NT 6.3 is Windows 8.1 and Windows 2012 R2.
+ # Determine the current Windows NT version. The NT version often differs from the marketing version, but offers a good way to find desktop and server releases that are based on the same codebase. For example NT 6.3 corresponds to Windows 8.1 and Windows 2012 R2.
#
# @param [Chef::Node] node the node to check
# @since 15.8
diff --git a/chef-utils/lib/chef-utils/internal.rb b/chef-utils/lib/chef-utils/internal.rb
index e5a7e65c89..0699618a5a 100644
--- a/chef-utils/lib/chef-utils/internal.rb
+++ b/chef-utils/lib/chef-utils/internal.rb
@@ -70,7 +70,7 @@ module ChefUtils
#
def __env_path
if __transport_connection
- __transport_connection.run_command("echo $PATH").stdout || ""
+ __transport_connection.run_command("echo $PATH").stdout.chomp || ""
else
ENV["PATH"] || ""
end
diff --git a/chef-utils/lib/chef-utils/mash.rb b/chef-utils/lib/chef-utils/mash.rb
index 484e172b33..bb48064aa3 100644
--- a/chef-utils/lib/chef-utils/mash.rb
+++ b/chef-utils/lib/chef-utils/mash.rb
@@ -94,6 +94,10 @@ module ChefUtils
end
end
+ unless method_defined?(:regular_reader)
+ alias_method :regular_reader, :[]
+ end
+
unless method_defined?(:regular_writer)
alias_method :regular_writer, :[]=
end
@@ -102,6 +106,11 @@ module ChefUtils
alias_method :regular_update, :update
end
+ # @param key<Object> The key to get.
+ def [](key)
+ regular_reader(key)
+ end
+
# @param key<Object> The key to set.
# @param value<Object>
# The value to set the key to.
@@ -114,6 +123,12 @@ module ChefUtils
# internal API for use by Chef's deep merge cache
# @api private
+ def internal_get(key)
+ regular_reader(key)
+ end
+
+ # internal API for use by Chef's deep merge cache
+ # @api private
def internal_set(key, value)
regular_writer(key, convert_value(value))
end
diff --git a/chef-utils/lib/chef-utils/parallel_map.rb b/chef-utils/lib/chef-utils/parallel_map.rb
new file mode 100644
index 0000000000..3c1be22006
--- /dev/null
+++ b/chef-utils/lib/chef-utils/parallel_map.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+#
+# Copyright:: Copyright (c) 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 "concurrent/executors"
+require "concurrent/future"
+require "singleton" unless defined?(Singleton)
+
+module ChefUtils
+ #
+ # This module contains ruby refinements that adds several methods to the Enumerable
+ # class which are useful for parallel processing.
+ #
+ module ParallelMap
+ refine Enumerable do
+
+ # Enumerates through the collection in parallel using the thread pool provided
+ # or the default thread pool. By using the default thread pool this supports
+ # recursively calling the method without deadlocking while using a globally
+ # fixed number of workers. This method supports lazy collections. It returns
+ # synchronously, waiting until all the work is done. Failures are only reported
+ # after the collection has executed and only the first exception is raised.
+ #
+ # (0..).lazy.parallel_map { |i| i*i }.first(5)
+ #
+ # @return [Array] output results
+ #
+ def parallel_map(pool: nil)
+ return self unless block_given?
+
+ pool ||= ChefUtils::DefaultThreadPool.instance.pool
+
+ futures = map do |item|
+ Concurrent::Future.execute(executor: pool) do
+ yield item
+ end
+ end
+
+ futures.map(&:value!)
+ end
+
+ # This has the same behavior as parallel_map but returns the enumerator instead of
+ # the return values.
+ #
+ # @return [Enumerable] the enumerable for method chaining
+ #
+ def parallel_each(pool: nil, &block)
+ return self unless block_given?
+
+ parallel_map(pool: pool, &block)
+
+ self
+ end
+
+ # The flat_each method is tightly coupled to the usage of parallel_map within the
+ # ChefFS implementation. It is not itself a parallel method, but it is used to
+ # iterate through the 2nd level of nested structure, which is tied to the nested
+ # structures that ChefFS returns.
+ #
+ # This is different from Enumerable#flat_map because that behaves like map.flatten(1) while
+ # this behaves more like flatten(1).each. We need this on an Enumerable, so we have no
+ # Enumerable#flatten method to call.
+ #
+ # [ [ 1, 2 ], [ 3, 4 ] ].flat_each(&block) calls block four times with 1, 2, 3, 4
+ #
+ # [ [ 1, 2 ], [ 3, 4 ] ].flat_map(&block) calls block twice with [1, 2] and [3,4]
+ #
+ def flat_each(&block)
+ map do |value|
+ if value.is_a?(Enumerable)
+ value.each(&block)
+ else
+ yield value
+ end
+ end
+ end
+ end
+ end
+
+ # The DefaultThreadPool has a fixed thread size and has no
+ # queue of work and the behavior on failure to find a thread is for the
+ # caller to run the work. This contract means that the thread pool can
+ # be called recursively without deadlocking and while keeping the fixed
+ # number of threads (and not exponentially growing the thread pool with
+ # the depth of recursion).
+ #
+ class DefaultThreadPool
+ include Singleton
+
+ DEFAULT_THREAD_SIZE = 10
+
+ # Size of the thread pool, must be set before getting the thread pool or
+ # calling parallel_map/parallel_each. Does not (but could be modified to)
+ # support dynamic resizing. To get fully synchronous behavior set this equal to
+ # zero rather than one since the caller will get work if the threads are
+ # busy.
+ #
+ # @return [Integer] number of threads
+ attr_accessor :threads
+
+ # Memoizing accessor for the thread pool
+ #
+ # @return [Concurrent::ThreadPoolExecutor] the thread pool
+ def pool
+ @pool ||= Concurrent::ThreadPoolExecutor.new(
+ min_threads: threads || DEFAULT_THREAD_SIZE,
+ max_threads: threads || DEFAULT_THREAD_SIZE,
+ max_queue: 0,
+ # "synchronous" redefines the 0 in max_queue to mean 'no queue' instead of 'infinite queue'
+ # it does not mean synchronous execution (no threads) but synchronous offload to the threads.
+ synchronous: true,
+ # this prevents deadlocks on recursive parallel usage
+ fallback_policy: :caller_runs
+ )
+ end
+ end
+end
diff --git a/chef-utils/lib/chef-utils/version.rb b/chef-utils/lib/chef-utils/version.rb
index 57f97f63b7..acaf40eb16 100644
--- a/chef-utils/lib/chef-utils/version.rb
+++ b/chef-utils/lib/chef-utils/version.rb
@@ -16,5 +16,5 @@
module ChefUtils
CHEFUTILS_ROOT = File.expand_path("..", __dir__)
- VERSION = "16.7.68"
+ VERSION = "17.3.0"
end
diff --git a/chef-utils/spec/unit/dsl/cloud_spec.rb b/chef-utils/spec/unit/dsl/cloud_spec.rb
index 8a718dc37d..9a88ce3825 100644
--- a/chef-utils/spec/unit/dsl/cloud_spec.rb
+++ b/chef-utils/spec/unit/dsl/cloud_spec.rb
@@ -45,6 +45,10 @@ RSpec.describe ChefUtils::DSL::Cloud do
end
end
+ context "on alibaba" do
+ cloud_reports_true_for(:cloud?, :alibaba?, node: { "alibaba" => {}, "cloud" => {} })
+ end
+
context "on ec2" do
cloud_reports_true_for(:cloud?, :ec2?, node: { "ec2" => {}, "cloud" => {} })
end
diff --git a/chef-utils/spec/unit/dsl/introspection_spec.rb b/chef-utils/spec/unit/dsl/introspection_spec.rb
index 2a841dee68..bcbe573ce1 100644
--- a/chef-utils/spec/unit/dsl/introspection_spec.rb
+++ b/chef-utils/spec/unit/dsl/introspection_spec.rb
@@ -32,6 +32,18 @@ RSpec.describe ChefUtils::DSL::Introspection do
let(:test_instance) { IntrospectionTestClass.new(node) }
+ context "#effortless?" do
+ # FIXME: use a real VividMash for these tests instead of stubbing
+ it "is false by default" do
+ expect(node).to receive(:read).with("chef_packages", "chef", "chef_effortless").and_return(nil)
+ expect(ChefUtils.effortless?(node)).to be false
+ end
+ it "is true when ohai reports a effortless" do
+ expect(node).to receive(:read).with("chef_packages", "chef", "chef_effortless").and_return(true)
+ expect(ChefUtils.effortless?(node)).to be true
+ end
+ end
+
context "#docker?" do
# FIXME: use a real VividMash for these tests instead of stubbing
it "is false by default" do
diff --git a/chef-utils/spec/unit/dsl/platform_family_spec.rb b/chef-utils/spec/unit/dsl/platform_family_spec.rb
index fdf4584afd..c4363c8e8d 100644
--- a/chef-utils/spec/unit/dsl/platform_family_spec.rb
+++ b/chef-utils/spec/unit/dsl/platform_family_spec.rb
@@ -25,7 +25,7 @@ def pf_reports_true_for(*args)
expect(described_class.send(method, node)).to be true
end
end
- (PLATFORM_FAMILY_HELPERS - [ :windows_ruby? ] - args).each do |method|
+ (PLATFORM_FAMILY_HELPERS - %i{windows_ruby? macos_ruby?} - args).each do |method|
it "reports false for #{method}" do
expect(described_class.send(method, node)).to be false
end
@@ -41,7 +41,7 @@ RSpec.describe ChefUtils::DSL::PlatformFamily do
end
end
- ( PLATFORM_FAMILY_HELPERS - [ :windows_ruby? ]).each do |helper|
+ ( PLATFORM_FAMILY_HELPERS - %i{windows_ruby? macos_ruby?}).each do |helper|
it "has the #{helper} in the ChefUtils module" do
expect(ChefUtils).to respond_to(helper)
end
@@ -90,13 +90,13 @@ RSpec.describe ChefUtils::DSL::PlatformFamily do
end
context "on centos6" do
- let(:options) { { platform: "centos", version: "6.10" } }
+ let(:options) { { platform: "centos", version: "6" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel6?)
end
context "on centos7" do
- let(:options) { { platform: "centos", version: "7.7.1908" } }
+ let(:options) { { platform: "centos", version: "7" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel7?)
end
@@ -108,7 +108,7 @@ RSpec.describe ChefUtils::DSL::PlatformFamily do
end
context "on clearos7" do
- let(:options) { { platform: "clearos", version: "7.4" } }
+ let(:options) { { platform: "clearos", version: "7" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel7?)
end
@@ -156,25 +156,25 @@ RSpec.describe ChefUtils::DSL::PlatformFamily do
end
context "on oracle6" do
- let(:options) { { platform: "oracle", version: "6.10" } }
+ let(:options) { { platform: "oracle", version: "6" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel6?)
end
context "on oracle7" do
- let(:options) { { platform: "oracle", version: "7.6" } }
+ let(:options) { { platform: "oracle", version: "7" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel7?)
end
context "on redhat6" do
- let(:options) { { platform: "redhat", version: "6.10" } }
+ let(:options) { { platform: "redhat", version: "6" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel6?)
end
context "on redhat7" do
- let(:options) { { platform: "redhat", version: "7.6" } }
+ let(:options) { { platform: "redhat", version: "7" } }
pf_reports_true_for(:rhel?, :rpm_based?, :fedora_derived?, :redhat_based?, :el?, :rhel7?)
end
@@ -220,4 +220,16 @@ RSpec.describe ChefUtils::DSL::PlatformFamily do
end
end
end
+
+ context "node-independent mac APIs" do
+ if RUBY_PLATFORM.match?(/darwin/)
+ it "reports true for :macos_ruby?" do
+ expect(described_class.macos_ruby?).to be true
+ end
+ else
+ it "reports false for :macos_ruby?" do
+ expect(described_class.macos_ruby?).to be false
+ end
+ end
+ end
end
diff --git a/chef-utils/spec/unit/dsl/platform_spec.rb b/chef-utils/spec/unit/dsl/platform_spec.rb
index 216e15f112..8ebdcbae8f 100644
--- a/chef-utils/spec/unit/dsl/platform_spec.rb
+++ b/chef-utils/spec/unit/dsl/platform_spec.rb
@@ -145,6 +145,20 @@ RSpec.describe ChefUtils::DSL::Platform do
platform_reports_true_for(:centos?, :centos_platform?)
end
+ context "on centos stream w/o os_release" do
+ let(:options) { { platform: "centos" } }
+ let(:node) { { "platform" => "centos", "platform_version" => "8", "platform_family" => "rhel", "os" => "linux", "lsb" => { "id" => "CentOSStream" }, "os_release" => nil } }
+
+ platform_reports_true_for(:centos?, :centos_platform?, :centos_stream_platform?)
+ end
+
+ context "on centos stream w/ os_release" do
+ let(:options) { { platform: "centos" } }
+ let(:node) { { "platform" => "centos", "platform_version" => "8", "platform_family" => "rhel", "os" => "linux", "os_release" => { "name" => "CentOS Stream" } } }
+
+ platform_reports_true_for(:centos?, :centos_platform?, :centos_stream_platform?)
+ end
+
context "on clearos" do
let(:options) { { platform: "clearos" } }
diff --git a/chef-utils/spec/unit/parallel_map_spec.rb b/chef-utils/spec/unit/parallel_map_spec.rb
new file mode 100644
index 0000000000..1346a9171e
--- /dev/null
+++ b/chef-utils/spec/unit/parallel_map_spec.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+#
+# Copyright:: Copyright (c) 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 "chef-utils/parallel_map"
+
+using ChefUtils::ParallelMap
+
+RSpec.describe ChefUtils::ParallelMap do
+
+ shared_examples_for "common parallel API tests" do
+
+ before(:each) do
+ ChefUtils::DefaultThreadPool.instance.instance_variable_set(:@pool, nil)
+ ChefUtils::DefaultThreadPool.instance.threads = threads
+ end
+
+ after(:each) do
+ ChefUtils::DefaultThreadPool.instance.instance_variable_set(:@pool, nil)
+ end
+
+ it "parallel_map runs in parallel" do
+ # this is implicitly also testing that we run in the caller when we exhaust threads by running threads+1
+ val = threads + 1
+ ret = []
+ start = Time.now
+ (1..val).parallel_map do |i|
+ loop do
+ if val == i
+ ret << i
+ val -= 1
+ break
+ end
+ # we spin for quite awhile to wait for very slow testers if we have to
+ if Time.now - start > 30
+ raise "timed out; deadlocked due to lack of parallelization?"
+ end
+
+ # need to sleep a tiny bit to let other threads schedule
+ sleep 0.000001
+ end
+ end
+ expected = (1..threads + 1).to_a.reverse
+ expect(ret).to eql(expected)
+ end
+
+ it "parallel_each runs in parallel" do
+ # this is implicitly also testing that we run in the caller when we exhaust threads by running threads+1
+ val = threads + 1
+ ret = []
+ start = Time.now
+ (1..val).parallel_each do |i|
+ loop do
+ if val == i
+ ret << i
+ val -= 1
+ break
+ end
+ # we spin for quite awhile to wait for very slow testers if we have to
+ if Time.now - start > 30
+ raise "timed out; deadlocked due to lack of parallelization?"
+ end
+
+ # need to sleep a tiny bit to let other threads schedule
+ sleep 0.000001
+ end
+ end
+ expected = (1..threads + 1).to_a.reverse
+ expect(ret).to eql(expected)
+ end
+
+ it "parallel_map throws exceptions" do
+ expect { (0..10).parallel_map { |i| raise "boom" } }.to raise_error(RuntimeError)
+ end
+
+ it "parallel_each throws exceptions" do
+ expect { (0..10).parallel_each { |i| raise "boom" } }.to raise_error(RuntimeError)
+ end
+
+ it "parallel_map runs" do
+ ans = Timeout.timeout(30) do
+ (1..10).parallel_map { |i| i }
+ end
+ expect(ans).to eql((1..10).to_a)
+ end
+
+ it "parallel_each runs" do
+ Timeout.timeout(30) do
+ (1..10).parallel_each { |i| i }
+ end
+ end
+
+ it "recursive parallel_map will not deadlock" do
+ ans = Timeout.timeout(30) do
+ (1..2).parallel_map { |i| (1..2).parallel_map { |i| i } }
+ end
+ expect(ans).to eql([[1, 2], [1, 2]])
+ end
+
+ it "recursive parallel_each will not deadlock" do
+ Timeout.timeout(30) do
+ (1..2).parallel_each { |i| (1..2).parallel_each { |i| i } }
+ end
+ end
+
+ it "parallel_map is lazy" do
+ ans = Timeout.timeout(30) do
+ (1..).lazy.parallel_map { |i| i }.first(5)
+ end
+ expect(ans).to eql((1..5).to_a)
+ end
+
+ it "parallel_each is lazy" do
+ Timeout.timeout(30) do
+ (1..).lazy.parallel_each { |i| i }.first(5)
+ end
+ end
+ end
+
+ context "with 10 threads" do
+ let(:threads) { 10 }
+ it_behaves_like "common parallel API tests"
+ end
+
+ context "with 0 threads" do
+ let(:threads) { 0 }
+ it_behaves_like "common parallel API tests"
+ end
+
+ context "with 1 threads" do
+ let(:threads) { 1 }
+ it_behaves_like "common parallel API tests"
+ end
+
+ context "flat_each" do
+ it "runs each over items which are nested one level" do
+ sum = 0
+ [ [ 1, 2 ], [3, 4]].flat_each { |i| sum += i }
+ expect(sum).to eql(10)
+ end
+ end
+end
diff --git a/chef.gemspec b/chef.gemspec
index 9379e74fa7..76f562cc71 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -1,4 +1,13 @@
$:.unshift(File.dirname(__FILE__) + "/lib")
+vs_path = File.expand_path("chef-utils/lib/chef-utils/version_string.rb", __dir__)
+
+if File.exist?(vs_path)
+ # this is the moral equivalent of a require_relative since bundler makes require_relative here fail hard
+ eval(IO.read(vs_path))
+else
+ # if the path doesn't exist then we're just in the wild gem and not in the git repo
+ require "chef-utils/version_string"
+end
require "chef/version"
Gem::Specification.new do |s|
@@ -26,45 +35,41 @@ Gem::Specification.new do |s|
s.add_dependency "mixlib-authentication", ">= 2.1", "< 4"
s.add_dependency "mixlib-shellout", ">= 3.1.1", "< 4.0"
s.add_dependency "mixlib-archive", ">= 0.4", "< 2.0"
- s.add_dependency "ohai", "~> 16.0"
+ s.add_dependency "ohai", "~> 17.0"
s.add_dependency "inspec-core", "~> 4.23"
- s.add_dependency "ffi", ">= 1.9.25"
+ s.add_dependency "ffi", ">= 1.5.0"
s.add_dependency "ffi-yajl", "~> 2.2"
- s.add_dependency "net-ssh", ">= 4.2", "< 7"
- s.add_dependency "net-ssh-multi", "~> 1.2", ">= 1.2.1"
- s.add_dependency "net-sftp", ">= 2.1.2", "< 4.0"
- s.add_dependency "ed25519", "~> 1.2" # ed25519 ssh key support
- s.add_dependency "bcrypt_pbkdf", "= 1.1.0.rc1" # ed25519 ssh key support
- s.add_dependency "highline", ">= 1.6.9", "< 3"
- s.add_dependency "tty-prompt", "~> 0.21" # knife ui.ask prompt
- s.add_dependency "tty-screen", "~> 0.6" # knife list
- s.add_dependency "tty-table", "~> 0.11" # knife render table output.
- s.add_dependency "pastel" # knife ui.color
- s.add_dependency "erubis", "~> 2.7"
- s.add_dependency "diff-lcs", ">= 1.2.4", "< 1.4.0" # 1.4 breaks output
- s.add_dependency "ffi-libarchive", "~> 1.0", ">= 1.0.3"
+ s.add_dependency "net-sftp", ">= 2.1.2", "< 4.0" # remote_file resource
+ s.add_dependency "erubis", "~> 2.7" # template resource / cookbook syntax check
+ s.add_dependency "diff-lcs", ">= 1.2.4", "< 1.4.0" # 1.4 breaks output. Used in lib/chef/util/diff
+ s.add_dependency "ffi-libarchive", "~> 1.0", ">= 1.0.3" # archive_file resource
s.add_dependency "chef-zero", ">= 14.0.11"
- s.add_dependency "chef-vault"
+ s.add_dependency "chef-vault" # chef-vault resources and helpers
- s.add_dependency "plist", "~> 3.2"
- s.add_dependency "iniparse", "~> 1.4"
+ s.add_dependency "plist", "~> 3.2" # launchd, dscl/mac user, macos_userdefaults, osx_profile and plist resources
+ s.add_dependency "iniparse", "~> 1.4" # systemd_unit resource
s.add_dependency "addressable"
s.add_dependency "syslog-logger", "~> 1.6"
- s.add_dependency "uuidtools", "~> 2.1.5"
+ s.add_dependency "uuidtools", ">= 2.1.5", "< 3.0" # osx_profile resource
s.add_dependency "proxifier", "~> 1.0"
- # v1.10 is needed as a runtime dep now for 'bundler/inline'
- # very deliberately avoiding putting a ceiling on this to avoid depsolver conflicts.
- s.add_dependency "bundler", ">= 1.10"
-
s.bindir = "bin"
- s.executables = %w{ knife }
+ s.executables = %w{ }
s.require_paths = %w{ lib }
s.files = %w{Gemfile Rakefile LICENSE README.md} +
Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } +
Dir.glob("*.gemspec") +
Dir.glob("tasks/rspec.rb")
+
+ s.metadata = {
+ "bug_tracker_uri" => "https://github.com/chef/chef/issues",
+ "changelog_uri" => "https://github.com/chef/chef/blob/master/CHANGELOG.md",
+ "documentation_uri" => "https://docs.chef.io/",
+ "homepage_uri" => "https://www.chef.io",
+ "mailing_list_uri" => "https://discourse.chef.io/",
+ "source_code_uri" => "https://github.com/chef/chef/",
+ }
end
diff --git a/cspell.json b/cspell.json
index 2c34043b30..614bb8f06d 100644
--- a/cspell.json
+++ b/cspell.json
@@ -14,6 +14,7 @@
"dictionaries": ["chef"],
// words - list of words to be always considered correct
"words": [
+ "IOAV",
"abcz",
"Abdulin",
"ABORTIFHUNG",
@@ -36,6 +37,8 @@
"albertomurillo",
"Albertson",
"Algorta",
+ "alibase",
+ "aliyun",
"Alloc",
"allowlist",
"allowlisted",
@@ -62,9 +65,8 @@
"ARPHELPLINK",
"ARPPRODUCTICON",
"arry",
- "Arțăriși",
"artem",
- "Ásgeirsson",
+ "Arțăriși",
"Ashwini",
"ASSIGNPRIMARYTOKEN",
"astoltz",
@@ -100,10 +102,6 @@
"backends",
"backoff",
"BACKOFFICE",
- "backport",
- "backported",
- "backporting",
- "Backporting",
"backslashing",
"backtrace",
"backtraces",
@@ -132,29 +130,21 @@
"bkup",
"blockdev",
"blocklist",
- "blocklisted",
- "blocklists",
- "bmhatfield",
- "bobberson",
- "bobchaos",
"bobo",
"bobotclown",
- "bootproto",
"bootstrapper",
"bootstrappers",
"borat",
- "Brimager",
"brodock",
"bsearch",
"bsevn",
"bufptr",
"bufsize",
"bugfix",
- "bugfixes",
"Bugfixes",
+ "bugfixes",
"bugfixing",
"bugok",
- "Buildkite",
"BUNDER",
"busybox",
"bypassproxy",
@@ -182,7 +172,6 @@
"CFLAGS",
"CFPREFERENCES",
"cfprefsd",
- "cgroup",
"chadmccune",
"Chaput",
"chardev",
@@ -198,10 +187,6 @@
"chefclientinstalldone",
"CHEFCONFIG",
"chefdir",
- "chefdk",
- "cheffish",
- "cheffs",
- "Chefignore",
"chefignored",
"chefignores",
"chefscriptresult",
@@ -209,33 +194,26 @@
"cheftest",
"CHEFUTILS",
"chefzero",
- "chgroup",
- "chgrp",
"chgrpmem",
"chilcote",
"CHINESEBIG",
- "chkconfig",
- "CHKCONFIG",
"Chouhan",
"ckbk",
"cksum",
"cleanpath",
"cleanpaths",
"cleanroom",
- "clearlinux",
- "clearos",
"CLEARTEXT",
"CLIENTAUTHISSUER",
"clientname",
"CLOEXEC",
- "Cloke",
"cmdlets",
- "cmds",
"CMDS",
+ "cmds",
"Cmnd",
+ "CMPL",
"cname",
"codepage",
- "coderanger",
"CODESEG",
"colleciton",
"COLORINDEX",
@@ -243,14 +221,13 @@
"COLORSPACE",
"COMBOBOX",
"commandline",
- "commmand",
"compat",
"COMPOSITECHECK",
"computerlevel",
"COMPUTERNAME",
"computersystem",
- "comspec",
"COMSPEC",
+ "comspec",
"concat",
"Cond",
"confd",
@@ -270,9 +247,8 @@
"convertto",
"cookbookname",
"cookiecurse",
- "cookstyle",
"Cookstyle",
- "coookbook",
+ "cookstyle",
"copypasta",
"coreutils",
"Cowie",
@@ -302,8 +278,8 @@
"Cxxx",
"dacl",
"Daemonization",
- "daemonizing",
"Daemonizing",
+ "daemonizing",
"databag",
"databags",
"Datacenter",
@@ -342,7 +318,6 @@
"depsolved",
"Depsolving",
"derekgroh",
- "Désarmes",
"DESCR",
"deserialization",
"destdir",
@@ -371,8 +346,8 @@
"disablerepo",
"DISCARDABLE",
"DISCARDNS",
- "dism",
"DISM",
+ "dism",
"displayname",
"distro",
"distros",
@@ -384,8 +359,8 @@
"dockerenv",
"dockerignore",
"dockerinit",
- "dokken",
"Dokken",
+ "dokken",
"domainandname",
"domainname",
"domainuser",
@@ -416,6 +391,7 @@
"DWORDLONG",
"DYNALINK",
"DYNLINK",
+ "Désarmes",
"Eachern",
"EASTEUROPE",
"EBUSY",
@@ -423,6 +399,7 @@
"ecparam",
"edir",
"egid",
+ "eipv",
"elif",
"ELOOP",
"EMBEDDEDBIN",
@@ -433,8 +410,8 @@
"enablement",
"enablerepo",
"encap",
- "encryptor",
"Encryptor",
+ "encryptor",
"endlocal",
"ENOENT",
"entriesread",
@@ -456,10 +433,10 @@
"ESRCH",
"etag",
"etags",
- "etcd",
"Etcd",
- "ethtool",
+ "etcd",
"ETHTOOL",
+ "ethtool",
"ETIMEDOUT",
"euca",
"Eugen",
@@ -477,8 +454,8 @@
"EXTGLOB",
"extname",
"extrastuff",
- "exts",
"Exts",
+ "exts",
"FACs",
"failburger",
"FAILCRITICALERRORS",
@@ -494,9 +471,10 @@
"FASTZIPDIR",
"fatals",
"fauxhai",
+ "fcntl",
"featurename",
- "fflags",
"FFLAGS",
+ "fflags",
"Fichter",
"fieldname",
"Fileexists",
@@ -538,8 +516,8 @@
"FULLSCREEN",
"fuzzifier",
"fuzzify",
- "fuzzyurl",
"Fuzzyurl",
+ "fuzzyurl",
"fzipi",
"gaffneyc",
"gecos",
@@ -550,6 +528,7 @@
"getc",
"getchef",
"GETFD",
+ "GETFL",
"getgrgid",
"getgrnam",
"gethostbyname",
@@ -567,6 +546,7 @@
"getremotelogin",
"getspnam",
"gettext",
+ "GETTHUMBPRINTCODE",
"gettimezone",
"gettype",
"gids",
@@ -625,7 +605,6 @@
"HHOOK",
"HIBYTE",
"HICON",
- "hidemins",
"Hinderliter",
"HINSTANCE",
"hintname",
@@ -645,13 +624,13 @@
"HMODULE",
"HMONITOR",
"HOMEDRIVE",
- "homepath",
"HOMEPATH",
+ "homepath",
"HOMESHARE",
"Hongli",
"hostnamectl",
- "hostnames",
"Hostnames",
+ "hostnames",
"hostport",
"hostspec",
"hostspecific",
@@ -663,7 +642,6 @@
"HRGN",
"HRSRC",
"Hsiao",
- "htop",
"httpd",
"Huon",
"hwaddr",
@@ -690,8 +668,8 @@
"includedir",
"includepkgs",
"includer",
- "indentable",
"Indentable",
+ "indentable",
"indentt",
"inet",
"infile",
@@ -724,12 +702,12 @@
"invokereturnasis",
"Ionuț",
"IOPL",
- "ipaddr",
"IPADDR",
+ "ipaddr",
"ipaddress",
"Ippolito",
- "ipscopes",
"Ipscopes",
+ "ipscopes",
"ipsec",
"iptables",
"Ireton",
@@ -765,8 +743,8 @@
"jxvf",
"katello",
"Kauppila",
- "kaustubh",
"Kaustubh",
+ "kaustubh",
"Keane",
"keepalive",
"keepalives",
@@ -826,8 +804,8 @@
"LIBPATH",
"libtool",
"libvirt",
- "libxml",
"Libxml",
+ "libxml",
"libxslt",
"libyaml",
"lifecycle",
@@ -908,8 +886,8 @@
"Madsen",
"magick",
"mailservers",
- "mailslot",
"MAILSLOT",
+ "mailslot",
"mailto",
"mainloop",
"Mainmodule",
@@ -941,8 +919,8 @@
"merlinjim",
"MESSAGEDEST",
"MESSAGENAME",
- "metafile",
"METAFILE",
+ "metafile",
"metalink",
"metasearch",
"Miah",
@@ -960,14 +938,14 @@
"mirrorlist",
"mirrorlists",
"MITM",
- "mixins",
"Mixins",
+ "mixins",
"mixlib",
"mkdir",
"mkgroup",
"mkmf",
- "mktemp",
"MKTEMP",
+ "mktemp",
"mktmpdir",
"mname",
"mntfs",
@@ -999,13 +977,13 @@
"msys",
"MSYSTEM",
"Mtyj",
- "Multicast",
"MULTICAST",
+ "Multicast",
"multiline",
"multipackage",
"multiplatform",
- "multiresource",
"Multiresource",
+ "multiresource",
"multitenant",
"multithreaded",
"Multiuser",
@@ -1018,8 +996,8 @@
"Mware",
"myapp",
"mycert",
- "mycook",
"Mycook",
+ "mycook",
"mycorp",
"mydirectory",
"myecrequest",
@@ -1053,6 +1031,7 @@
"NETBINDDISABLE",
"NETBINDENABLE",
"NETBINDREMOVE",
+ "netbw",
"NETCARD",
"netdom",
"NETLOGON",
@@ -1105,8 +1084,8 @@
"nopasswd",
"noprofile",
"noprogressbar",
- "norestart",
"NORESTART",
+ "norestart",
"nospace",
"nospinner",
"notapplicable",
@@ -1128,8 +1107,8 @@
"objfs",
"objs",
"OEMCP",
- "ohai",
"Ohai",
+ "ohai",
"Ojeda",
"oldguard",
"OLDLOGLOCATION",
@@ -1141,8 +1120,8 @@
"oneshot",
"onidle",
"onlogon",
- "onparent",
"ONPARENT",
+ "onparent",
"onstart",
"OPLOCK",
"OPTARG",
@@ -1169,11 +1148,13 @@
"Parallelizer",
"PARAMCHANGE",
"parms",
+ "Partof",
"passstr",
"passw",
"passwordage",
"passwordless",
"PASSWORDNAME",
+ "passwordprompt",
"PATCHLEVEL",
"pathed",
"PATHEXT",
@@ -1230,8 +1211,8 @@
"plutil",
"PNAME",
"Policybuilder",
- "policyfile",
"Policyfile",
+ "policyfile",
"Policyfiles",
"POLICYNAME",
"popen",
@@ -1281,10 +1262,10 @@
"PROTSEQ",
"PROTSEQS",
"proxified",
- "proxifier",
"Proxifier",
- "psapi",
+ "proxifier",
"PSAPI",
+ "psapi",
"pschaumburg",
"Pscx",
"Psec",
@@ -1330,8 +1311,8 @@
"rassoc",
"rbag",
"rbconfig",
- "rbenv",
"RBENV",
+ "rbenv",
"rcscript",
"rcvar",
"rdoc",
@@ -1342,8 +1323,8 @@
"realloc",
"realname",
"realpath",
- "rebooter",
"Rebooter",
+ "rebooter",
"rebootnow",
"rebuilddb",
"reconfig",
@@ -1357,8 +1338,8 @@
"REDIRECTOR",
"Redistributable",
"redownloading",
- "reenable",
"Reenable",
+ "reenable",
"reenabled",
"reenabling",
"refcount",
@@ -1430,8 +1411,8 @@
"runlock",
"runpid",
"runrun",
- "runtimes",
"Runtimes",
+ "runtimes",
"RXACT",
"rxhash",
"rxvlan",
@@ -1473,6 +1454,7 @@
"SETCOUNT",
"setenv",
"SETFD",
+ "SETFL",
"setlocal",
"SETMARK",
"setobj",
@@ -1568,16 +1550,16 @@
"STARTUPINFO",
"stdcall",
"stdlib",
- "stepable",
"Stepable",
+ "stepable",
"stopsrc",
"strace",
"Stratton",
"strftime",
"stringio",
"strptime",
- "struct",
"Struct",
+ "struct",
"stubabble",
"stubbable",
"subclassable",
@@ -1615,16 +1597,16 @@
"swapon",
"swappable",
"swappiness",
- "symlink",
"Symlink",
+ "symlink",
"symlinked",
"symlinking",
"symlinks",
"sync",
"syntaxcache",
"sysadminctl",
- "sysconf",
"Sysconf",
+ "sysconf",
"sysconfig",
"sysctl",
"sysctld",
@@ -1634,8 +1616,8 @@
"syslog",
"sysread",
"systctl",
- "systemdrive",
"SYSTEMDRIVE",
+ "systemdrive",
"systemrestart",
"SYSTEMROOT",
"systemsetup",
@@ -1670,6 +1652,7 @@
"Thom",
"THRDS",
"THREADID",
+ "THREADSIZE",
"thumbsup",
"tilesize",
"Timberman",
@@ -1710,8 +1693,8 @@
"udiff",
"UHALF",
"uids",
- "uint",
"UINT",
+ "uint",
"ulong",
"ULONGLONG",
"UNAVAIL",
@@ -1720,17 +1703,18 @@
"uncommented",
"uncompress",
"Uncompressing",
+ "unbuffered",
"Undock",
"unencrypted",
"unescaping",
"UNEXP",
"UNEXPORTED",
"unforked",
- "unformatter",
"Unformatter",
+ "unformatter",
"unhold",
- "unicast",
"Unicast",
+ "unicast",
"unignored",
"uninst",
"unintuitive",
@@ -1759,8 +1743,8 @@
"untap",
"untar",
"untracked",
- "untrusted",
"Untrusted",
+ "untrusted",
"unversioned",
"UNWRITABLE",
"upcase",
@@ -1854,8 +1838,8 @@
"winevt",
"Winmgmt",
"winmgmts",
- "winnt",
"WINNT",
+ "winnt",
"winprog",
"WINVER",
"WIXUI",
@@ -1890,7 +1874,8 @@
"zolo",
"zombiejs",
"Zuazo",
- "zypp"
+ "zypp",
+ "Ásgeirsson"
],
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
@@ -1908,12 +1893,12 @@
"**/*.yml",
"**/*.toml",
"**/Berksfile",
- "spec/data/**/*",
"lib/chef/provider/package/yum/simplejson/**/*",
"lib/chef/provider/package/yum/simplejson/*",
"omnibus/resources/chef/**/*",
- "kitchen-tests/cookbooks/end_to_end/files/*",
+ "kitchen-tests/cookbooks/end_to_end/files/**/*",
"spec/**",
+ "knife/spec/**",
"docs_site",
"distro/ruby_bin_folder/**/*"
],
diff --git a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll
index 022591ffd1..cfe7078d38 100644
--- a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll
+++ b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll
index 2316fffdb5..dbffc87ece 100644
--- a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll
+++ b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/Newtonsoft.Json.dll b/distro/ruby_bin_folder/AMD64/Newtonsoft.Json.dll
index 1971a35679..7af125a246 100644
--- a/distro/ruby_bin_folder/AMD64/Newtonsoft.Json.dll
+++ b/distro/ruby_bin_folder/AMD64/Newtonsoft.Json.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
index cb9a5c276b..7b1c4d14d6 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
index 3f472f2c81..44ba78d3fa 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
index 03a0860086..d3bf195294 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
index a415e4237a..03e2618ef2 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
@@ -9,14 +9,14 @@
".NETCoreApp,Version=v5.0/win10-x64": {
"Chef.Powershell.Core/1.0.0": {
"dependencies": {
- "Microsoft.PowerShell.Commands.Diagnostics": "7.1.0",
- "Microsoft.PowerShell.Commands.Management": "7.1.0",
- "Microsoft.PowerShell.Commands.Utility": "7.1.0",
- "Microsoft.PowerShell.ConsoleHost": "7.1.0",
- "Microsoft.WSMan.Management": "7.1.0",
- "Microsoft.WSMan.Runtime": "7.1.0",
- "Newtonsoft.Json": "12.0.3",
- "System.Management.Automation": "7.1.0",
+ "Microsoft.PowerShell.Commands.Diagnostics": "7.1.3",
+ "Microsoft.PowerShell.Commands.Management": "7.1.3",
+ "Microsoft.PowerShell.Commands.Utility": "7.1.3",
+ "Microsoft.PowerShell.ConsoleHost": "7.1.3",
+ "Microsoft.WSMan.Management": "7.1.3",
+ "Microsoft.WSMan.Runtime": "7.1.3",
+ "Newtonsoft.Json": "13.0.1",
+ "System.Management.Automation": "7.1.3",
"runtimepack.Microsoft.NETCore.App.Runtime.win-x64": "5.0.0"
},
"runtime": {
@@ -1037,78 +1037,78 @@
}
}
},
- "Microsoft.NETCore.Platforms/5.0.0": {},
+ "Microsoft.NETCore.Platforms/5.0.1": {},
"Microsoft.NETCore.Targets/1.1.0": {},
- "Microsoft.PowerShell.Commands.Diagnostics/7.1.0": {
+ "Microsoft.PowerShell.Commands.Diagnostics/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Diagnostics.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.Commands.Management/7.1.0": {
+ "Microsoft.PowerShell.Commands.Management/7.1.3": {
"dependencies": {
- "Microsoft.PowerShell.Security": "7.1.0",
+ "Microsoft.PowerShell.Security": "7.1.3",
"System.ServiceProcess.ServiceController": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Management.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.Commands.Utility/7.1.0": {
+ "Microsoft.PowerShell.Commands.Utility/7.1.3": {
"dependencies": {
"Microsoft.CodeAnalysis.CSharp": "3.7.0",
- "Microsoft.PowerShell.MarkdownRender": "7.1.0",
+ "Microsoft.PowerShell.MarkdownRender": "7.1.3",
"NJsonSchema": "10.2.2",
- "System.Drawing.Common": "5.0.0",
- "System.Management.Automation": "7.1.0",
+ "System.Drawing.Common": "5.0.2",
+ "System.Management.Automation": "7.1.3",
"System.Threading.AccessControl": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Utility.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.ConsoleHost/7.1.0": {
+ "Microsoft.PowerShell.ConsoleHost/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.ConsoleHost.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.CoreCLR.Eventing/7.1.0": {
+ "Microsoft.PowerShell.CoreCLR.Eventing/7.1.3": {
"dependencies": {
- "System.Diagnostics.EventLog": "5.0.0"
+ "System.Diagnostics.EventLog": "5.0.1"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.CoreCLR.Eventing.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.MarkdownRender/7.1.0": {
+ "Microsoft.PowerShell.MarkdownRender/7.1.3": {
"dependencies": {
"Markdig.Signed": "0.21.1",
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.MarkdownRender.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1122,14 +1122,14 @@
}
}
},
- "Microsoft.PowerShell.Security/7.1.0": {
+ "Microsoft.PowerShell.Security/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Security.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1159,7 +1159,7 @@
},
"Microsoft.Win32.SystemEvents/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0"
+ "Microsoft.NETCore.Platforms": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll": {
@@ -1168,24 +1168,24 @@
}
}
},
- "Microsoft.WSMan.Management/7.1.0": {
+ "Microsoft.WSMan.Management/7.1.3": {
"dependencies": {
- "Microsoft.WSMan.Runtime": "7.1.0",
- "System.Management.Automation": "7.1.0",
+ "Microsoft.WSMan.Runtime": "7.1.3",
+ "System.Management.Automation": "7.1.3",
"System.ServiceProcess.ServiceController": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.WSMan.Management.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.WSMan.Runtime/7.1.0": {
+ "Microsoft.WSMan.Runtime/7.1.3": {
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.WSMan.Runtime.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1200,18 +1200,18 @@
}
}
},
- "Newtonsoft.Json/12.0.3": {
+ "Newtonsoft.Json/13.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
- "assemblyVersion": "12.0.0.0",
- "fileVersion": "12.0.3.23909"
+ "assemblyVersion": "13.0.0.0",
+ "fileVersion": "13.0.1.25517"
}
}
},
"NJsonSchema/10.2.2": {
"dependencies": {
"Namotion.Reflection": "1.0.14",
- "Newtonsoft.Json": "12.0.3"
+ "Newtonsoft.Json": "13.0.1"
},
"runtime": {
"lib/netstandard2.0/NJsonSchema.dll": {
@@ -1359,7 +1359,7 @@
},
"System.Collections/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Collections": "4.3.0"
@@ -1387,7 +1387,7 @@
},
"System.Diagnostics.Debug/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.win.System.Diagnostics.Debug": "4.3.0"
@@ -1401,22 +1401,26 @@
}
}
},
- "System.Diagnostics.EventLog/5.0.0": {
+ "System.Diagnostics.EventLog/5.0.1": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.Win32.Registry": "5.0.0",
"System.Security.Principal.Windows": "5.0.0"
},
"runtime": {
+ "runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.Messages.dll": {
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "0.0.0.0"
+ },
"runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "5.0.321.7212"
}
}
},
"System.DirectoryServices/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.AccessControl": "5.0.0",
"System.Security.Permissions": "5.0.0",
@@ -1429,14 +1433,14 @@
}
}
},
- "System.Drawing.Common/5.0.0": {
+ "System.Drawing.Common/5.0.2": {
"dependencies": {
"Microsoft.Win32.SystemEvents": "5.0.0"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.2",
+ "fileVersion": "5.0.421.11614"
}
}
},
@@ -1474,7 +1478,7 @@
},
"System.Globalization/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Globalization": "4.3.0"
@@ -1482,7 +1486,7 @@
},
"System.IO/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
@@ -1546,7 +1550,7 @@
},
"System.Management/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.Win32.Registry": "5.0.0",
"System.CodeDom": "5.0.0"
},
@@ -1557,28 +1561,28 @@
}
}
},
- "System.Management.Automation/7.1.0": {
+ "System.Management.Automation/7.1.3": {
"dependencies": {
"Microsoft.ApplicationInsights": "2.15.0",
"Microsoft.Management.Infrastructure": "2.0.0",
- "Microsoft.PowerShell.CoreCLR.Eventing": "7.1.0",
+ "Microsoft.PowerShell.CoreCLR.Eventing": "7.1.3",
"Microsoft.PowerShell.Native": "7.1.0",
"Microsoft.Win32.Registry.AccessControl": "5.0.0",
- "Newtonsoft.Json": "12.0.3",
+ "Newtonsoft.Json": "13.0.1",
"System.Configuration.ConfigurationManager": "5.0.0",
"System.DirectoryServices": "5.0.0",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Management": "5.0.0",
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
"System.Security.AccessControl": "5.0.0",
- "System.Security.Cryptography.Pkcs": "5.0.0",
+ "System.Security.Cryptography.Pkcs": "5.0.1",
"System.Security.Permissions": "5.0.0",
"System.Text.Encoding.CodePages": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/System.Management.Automation.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1600,14 +1604,14 @@
},
"System.Private.Uri/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.win7.System.Private.Uri": "4.3.0"
}
},
"System.Reflection/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
@@ -1659,7 +1663,7 @@
},
"System.Reflection.Extensions/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0",
@@ -1676,7 +1680,7 @@
},
"System.Reflection.Primitives/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection.Primitives": "4.3.0"
@@ -1696,7 +1700,7 @@
},
"System.Resources.ResourceManager/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
@@ -1706,7 +1710,7 @@
},
"System.Runtime/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.any.System.Runtime": "4.3.0"
}
@@ -1721,7 +1725,7 @@
},
"System.Runtime.Extensions/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.win.System.Runtime.Extensions": "4.3.0"
@@ -1729,7 +1733,7 @@
},
"System.Runtime.Handles/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Runtime.Handles": "4.3.0"
@@ -1737,7 +1741,7 @@
},
"System.Runtime.InteropServices/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
@@ -1748,7 +1752,7 @@
},
"System.Security.AccessControl/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"System.Security.Principal.Windows": "5.0.0"
},
"runtime": {
@@ -1769,15 +1773,15 @@
}
}
},
- "System.Security.Cryptography.Pkcs/5.0.0": {
+ "System.Security.Cryptography.Pkcs/5.0.1": {
"dependencies": {
"System.Formats.Asn1": "5.0.0",
"System.Security.Cryptography.Cng": "5.0.0"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Security.Cryptography.Pkcs.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "5.0.120.57516"
}
}
},
@@ -1811,7 +1815,7 @@
},
"System.ServiceProcess.ServiceController/5.0.0": {
"dependencies": {
- "System.Diagnostics.EventLog": "5.0.0"
+ "System.Diagnostics.EventLog": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netstandard2.0/System.ServiceProcess.ServiceController.dll": {
@@ -1822,7 +1826,7 @@
},
"System.Text.Encoding/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Text.Encoding": "4.3.0"
@@ -1830,7 +1834,7 @@
},
"System.Text.Encoding.CodePages/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0"
+ "Microsoft.NETCore.Platforms": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": {
@@ -1865,7 +1869,7 @@
},
"System.Threading.Tasks/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Threading.Tasks": "4.3.0"
@@ -1874,7 +1878,7 @@
"System.Threading.Tasks.Extensions/4.5.3": {},
"System.Windows.Extensions/5.0.0": {
"dependencies": {
- "System.Drawing.Common": "5.0.0"
+ "System.Drawing.Common": "5.0.2"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Windows.Extensions.dll": {
@@ -1959,12 +1963,12 @@
"path": "microsoft.management.infrastructure.runtime.win/2.0.0",
"hashPath": "microsoft.management.infrastructure.runtime.win.2.0.0.nupkg.sha512"
},
- "Microsoft.NETCore.Platforms/5.0.0": {
+ "Microsoft.NETCore.Platforms/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
- "path": "microsoft.netcore.platforms/5.0.0",
- "hashPath": "microsoft.netcore.platforms.5.0.0.nupkg.sha512"
+ "sha512": "sha512-z3YFkbnl1RMj6lb+Bm/2tsZ0cJCULlB4uPOFqlj6dNSm/8feJl4UrXmG6TcZh8ipJQwkAS5I6UCs1FnGog4QZg==",
+ "path": "microsoft.netcore.platforms/5.0.1",
+ "hashPath": "microsoft.netcore.platforms.5.0.1.nupkg.sha512"
},
"Microsoft.NETCore.Targets/1.1.0": {
"type": "package",
@@ -1973,47 +1977,47 @@
"path": "microsoft.netcore.targets/1.1.0",
"hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Diagnostics/7.1.0": {
+ "Microsoft.PowerShell.Commands.Diagnostics/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-Hipqx38QOv1xWXbV4yGWHOmFIV8zp0O15z2H+Vx0Z0cOnzu4eb+MUblb4Dc1FA3S7ZE56qplC3XBK3hs0EsvYA==",
- "path": "microsoft.powershell.commands.diagnostics/7.1.0",
- "hashPath": "microsoft.powershell.commands.diagnostics.7.1.0.nupkg.sha512"
+ "sha512": "sha512-LjYU4Fn/CrA/8hBpadisKjnycJgGkS7GlP3PgnFCgVhMq5rsTABgm0akA9rucH8NQnFN9LjSk9dPrkL0wpP6Eg==",
+ "path": "microsoft.powershell.commands.diagnostics/7.1.3",
+ "hashPath": "microsoft.powershell.commands.diagnostics.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Management/7.1.0": {
+ "Microsoft.PowerShell.Commands.Management/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-3S5CU0Zq8ecdywaI3NoKyzjyaLaS7OKAi6z2AfiyKDG5IZfoLTBgIzNQElNFag8UALTNRlKGD8cumVtM3myAQA==",
- "path": "microsoft.powershell.commands.management/7.1.0",
- "hashPath": "microsoft.powershell.commands.management.7.1.0.nupkg.sha512"
+ "sha512": "sha512-0z2I3xpfLTFb5lygdytPido1TEhpvurDFQSDRnYl0v1zIEbStFIPeJajT0WhqPhSIzXmI4uj/+MeToD/79qaHQ==",
+ "path": "microsoft.powershell.commands.management/7.1.3",
+ "hashPath": "microsoft.powershell.commands.management.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Utility/7.1.0": {
+ "Microsoft.PowerShell.Commands.Utility/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-6BXJ7NRCs8xCHqspv1VoijXKLfLrM6O8rJwyTk0mPc5BZvpC2NSQq1NAJHAlk60ENFTu0vtxK2IzWkbFvJ/BUQ==",
- "path": "microsoft.powershell.commands.utility/7.1.0",
- "hashPath": "microsoft.powershell.commands.utility.7.1.0.nupkg.sha512"
+ "sha512": "sha512-AsQTmJdC13QN0JyqH0cy2vqi2VM9kGKWkfzGpyktYaY4lnWJbnzCg/0dOD1SJlmB+41DSyjTg03RBgTPOsoOXA==",
+ "path": "microsoft.powershell.commands.utility/7.1.3",
+ "hashPath": "microsoft.powershell.commands.utility.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.ConsoleHost/7.1.0": {
+ "Microsoft.PowerShell.ConsoleHost/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-vfs4i5cViK5YsiIveuFv7OTrN2m5vPfOXj3XBkrQHtlMb2u0mkIsqVMTDHRMAyopcpp0ZJ1H+4ZsZ9z1bcpagQ==",
- "path": "microsoft.powershell.consolehost/7.1.0",
- "hashPath": "microsoft.powershell.consolehost.7.1.0.nupkg.sha512"
+ "sha512": "sha512-u/iNvSQ/cGYzxj1TJQCtcSM78H8pbpPiYs30I1HrJxvtj0tqKtBagzWvMoeCFYlA6p4ZgztPHSKr4xiHh/Wrxw==",
+ "path": "microsoft.powershell.consolehost/7.1.3",
+ "hashPath": "microsoft.powershell.consolehost.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.CoreCLR.Eventing/7.1.0": {
+ "Microsoft.PowerShell.CoreCLR.Eventing/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-TCTaoyysnTHUErm0w8W50CVB5Mzwj1PQyb740K1DLvbw8Ba607Q1HCSzBHClEDlG+Cphuv9ie2pbvEWcUKslVg==",
- "path": "microsoft.powershell.coreclr.eventing/7.1.0",
- "hashPath": "microsoft.powershell.coreclr.eventing.7.1.0.nupkg.sha512"
+ "sha512": "sha512-gsshJYIbjJjlTDGlDntENxMkRd+KWZo0CXF1jLJrpqtF51nmP5AHMKysFXRTPBWTNFmFTZ0tqgazkzF5S92/VA==",
+ "path": "microsoft.powershell.coreclr.eventing/7.1.3",
+ "hashPath": "microsoft.powershell.coreclr.eventing.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.MarkdownRender/7.1.0": {
+ "Microsoft.PowerShell.MarkdownRender/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-FD8ARPyKg22yRhU3gUk6NKZtmtSk5uekqufjJCiTu8A5lvWF9A0mCr/OUYE7+HQLnJoBu8BR19vwnED0Jpd1WQ==",
- "path": "microsoft.powershell.markdownrender/7.1.0",
- "hashPath": "microsoft.powershell.markdownrender.7.1.0.nupkg.sha512"
+ "sha512": "sha512-86h8paEYo7BQ017G47cKL1twAxdhOD9sJRu6NSEuwbDgjVd3t5fahgLz7YDxwugcxDiA1TLCgH1cwUjChvoOrw==",
+ "path": "microsoft.powershell.markdownrender/7.1.3",
+ "hashPath": "microsoft.powershell.markdownrender.7.1.3.nupkg.sha512"
},
"Microsoft.PowerShell.Native/7.1.0": {
"type": "package",
@@ -2022,12 +2026,12 @@
"path": "microsoft.powershell.native/7.1.0",
"hashPath": "microsoft.powershell.native.7.1.0.nupkg.sha512"
},
- "Microsoft.PowerShell.Security/7.1.0": {
+ "Microsoft.PowerShell.Security/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-I4ab6z1Vlu25c33jc0gG0Val6YpSgyOyxNsNOENcBGue9TDJQ+d6ppw1IKcCILefpVAo/UWiE4xrWxO04KUc3g==",
- "path": "microsoft.powershell.security/7.1.0",
- "hashPath": "microsoft.powershell.security.7.1.0.nupkg.sha512"
+ "sha512": "sha512-hmXwtDMiF13vUTF1wk/FArwy/nOM5YNtNoOEY5Vrtf0TOiYyjx/PCsObMEPCb5jLgpUO2AC4b5gtVNmyEw/5LQ==",
+ "path": "microsoft.powershell.security/7.1.3",
+ "hashPath": "microsoft.powershell.security.7.1.3.nupkg.sha512"
},
"Microsoft.Win32.Registry/5.0.0": {
"type": "package",
@@ -2050,19 +2054,19 @@
"path": "microsoft.win32.systemevents/5.0.0",
"hashPath": "microsoft.win32.systemevents.5.0.0.nupkg.sha512"
},
- "Microsoft.WSMan.Management/7.1.0": {
+ "Microsoft.WSMan.Management/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-noQxoEaJW5W4vbRfwIB1l+ecERA+K4ZygZBv1YNu0IwdDos0mIiPY+YpFKz2uHyeNSk1Z3j1fsXV0b/r+eRT1Q==",
- "path": "microsoft.wsman.management/7.1.0",
- "hashPath": "microsoft.wsman.management.7.1.0.nupkg.sha512"
+ "sha512": "sha512-pSo+31uFqBNS2hXzb5X8MUXOy5BgzMdW0sadBeHA0kaGqSItVFniO/YI5nSboPzyFUPeJhENXnch/uQ3kBKsdg==",
+ "path": "microsoft.wsman.management/7.1.3",
+ "hashPath": "microsoft.wsman.management.7.1.3.nupkg.sha512"
},
- "Microsoft.WSMan.Runtime/7.1.0": {
+ "Microsoft.WSMan.Runtime/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-DVtFU3QQ12w5ej86Gw/vNtJjCXPd7r9fLJs9tzYqptEO6WtJX+vH65qPacP0DN/LEuc+93+iRVfNjjf/EMuNBQ==",
- "path": "microsoft.wsman.runtime/7.1.0",
- "hashPath": "microsoft.wsman.runtime.7.1.0.nupkg.sha512"
+ "sha512": "sha512-whAtJ6Gl/KoclB3NALkk1MptpP6vaVlA8zSuQo4AXBrMuSLUZZV9cbsc3f8Xb2c/5W/C+Of5fSusBGsaGt6wgQ==",
+ "path": "microsoft.wsman.runtime/7.1.3",
+ "hashPath": "microsoft.wsman.runtime.7.1.3.nupkg.sha512"
},
"Namotion.Reflection/1.0.14": {
"type": "package",
@@ -2071,12 +2075,12 @@
"path": "namotion.reflection/1.0.14",
"hashPath": "namotion.reflection.1.0.14.nupkg.sha512"
},
- "Newtonsoft.Json/12.0.3": {
+ "Newtonsoft.Json/13.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg==",
- "path": "newtonsoft.json/12.0.3",
- "hashPath": "newtonsoft.json.12.0.3.nupkg.sha512"
+ "sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
+ "path": "newtonsoft.json/13.0.1",
+ "hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
},
"NJsonSchema/10.2.2": {
"type": "package",
@@ -2232,12 +2236,12 @@
"path": "system.diagnostics.diagnosticsource/4.6.0",
"hashPath": "system.diagnostics.diagnosticsource.4.6.0.nupkg.sha512"
},
- "System.Diagnostics.EventLog/5.0.0": {
+ "System.Diagnostics.EventLog/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-FHkCwUfsTs+/5tsK+c0egLfacUgbhvcwi3wUFWSEEArSXao343mYqcpOVVFMlcCkdNtjU4YwAWaKYwal6f02og==",
- "path": "system.diagnostics.eventlog/5.0.0",
- "hashPath": "system.diagnostics.eventlog.5.0.0.nupkg.sha512"
+ "sha512": "sha512-k4O5RrjnhJZrP4EgOklUVkcmVdAxs9+PoXCGmlNS3NPIwaSyMMLy7pUaamMHCFkduiOO/ZUzIRjyoCnvXLJpfw==",
+ "path": "system.diagnostics.eventlog/5.0.1",
+ "hashPath": "system.diagnostics.eventlog.5.0.1.nupkg.sha512"
},
"System.DirectoryServices/5.0.0": {
"type": "package",
@@ -2246,12 +2250,12 @@
"path": "system.directoryservices/5.0.0",
"hashPath": "system.directoryservices.5.0.0.nupkg.sha512"
},
- "System.Drawing.Common/5.0.0": {
+ "System.Drawing.Common/5.0.2": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==",
- "path": "system.drawing.common/5.0.0",
- "hashPath": "system.drawing.common.5.0.0.nupkg.sha512"
+ "sha512": "sha512-rvr/M1WPf24ljpvvrVd74+NdjRUJu1bBkspkZcnzSZnmAUQWSvanlQ0k/hVHk+cHufZbZfu7vOh/vYc0q5Uu/A==",
+ "path": "system.drawing.common/5.0.2",
+ "hashPath": "system.drawing.common.5.0.2.nupkg.sha512"
},
"System.Dynamic.Runtime/4.3.0": {
"type": "package",
@@ -2309,12 +2313,12 @@
"path": "system.management/5.0.0",
"hashPath": "system.management.5.0.0.nupkg.sha512"
},
- "System.Management.Automation/7.1.0": {
+ "System.Management.Automation/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-WjmMs8kNETGWUmbE2A87cTcAGRz0WJ24TOZm5xkhop/93fIINK8TEcghEr2I0mSgXb7t0pWkPdKdEA5vizFQPA==",
- "path": "system.management.automation/7.1.0",
- "hashPath": "system.management.automation.7.1.0.nupkg.sha512"
+ "sha512": "sha512-rXPo+ujbsrraKk7F0VYQLM5oTXUo8bOmd+lnALCNPXaeZZ7pcGLqAfayuGX/Y6sXd4zoOE0jDtF572u4vCR5fg==",
+ "path": "system.management.automation/7.1.3",
+ "hashPath": "system.management.automation.7.1.3.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
@@ -2449,12 +2453,12 @@
"path": "system.security.cryptography.cng/5.0.0",
"hashPath": "system.security.cryptography.cng.5.0.0.nupkg.sha512"
},
- "System.Security.Cryptography.Pkcs/5.0.0": {
+ "System.Security.Cryptography.Pkcs/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-9TPLGjBCGKmNvG8pjwPeuYy0SMVmGZRwlTZvyPHDbYv/DRkoeumJdfumaaDNQzVGMEmbWtg07zUpSW9q70IlDQ==",
- "path": "system.security.cryptography.pkcs/5.0.0",
- "hashPath": "system.security.cryptography.pkcs.5.0.0.nupkg.sha512"
+ "sha512": "sha512-9ualfJXOMrjW/E4z73cGHVcAvFMCCnMZQE+8xME9eX70bTZ0UAJCstG0khsMvL8B+9c9qw+ktowt1fN0BffMnQ==",
+ "path": "system.security.cryptography.pkcs/5.0.1",
+ "hashPath": "system.security.cryptography.pkcs.5.0.1.nupkg.sha512"
},
"System.Security.Cryptography.ProtectedData/5.0.0": {
"type": "package",
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
index 9ada03f00f..7a6f670da3 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
index 7d8d5afcd3..0311e38ecb 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
index b63f889771..308409049b 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
index 96fa29deea..df08fade3c 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
index 1dc32290ac..c3d1468fa2 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
index 6af4347c1d..404cb95e8c 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
index fd06cf702f..420fb2a1f3 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
index d33a2ffd25..9bea3d5df0 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
index 5644be0dfd..20ac15fc0a 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
index b501fb6a29..1ffeabe658 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll
new file mode 100644
index 0000000000..c5150acc62
--- /dev/null
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
index bb76446a5e..5796c86256 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
index 87fe0ae8bb..b80b430426 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
index aa3f0fccb5..2707bd1936 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
index 252b80c246..fb97d63592 100644
--- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
+++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/Chef.PowerShell.dll b/distro/ruby_bin_folder/x86/Chef.PowerShell.dll
index 8a528ad226..558ac94224 100644
--- a/distro/ruby_bin_folder/x86/Chef.PowerShell.dll
+++ b/distro/ruby_bin_folder/x86/Chef.PowerShell.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll b/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll
index 203d243b37..0153bcec03 100644
--- a/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll
+++ b/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/Newtonsoft.Json.dll b/distro/ruby_bin_folder/x86/Newtonsoft.Json.dll
index 1971a35679..7af125a246 100644
--- a/distro/ruby_bin_folder/x86/Newtonsoft.Json.dll
+++ b/distro/ruby_bin_folder/x86/Newtonsoft.Json.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
index 879df9ab6e..9a85c84155 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
index ff50126c80..3945bf7ab4 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
index 5c979f1750..d98811d840 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
index 08ea0aaff0..b0760558a8 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.NETCore.App.deps.json
@@ -9,14 +9,14 @@
".NETCoreApp,Version=v5.0/win10-x86": {
"Chef.Powershell.Core/1.0.0": {
"dependencies": {
- "Microsoft.PowerShell.Commands.Diagnostics": "7.1.0",
- "Microsoft.PowerShell.Commands.Management": "7.1.0",
- "Microsoft.PowerShell.Commands.Utility": "7.1.0",
- "Microsoft.PowerShell.ConsoleHost": "7.1.0",
- "Microsoft.WSMan.Management": "7.1.0",
- "Microsoft.WSMan.Runtime": "7.1.0",
- "Newtonsoft.Json": "12.0.3",
- "System.Management.Automation": "7.1.0",
+ "Microsoft.PowerShell.Commands.Diagnostics": "7.1.3",
+ "Microsoft.PowerShell.Commands.Management": "7.1.3",
+ "Microsoft.PowerShell.Commands.Utility": "7.1.3",
+ "Microsoft.PowerShell.ConsoleHost": "7.1.3",
+ "Microsoft.WSMan.Management": "7.1.3",
+ "Microsoft.WSMan.Runtime": "7.1.3",
+ "Newtonsoft.Json": "13.0.1",
+ "System.Management.Automation": "7.1.3",
"runtimepack.Microsoft.NETCore.App.Runtime.win-x86": "5.0.0"
},
"runtime": {
@@ -1040,78 +1040,78 @@
}
}
},
- "Microsoft.NETCore.Platforms/5.0.0": {},
+ "Microsoft.NETCore.Platforms/5.0.1": {},
"Microsoft.NETCore.Targets/1.1.0": {},
- "Microsoft.PowerShell.Commands.Diagnostics/7.1.0": {
+ "Microsoft.PowerShell.Commands.Diagnostics/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Diagnostics.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.Commands.Management/7.1.0": {
+ "Microsoft.PowerShell.Commands.Management/7.1.3": {
"dependencies": {
- "Microsoft.PowerShell.Security": "7.1.0",
+ "Microsoft.PowerShell.Security": "7.1.3",
"System.ServiceProcess.ServiceController": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Management.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.Commands.Utility/7.1.0": {
+ "Microsoft.PowerShell.Commands.Utility/7.1.3": {
"dependencies": {
"Microsoft.CodeAnalysis.CSharp": "3.7.0",
- "Microsoft.PowerShell.MarkdownRender": "7.1.0",
+ "Microsoft.PowerShell.MarkdownRender": "7.1.3",
"NJsonSchema": "10.2.2",
- "System.Drawing.Common": "5.0.0",
- "System.Management.Automation": "7.1.0",
+ "System.Drawing.Common": "5.0.2",
+ "System.Management.Automation": "7.1.3",
"System.Threading.AccessControl": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Commands.Utility.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.ConsoleHost/7.1.0": {
+ "Microsoft.PowerShell.ConsoleHost/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.ConsoleHost.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.CoreCLR.Eventing/7.1.0": {
+ "Microsoft.PowerShell.CoreCLR.Eventing/7.1.3": {
"dependencies": {
- "System.Diagnostics.EventLog": "5.0.0"
+ "System.Diagnostics.EventLog": "5.0.1"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.CoreCLR.Eventing.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.PowerShell.MarkdownRender/7.1.0": {
+ "Microsoft.PowerShell.MarkdownRender/7.1.3": {
"dependencies": {
"Markdig.Signed": "0.21.1",
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.MarkdownRender.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1125,14 +1125,14 @@
}
}
},
- "Microsoft.PowerShell.Security/7.1.0": {
+ "Microsoft.PowerShell.Security/7.1.3": {
"dependencies": {
- "System.Management.Automation": "7.1.0"
+ "System.Management.Automation": "7.1.3"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.PowerShell.Security.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1162,7 +1162,7 @@
},
"Microsoft.Win32.SystemEvents/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0"
+ "Microsoft.NETCore.Platforms": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll": {
@@ -1171,24 +1171,24 @@
}
}
},
- "Microsoft.WSMan.Management/7.1.0": {
+ "Microsoft.WSMan.Management/7.1.3": {
"dependencies": {
- "Microsoft.WSMan.Runtime": "7.1.0",
- "System.Management.Automation": "7.1.0",
+ "Microsoft.WSMan.Runtime": "7.1.3",
+ "System.Management.Automation": "7.1.3",
"System.ServiceProcess.ServiceController": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.WSMan.Management.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
- "Microsoft.WSMan.Runtime/7.1.0": {
+ "Microsoft.WSMan.Runtime/7.1.3": {
"runtime": {
"runtimes/win/lib/net5.0/Microsoft.WSMan.Runtime.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1203,18 +1203,18 @@
}
}
},
- "Newtonsoft.Json/12.0.3": {
+ "Newtonsoft.Json/13.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
- "assemblyVersion": "12.0.0.0",
- "fileVersion": "12.0.3.23909"
+ "assemblyVersion": "13.0.0.0",
+ "fileVersion": "13.0.1.25517"
}
}
},
"NJsonSchema/10.2.2": {
"dependencies": {
"Namotion.Reflection": "1.0.14",
- "Newtonsoft.Json": "12.0.3"
+ "Newtonsoft.Json": "13.0.1"
},
"runtime": {
"lib/netstandard2.0/NJsonSchema.dll": {
@@ -1362,7 +1362,7 @@
},
"System.Collections/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Collections": "4.3.0"
@@ -1390,7 +1390,7 @@
},
"System.Diagnostics.Debug/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.win.System.Diagnostics.Debug": "4.3.0"
@@ -1404,22 +1404,26 @@
}
}
},
- "System.Diagnostics.EventLog/5.0.0": {
+ "System.Diagnostics.EventLog/5.0.1": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.Win32.Registry": "5.0.0",
"System.Security.Principal.Windows": "5.0.0"
},
"runtime": {
+ "runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.Messages.dll": {
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "0.0.0.0"
+ },
"runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "5.0.321.7212"
}
}
},
"System.DirectoryServices/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Security.AccessControl": "5.0.0",
"System.Security.Permissions": "5.0.0",
@@ -1432,14 +1436,14 @@
}
}
},
- "System.Drawing.Common/5.0.0": {
+ "System.Drawing.Common/5.0.2": {
"dependencies": {
"Microsoft.Win32.SystemEvents": "5.0.0"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.2",
+ "fileVersion": "5.0.421.11614"
}
}
},
@@ -1477,7 +1481,7 @@
},
"System.Globalization/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Globalization": "4.3.0"
@@ -1485,7 +1489,7 @@
},
"System.IO/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
@@ -1549,7 +1553,7 @@
},
"System.Management/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.Win32.Registry": "5.0.0",
"System.CodeDom": "5.0.0"
},
@@ -1560,28 +1564,28 @@
}
}
},
- "System.Management.Automation/7.1.0": {
+ "System.Management.Automation/7.1.3": {
"dependencies": {
"Microsoft.ApplicationInsights": "2.15.0",
"Microsoft.Management.Infrastructure": "2.0.0",
- "Microsoft.PowerShell.CoreCLR.Eventing": "7.1.0",
+ "Microsoft.PowerShell.CoreCLR.Eventing": "7.1.3",
"Microsoft.PowerShell.Native": "7.1.0",
"Microsoft.Win32.Registry.AccessControl": "5.0.0",
- "Newtonsoft.Json": "12.0.3",
+ "Newtonsoft.Json": "13.0.1",
"System.Configuration.ConfigurationManager": "5.0.0",
"System.DirectoryServices": "5.0.0",
"System.IO.FileSystem.AccessControl": "5.0.0",
"System.Management": "5.0.0",
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
"System.Security.AccessControl": "5.0.0",
- "System.Security.Cryptography.Pkcs": "5.0.0",
+ "System.Security.Cryptography.Pkcs": "5.0.1",
"System.Security.Permissions": "5.0.0",
"System.Text.Encoding.CodePages": "5.0.0"
},
"runtime": {
"runtimes/win/lib/net5.0/System.Management.Automation.dll": {
- "assemblyVersion": "7.1.0.0",
- "fileVersion": "7.1.0.0"
+ "assemblyVersion": "7.1.3.0",
+ "fileVersion": "7.1.3.0"
}
}
},
@@ -1603,14 +1607,14 @@
},
"System.Private.Uri/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.win7.System.Private.Uri": "4.3.0"
}
},
"System.Reflection/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
@@ -1662,7 +1666,7 @@
},
"System.Reflection.Extensions/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0",
@@ -1679,7 +1683,7 @@
},
"System.Reflection.Primitives/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection.Primitives": "4.3.0"
@@ -1699,7 +1703,7 @@
},
"System.Resources.ResourceManager/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
@@ -1709,7 +1713,7 @@
},
"System.Runtime/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.any.System.Runtime": "4.3.0"
}
@@ -1724,7 +1728,7 @@
},
"System.Runtime.Extensions/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.win.System.Runtime.Extensions": "4.3.0"
@@ -1732,7 +1736,7 @@
},
"System.Runtime.Handles/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Runtime.Handles": "4.3.0"
@@ -1740,7 +1744,7 @@
},
"System.Runtime.InteropServices/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
@@ -1751,7 +1755,7 @@
},
"System.Security.AccessControl/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"System.Security.Principal.Windows": "5.0.0"
},
"runtime": {
@@ -1772,15 +1776,15 @@
}
}
},
- "System.Security.Cryptography.Pkcs/5.0.0": {
+ "System.Security.Cryptography.Pkcs/5.0.1": {
"dependencies": {
"System.Formats.Asn1": "5.0.0",
"System.Security.Cryptography.Cng": "5.0.0"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Security.Cryptography.Pkcs.dll": {
- "assemblyVersion": "5.0.0.0",
- "fileVersion": "5.0.20.51904"
+ "assemblyVersion": "5.0.0.1",
+ "fileVersion": "5.0.120.57516"
}
}
},
@@ -1814,7 +1818,7 @@
},
"System.ServiceProcess.ServiceController/5.0.0": {
"dependencies": {
- "System.Diagnostics.EventLog": "5.0.0"
+ "System.Diagnostics.EventLog": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netstandard2.0/System.ServiceProcess.ServiceController.dll": {
@@ -1825,7 +1829,7 @@
},
"System.Text.Encoding/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Text.Encoding": "4.3.0"
@@ -1833,7 +1837,7 @@
},
"System.Text.Encoding.CodePages/5.0.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0"
+ "Microsoft.NETCore.Platforms": "5.0.1"
},
"runtime": {
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": {
@@ -1868,7 +1872,7 @@
},
"System.Threading.Tasks/4.3.0": {
"dependencies": {
- "Microsoft.NETCore.Platforms": "5.0.0",
+ "Microsoft.NETCore.Platforms": "5.0.1",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Threading.Tasks": "4.3.0"
@@ -1877,7 +1881,7 @@
"System.Threading.Tasks.Extensions/4.5.3": {},
"System.Windows.Extensions/5.0.0": {
"dependencies": {
- "System.Drawing.Common": "5.0.0"
+ "System.Drawing.Common": "5.0.2"
},
"runtime": {
"runtimes/win/lib/netcoreapp3.0/System.Windows.Extensions.dll": {
@@ -1962,12 +1966,12 @@
"path": "microsoft.management.infrastructure.runtime.win/2.0.0",
"hashPath": "microsoft.management.infrastructure.runtime.win.2.0.0.nupkg.sha512"
},
- "Microsoft.NETCore.Platforms/5.0.0": {
+ "Microsoft.NETCore.Platforms/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
- "path": "microsoft.netcore.platforms/5.0.0",
- "hashPath": "microsoft.netcore.platforms.5.0.0.nupkg.sha512"
+ "sha512": "sha512-z3YFkbnl1RMj6lb+Bm/2tsZ0cJCULlB4uPOFqlj6dNSm/8feJl4UrXmG6TcZh8ipJQwkAS5I6UCs1FnGog4QZg==",
+ "path": "microsoft.netcore.platforms/5.0.1",
+ "hashPath": "microsoft.netcore.platforms.5.0.1.nupkg.sha512"
},
"Microsoft.NETCore.Targets/1.1.0": {
"type": "package",
@@ -1976,47 +1980,47 @@
"path": "microsoft.netcore.targets/1.1.0",
"hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Diagnostics/7.1.0": {
+ "Microsoft.PowerShell.Commands.Diagnostics/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-Hipqx38QOv1xWXbV4yGWHOmFIV8zp0O15z2H+Vx0Z0cOnzu4eb+MUblb4Dc1FA3S7ZE56qplC3XBK3hs0EsvYA==",
- "path": "microsoft.powershell.commands.diagnostics/7.1.0",
- "hashPath": "microsoft.powershell.commands.diagnostics.7.1.0.nupkg.sha512"
+ "sha512": "sha512-LjYU4Fn/CrA/8hBpadisKjnycJgGkS7GlP3PgnFCgVhMq5rsTABgm0akA9rucH8NQnFN9LjSk9dPrkL0wpP6Eg==",
+ "path": "microsoft.powershell.commands.diagnostics/7.1.3",
+ "hashPath": "microsoft.powershell.commands.diagnostics.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Management/7.1.0": {
+ "Microsoft.PowerShell.Commands.Management/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-3S5CU0Zq8ecdywaI3NoKyzjyaLaS7OKAi6z2AfiyKDG5IZfoLTBgIzNQElNFag8UALTNRlKGD8cumVtM3myAQA==",
- "path": "microsoft.powershell.commands.management/7.1.0",
- "hashPath": "microsoft.powershell.commands.management.7.1.0.nupkg.sha512"
+ "sha512": "sha512-0z2I3xpfLTFb5lygdytPido1TEhpvurDFQSDRnYl0v1zIEbStFIPeJajT0WhqPhSIzXmI4uj/+MeToD/79qaHQ==",
+ "path": "microsoft.powershell.commands.management/7.1.3",
+ "hashPath": "microsoft.powershell.commands.management.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.Commands.Utility/7.1.0": {
+ "Microsoft.PowerShell.Commands.Utility/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-6BXJ7NRCs8xCHqspv1VoijXKLfLrM6O8rJwyTk0mPc5BZvpC2NSQq1NAJHAlk60ENFTu0vtxK2IzWkbFvJ/BUQ==",
- "path": "microsoft.powershell.commands.utility/7.1.0",
- "hashPath": "microsoft.powershell.commands.utility.7.1.0.nupkg.sha512"
+ "sha512": "sha512-AsQTmJdC13QN0JyqH0cy2vqi2VM9kGKWkfzGpyktYaY4lnWJbnzCg/0dOD1SJlmB+41DSyjTg03RBgTPOsoOXA==",
+ "path": "microsoft.powershell.commands.utility/7.1.3",
+ "hashPath": "microsoft.powershell.commands.utility.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.ConsoleHost/7.1.0": {
+ "Microsoft.PowerShell.ConsoleHost/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-vfs4i5cViK5YsiIveuFv7OTrN2m5vPfOXj3XBkrQHtlMb2u0mkIsqVMTDHRMAyopcpp0ZJ1H+4ZsZ9z1bcpagQ==",
- "path": "microsoft.powershell.consolehost/7.1.0",
- "hashPath": "microsoft.powershell.consolehost.7.1.0.nupkg.sha512"
+ "sha512": "sha512-u/iNvSQ/cGYzxj1TJQCtcSM78H8pbpPiYs30I1HrJxvtj0tqKtBagzWvMoeCFYlA6p4ZgztPHSKr4xiHh/Wrxw==",
+ "path": "microsoft.powershell.consolehost/7.1.3",
+ "hashPath": "microsoft.powershell.consolehost.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.CoreCLR.Eventing/7.1.0": {
+ "Microsoft.PowerShell.CoreCLR.Eventing/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-TCTaoyysnTHUErm0w8W50CVB5Mzwj1PQyb740K1DLvbw8Ba607Q1HCSzBHClEDlG+Cphuv9ie2pbvEWcUKslVg==",
- "path": "microsoft.powershell.coreclr.eventing/7.1.0",
- "hashPath": "microsoft.powershell.coreclr.eventing.7.1.0.nupkg.sha512"
+ "sha512": "sha512-gsshJYIbjJjlTDGlDntENxMkRd+KWZo0CXF1jLJrpqtF51nmP5AHMKysFXRTPBWTNFmFTZ0tqgazkzF5S92/VA==",
+ "path": "microsoft.powershell.coreclr.eventing/7.1.3",
+ "hashPath": "microsoft.powershell.coreclr.eventing.7.1.3.nupkg.sha512"
},
- "Microsoft.PowerShell.MarkdownRender/7.1.0": {
+ "Microsoft.PowerShell.MarkdownRender/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-FD8ARPyKg22yRhU3gUk6NKZtmtSk5uekqufjJCiTu8A5lvWF9A0mCr/OUYE7+HQLnJoBu8BR19vwnED0Jpd1WQ==",
- "path": "microsoft.powershell.markdownrender/7.1.0",
- "hashPath": "microsoft.powershell.markdownrender.7.1.0.nupkg.sha512"
+ "sha512": "sha512-86h8paEYo7BQ017G47cKL1twAxdhOD9sJRu6NSEuwbDgjVd3t5fahgLz7YDxwugcxDiA1TLCgH1cwUjChvoOrw==",
+ "path": "microsoft.powershell.markdownrender/7.1.3",
+ "hashPath": "microsoft.powershell.markdownrender.7.1.3.nupkg.sha512"
},
"Microsoft.PowerShell.Native/7.1.0": {
"type": "package",
@@ -2025,12 +2029,12 @@
"path": "microsoft.powershell.native/7.1.0",
"hashPath": "microsoft.powershell.native.7.1.0.nupkg.sha512"
},
- "Microsoft.PowerShell.Security/7.1.0": {
+ "Microsoft.PowerShell.Security/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-I4ab6z1Vlu25c33jc0gG0Val6YpSgyOyxNsNOENcBGue9TDJQ+d6ppw1IKcCILefpVAo/UWiE4xrWxO04KUc3g==",
- "path": "microsoft.powershell.security/7.1.0",
- "hashPath": "microsoft.powershell.security.7.1.0.nupkg.sha512"
+ "sha512": "sha512-hmXwtDMiF13vUTF1wk/FArwy/nOM5YNtNoOEY5Vrtf0TOiYyjx/PCsObMEPCb5jLgpUO2AC4b5gtVNmyEw/5LQ==",
+ "path": "microsoft.powershell.security/7.1.3",
+ "hashPath": "microsoft.powershell.security.7.1.3.nupkg.sha512"
},
"Microsoft.Win32.Registry/5.0.0": {
"type": "package",
@@ -2053,19 +2057,19 @@
"path": "microsoft.win32.systemevents/5.0.0",
"hashPath": "microsoft.win32.systemevents.5.0.0.nupkg.sha512"
},
- "Microsoft.WSMan.Management/7.1.0": {
+ "Microsoft.WSMan.Management/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-noQxoEaJW5W4vbRfwIB1l+ecERA+K4ZygZBv1YNu0IwdDos0mIiPY+YpFKz2uHyeNSk1Z3j1fsXV0b/r+eRT1Q==",
- "path": "microsoft.wsman.management/7.1.0",
- "hashPath": "microsoft.wsman.management.7.1.0.nupkg.sha512"
+ "sha512": "sha512-pSo+31uFqBNS2hXzb5X8MUXOy5BgzMdW0sadBeHA0kaGqSItVFniO/YI5nSboPzyFUPeJhENXnch/uQ3kBKsdg==",
+ "path": "microsoft.wsman.management/7.1.3",
+ "hashPath": "microsoft.wsman.management.7.1.3.nupkg.sha512"
},
- "Microsoft.WSMan.Runtime/7.1.0": {
+ "Microsoft.WSMan.Runtime/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-DVtFU3QQ12w5ej86Gw/vNtJjCXPd7r9fLJs9tzYqptEO6WtJX+vH65qPacP0DN/LEuc+93+iRVfNjjf/EMuNBQ==",
- "path": "microsoft.wsman.runtime/7.1.0",
- "hashPath": "microsoft.wsman.runtime.7.1.0.nupkg.sha512"
+ "sha512": "sha512-whAtJ6Gl/KoclB3NALkk1MptpP6vaVlA8zSuQo4AXBrMuSLUZZV9cbsc3f8Xb2c/5W/C+Of5fSusBGsaGt6wgQ==",
+ "path": "microsoft.wsman.runtime/7.1.3",
+ "hashPath": "microsoft.wsman.runtime.7.1.3.nupkg.sha512"
},
"Namotion.Reflection/1.0.14": {
"type": "package",
@@ -2074,12 +2078,12 @@
"path": "namotion.reflection/1.0.14",
"hashPath": "namotion.reflection.1.0.14.nupkg.sha512"
},
- "Newtonsoft.Json/12.0.3": {
+ "Newtonsoft.Json/13.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg==",
- "path": "newtonsoft.json/12.0.3",
- "hashPath": "newtonsoft.json.12.0.3.nupkg.sha512"
+ "sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
+ "path": "newtonsoft.json/13.0.1",
+ "hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
},
"NJsonSchema/10.2.2": {
"type": "package",
@@ -2235,12 +2239,12 @@
"path": "system.diagnostics.diagnosticsource/4.6.0",
"hashPath": "system.diagnostics.diagnosticsource.4.6.0.nupkg.sha512"
},
- "System.Diagnostics.EventLog/5.0.0": {
+ "System.Diagnostics.EventLog/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-FHkCwUfsTs+/5tsK+c0egLfacUgbhvcwi3wUFWSEEArSXao343mYqcpOVVFMlcCkdNtjU4YwAWaKYwal6f02og==",
- "path": "system.diagnostics.eventlog/5.0.0",
- "hashPath": "system.diagnostics.eventlog.5.0.0.nupkg.sha512"
+ "sha512": "sha512-k4O5RrjnhJZrP4EgOklUVkcmVdAxs9+PoXCGmlNS3NPIwaSyMMLy7pUaamMHCFkduiOO/ZUzIRjyoCnvXLJpfw==",
+ "path": "system.diagnostics.eventlog/5.0.1",
+ "hashPath": "system.diagnostics.eventlog.5.0.1.nupkg.sha512"
},
"System.DirectoryServices/5.0.0": {
"type": "package",
@@ -2249,12 +2253,12 @@
"path": "system.directoryservices/5.0.0",
"hashPath": "system.directoryservices.5.0.0.nupkg.sha512"
},
- "System.Drawing.Common/5.0.0": {
+ "System.Drawing.Common/5.0.2": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==",
- "path": "system.drawing.common/5.0.0",
- "hashPath": "system.drawing.common.5.0.0.nupkg.sha512"
+ "sha512": "sha512-rvr/M1WPf24ljpvvrVd74+NdjRUJu1bBkspkZcnzSZnmAUQWSvanlQ0k/hVHk+cHufZbZfu7vOh/vYc0q5Uu/A==",
+ "path": "system.drawing.common/5.0.2",
+ "hashPath": "system.drawing.common.5.0.2.nupkg.sha512"
},
"System.Dynamic.Runtime/4.3.0": {
"type": "package",
@@ -2312,12 +2316,12 @@
"path": "system.management/5.0.0",
"hashPath": "system.management.5.0.0.nupkg.sha512"
},
- "System.Management.Automation/7.1.0": {
+ "System.Management.Automation/7.1.3": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-WjmMs8kNETGWUmbE2A87cTcAGRz0WJ24TOZm5xkhop/93fIINK8TEcghEr2I0mSgXb7t0pWkPdKdEA5vizFQPA==",
- "path": "system.management.automation/7.1.0",
- "hashPath": "system.management.automation.7.1.0.nupkg.sha512"
+ "sha512": "sha512-rXPo+ujbsrraKk7F0VYQLM5oTXUo8bOmd+lnALCNPXaeZZ7pcGLqAfayuGX/Y6sXd4zoOE0jDtF572u4vCR5fg==",
+ "path": "system.management.automation/7.1.3",
+ "hashPath": "system.management.automation.7.1.3.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
@@ -2452,12 +2456,12 @@
"path": "system.security.cryptography.cng/5.0.0",
"hashPath": "system.security.cryptography.cng.5.0.0.nupkg.sha512"
},
- "System.Security.Cryptography.Pkcs/5.0.0": {
+ "System.Security.Cryptography.Pkcs/5.0.1": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-9TPLGjBCGKmNvG8pjwPeuYy0SMVmGZRwlTZvyPHDbYv/DRkoeumJdfumaaDNQzVGMEmbWtg07zUpSW9q70IlDQ==",
- "path": "system.security.cryptography.pkcs/5.0.0",
- "hashPath": "system.security.cryptography.pkcs.5.0.0.nupkg.sha512"
+ "sha512": "sha512-9ualfJXOMrjW/E4z73cGHVcAvFMCCnMZQE+8xME9eX70bTZ0UAJCstG0khsMvL8B+9c9qw+ktowt1fN0BffMnQ==",
+ "path": "system.security.cryptography.pkcs/5.0.1",
+ "hashPath": "system.security.cryptography.pkcs.5.0.1.nupkg.sha512"
},
"System.Security.Cryptography.ProtectedData/5.0.0": {
"type": "package",
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
index 9ada03f00f..7a6f670da3 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Diagnostics.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
index 7d8d5afcd3..0311e38ecb 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Management.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
index b63f889771..308409049b 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Commands.Utility.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
index 96fa29deea..df08fade3c 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.ConsoleHost.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
index 1dc32290ac..c3d1468fa2 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.CoreCLR.Eventing.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
index 6af4347c1d..404cb95e8c 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.MarkdownRender.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
index fd06cf702f..420fb2a1f3 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.PowerShell.Security.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
index d33a2ffd25..9bea3d5df0 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Management.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
index 5644be0dfd..20ac15fc0a 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Microsoft.WSMan.Runtime.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
index b501fb6a29..1ffeabe658 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Newtonsoft.Json.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll
new file mode 100644
index 0000000000..c5150acc62
--- /dev/null
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.Messages.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
index bb76446a5e..5796c86256 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Diagnostics.EventLog.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
index 87fe0ae8bb..b80b430426 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Drawing.Common.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
index aa3f0fccb5..2707bd1936 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Management.Automation.dll
Binary files differ
diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
index 252b80c246..fb97d63592 100644
--- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
+++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/System.Security.Cryptography.Pkcs.dll
Binary files differ
diff --git a/dobi.yaml b/dobi.yaml
index b038f8c411..0c85cd7946 100644
--- a/dobi.yaml
+++ b/dobi.yaml
@@ -18,4 +18,5 @@ image=chef:
CHANNEL: unstable
annotations:
tags:
- - expeditor:final-channel-tags={{major}},{{major}}.{{minor}} \ No newline at end of file
+ - expeditor:default-tags={{channel}}
+ - expeditor:final-channel-tags=latest,{{major}},{{major}}.{{minor}} \ No newline at end of file
diff --git a/docs/dev/design_documents/action_collection.md b/docs/dev/design_documents/action_collection.md
index df7dd46a84..367f160fcc 100644
--- a/docs/dev/design_documents/action_collection.md
+++ b/docs/dev/design_documents/action_collection.md
@@ -77,6 +77,39 @@ As the Action Collection API was initially designed around the Resource Reporter
and could easily lift some of the searching features on the name of the resource from the resource collection, and could use a more fluent API
for composing searches.
+# Simple Example
+
+A simple example which can be put into any cookbook's libraries directory and will dump out a list of all the updated (or failed) resources at the end
+of the run is the following:
+
+```
+Chef.run_context.action_collection.register(self)
+
+Chef.event_handler do
+ on :run_completed do
+ MyModule.dump_resources
+ end
+ on :run_failed do
+ MyModule.dump_resources
+ end
+end
+
+class MyModule
+ def self.dump_resources
+ Chef.run_context.action_collection.filtered_collection(up_to_date: false, skipped: false, unprocessed: false).each do |action_record|
+ puts action_record.new_resource
+ end
+ end
+end
+```
+
+For more complicated examples see the `Chef::ResourceReporter` and `Chef::DataCollector` in the source code.
+
+Note that any cookbook library event handlers obviously cannot handle failures that happen earlier in the chef-client run than cookbook library loading
+time. For more truly robust reporting it is best to use the `Chef::DataCollector` directly. A custom implementation would look like copying the defensive
+coding in the data collector into a class which was loaded very early, perhaps at `Chef::Config` loading time (catching errors caused by `Chef::Config` loading
+though is fairly impossible the way the core of the chef-client works though so it is better to keep that simple).
+
# Implementation Details
## Resource Event Lifecycle Hooks
diff --git a/docs/dev/devtools/README.md b/docs/dev/devtools/README.md
new file mode 100644
index 0000000000..160bc4d829
--- /dev/null
+++ b/docs/dev/devtools/README.md
@@ -0,0 +1,5 @@
+# Here there be dragons
+
+Here is where we document tools that may be useful to Chef developers or
+power users. These tools have no guarantees and are not supported. You're
+welcome to use them, but you are on your own.
diff --git a/docs/dev/devtools/chef-apply.md b/docs/dev/devtools/chef-apply.md
new file mode 100644
index 0000000000..85305cf50d
--- /dev/null
+++ b/docs/dev/devtools/chef-apply.md
@@ -0,0 +1,129 @@
++++
+title = "chef-apply (executable)"
+draft = false
+
+gh_repo = "chef-workstation"
+
+aliases = ["/ctl_chef_apply.html", "/ctl_chef_apply/"]
+
+[menu]
+ [menu.workstation]
+ title = "chef-apply (executable)"
+ identifier = "chef_workstation/chef_workstation_tools/ctl_chef_apply.md chef-apply (executable)"
+ parent = "chef_workstation/chef_workstation_tools"
+ weight = 30
++++
+
+chef-apply is an executable program that runs a single recipe from the
+command line:
+
+- Is part of Chef Workstation
+- A great way to explore resources
+- Is **NOT** how Chef is run in production
+
+## Options
+
+This command has the following syntax:
+
+``` bash
+chef-apply name_of_recipe.rb
+```
+
+This tool has the following options:
+
+`-e RECIPE_TEXT`, `--execute RECIPE_TEXT`
+
+: Execute a resource using a string.
+
+`-l LEVEL`, `--log_level LEVEL`
+
+: The level of logging to be stored in a log file.
+
+`-s`, `--stdin`
+
+: Execute a resource using standard input.
+
+`-v`, `--version`
+
+: The Chef Infra Client version.
+
+`-W`, `--why-run`
+
+: Run the executable in why-run mode, which is a type of Chef Infra Client run that does everything except modify the system. Use why-run mode to understand why Chef Infra Client makes the decisions that it makes and to learn more about the current and proposed state of the system.
+
+`-h`, `--help`
+
+: Show help for the command.
+
+## Examples
+
+**Run a recipe**
+
+Run a recipe named `machinations.rb`:
+
+``` bash
+chef-apply machinations.rb
+```
+
+**Install Emacs**
+
+Run:
+
+``` bash
+sudo chef-apply -e "package 'emacs'"
+```
+
+Returns:
+
+``` bash
+Recipe: (chef-apply cookbook)::(chef-apply recipe)
+ * package[emacs] action install
+ - install version 23.1-25.el6 of package emacs
+```
+
+**Install nano**
+
+Run:
+
+``` bash
+sudo chef-apply -e "package 'nano'"
+```
+
+Returns:
+
+``` bash
+Recipe: (chef-apply cookbook)::(chef-apply recipe)
+ * package[nano] action install
+ - install version 2.0.9-7.el6 of package nano
+```
+
+**Install vim**
+
+Run:
+
+``` bash
+sudo chef-apply -e "package 'vim'"
+```
+
+Returns:
+
+``` bash
+Recipe: (chef-apply cookbook)::(chef-apply recipe)
+ * package[vim] action install
+ - install version 7.2.411-1.8.el6 of package vim-enhanced
+```
+
+**Rerun a recipe**
+
+Run:
+
+``` bash
+sudo chef-apply -e "package 'vim'"
+```
+
+Returns:
+
+``` bash
+Recipe: (chef-apply cookbook)::(chef-apply recipe)
+ * package[vim] action install (up to date)
+```
diff --git a/docs/dev/how_to/adding_new_platforms_to_infra.md b/docs/dev/how_to/adding_new_platforms_to_infra.md
new file mode 100644
index 0000000000..99a2544710
--- /dev/null
+++ b/docs/dev/how_to/adding_new_platforms_to_infra.md
@@ -0,0 +1,57 @@
+# Adding New Platforms to Chef Infra
+
+Adding a new platform to Chef Infra involves not just adding the platform to the `chef/chef` repo in GitHub, but also ensuring that the platform is supported in all of our ecosystem tooling. This document breaks down all the places we need to update to bring in a new platform.
+
+## Adding the Build to Chef/Chef
+
+### Pull Request Testing
+
+When we add a new platform, if possible, we want to ensure that we test that platform on all Pull Requests to the `chef/chef` GitHub repository. There are a few places to update to accomplish this.
+
+#### RubyDistros
+
+We run RSpec tests against all Pull Requests to `chef/chef` using containers built from our [RubyDistros project](https://github.com/chef/rubydistros). These containers are built on various Linux distribution releases and target the last few Ruby releases. This allows us to run RSpec tests on common Linux platforms like CentOS 8 with Ruby releases such as 2.7 or 3.0.
+
+For major new Linux or Windows distribution releases, you'll want to add these to the RubyDistros repository. You can test these builds locally in Docker and then set the builds to run on DockerHub. If you're not a member of the [RubyDistros DockerHub org](https://hub.docker.com/orgs/rubydistros) then ask #releng-support to add you to that. From there you can add a new repository matching the existing repository setups. Make sure you configure the automated builds to point to the correct Dockerfile path with each Ruby version as a tag. See https://hub.docker.com/repository/docker/rubydistros/opensuse-15 for an example of how we set this up.
+
+Note: Windows builds can't be built on DockerHub and have to be pushed manually from your workstation instead.
+
+Once you've added the distro to RubyDistros and the image is pushed to DockerHub, you can add that new distro to the `chef/chef` [verify.pipeline.yml](https://github.com/chef/chef/blob/master/.expeditor/verify.pipeline.yml).
+
+#### Kitchen Dokken Images
+
+We utilize the kitchen-dokken Test Kitchen plugin to test the contents of `chef/chef` against various Linux distributions. This works best when run against containers that look more like VMs and less like slim containers. The [dokken-images](https://github.com/test-kitchen/dokken-images) repository defines many Docker images for common Linux distributions. These are all based on the official distro Docker images but contain additional packages to make them behave more like full systems.
+
+Similar to the RubyDistros setup, these are defined in GitHub and built-in DockerHub. You'll need to be a member of the [dokken DockerHub organization](https://hub.docker.com/orgs/dokken). If you don't have access to that please ask #releng-support to add you. Once you've added a distro to the GitHub repo you can add a new repository to that DockerHub Organization. Make sure to copy the automated builds settings and specify the correct path to the Dockerfile. Once that is complete and the image is pushed to DockerHub you can add the new test to `chef/chef`. You'll need to edit the [kitchen-test/kitchen.yml](https://github.com/chef/chef/blob/master/kitchen-tests/kitchen.yml) file and then add that new platform to the [verify.pipeline.yml](https://github.com/chef/chef/blob/master/.expeditor/verify.pipeline.yml) config.
+
+#### Azure Pipelines tests
+
+COMING SOON!
+
+### Omnibus Pipeline
+
+With the new platform tested in Pull Requests, you'll now want to ensure that we build and test these packages in our Buildkite Omnibus pipeline.
+
+HOW TO DO THIS COMING SOON!
+
+### Code Changes
+
+With builds in place, we also want to make sure we support this new platform within the Chef Infra Client itself. Most of this is not something that can be documented here. You'll just need to understand where in Chef Infra Client we need to support a new distro. The two most common places to add support are `Ohai` and `chef-utils`:
+
+#### chef-utils
+
+chef-utils provides a large number of helpers for making cookbook authoring simpler. One of the most useful sets of helpers is helpers for platforms and platform families, which will need to be updated if we added new distros. Make sure new platform are supported in `platform.rb` / `platform_family.rb` files in [chef-utils/lib/chef-utils/dsl](https://github.com/chef/chef/tree/master/chef-utils/lib/chef-utils/dsl). In order to test these changes you'll most likely need to update the Fauxhai data. See the section below for instructions on updating that data.
+
+#### Ohai
+
+Ohai powers all system configuration detection for Chef Infra Client. New distributions have pretty far-reaching impacts on Ohai and should be evaluated carefully. The most basic task when adding a new distro is to make sure that the platform is properly mapped to the appropriate platform_family value. On Linux systems this is performed in the [Linux Platform Plugin](https://github.com/chef/ohai/blob/master/lib/ohai/plugins/linux/platform.rb).
+
+## Additional Ecosystem Updates
+
+### Bento Boxes
+
+Bento boxes are used by the kitchen-vagrant driver by customers for cookbook testing. When we add a new platform we need to make sure that customers can use it in their testing processes. The [bento repository](https://github.com/chef/bento/) contains packer templates for each platform / platform version, a [builds.yml](https://github.com/chef/bento/blob/master/builds.yml) file which defines the systems to build, and a ruby application which helps with building systems. Bento boxes are generally build for VirtualBox, VMware Fusion, Parallels Desktop, and Hyper-V so you will need both a macOS and Windows system to build all the systems. Once this is complete you can use the `bento upload` command to push these to Vagrant Cloud. You'll need to be added to the Bento Vagrant Cloud org, which #releng-support can assist you with.
+
+### Fauxhai Dumps
+
+Fauxhai is used by ChefSpec for unit testing of cookbooks and we also use it directly in `chef/chef` to test the helpers in the `chef-utils` gem. When we add support for new platforms we need to make sure we update the Fauxhai dumps as well. The [fauxhai repository](https://github.com/chefspec/fauxhai/) contains the actual dump files, and the [fauxhai_generator](https://github.com/chefspec/fauxhai_generator) repository contains a `kitchen.yml` file with a custom provisioner for gathering dump data. These spins up hosts in AWS and gathers their dumps. Keep in mind this only works on Linux hosts and sometimes it requires tracking down custom AMIs. You can see how messy this gets by looking at the values in the `kitchen.yml`. Running this driver using `bundle exec kitchen test FOO` will dump a file locally. You'll then need to use a json sorting tool to sort this JSON. Once it's sorted stick it in the appropriate directory in the Fauxhai project, run `rake documentation:update_platforms` to generate the `PLATFORMS.md` file and commit the change.
diff --git a/docs/dev/how_to/bumping_the_major_version.md b/docs/dev/how_to/bumping_the_major_version.md
index ff93f5bcf6..7247b3d470 100644
--- a/docs/dev/how_to/bumping_the_major_version.md
+++ b/docs/dev/how_to/bumping_the_major_version.md
@@ -8,20 +8,29 @@ Chef consumes Ohai from GitHub as both a runtime dependency and a testing depend
### Create a new stable branch of Ohai
-On your local machine fork the current master branch to a new stable branch. For example: `git checkout -b 15-stable`. You'll then want to edit the Expeditor config.yml file to update the branch configs like this:
+1. Edit the Expeditor config for the new branch, which you'll create shortly:
-https://github.com/chef/ohai/commit/ad208165619425dd7886b2de3f168b49ded25146
+ - Example config change commit: https://github.com/chef/ohai/commit/1ad8c5946606a7f08ffb841e3682ae2d4991077f
-With the expeditor config complete push the branch `git push --set-upstream origin 15-stable`
+2. On your local machine fork the current master branch to a new stable branch. For example: `git checkout -b 16-stable`.
+
+3. Push the branch `git push --set-upstream origin 16-stable`
### Bump Ohai master to the new major version
-Create a PR which:
+Starting from the master branch create a PR which:
- Edits the `VERSION` file in the root of the repository to the new major release
-- Updates the `chef-config` dependency to allow for the new major release of Chef Infra in `ohai.gemspec`
+- Updates the `chef-config` and `chef-utils` dependencies to allow for the new major release of Chef Infra in `ohai.gemspec`
+- Update the `chef-config` and `chef-utils` deps in the Gemfile to point the the yet to be created chef-XYZ stable branch in the `chef/chef` repo. Note: This is going to fail for now. We have to start somewhere.
+
+## Update chef/chef
+
+### Prep master branch for forking
-## Fork Chef master to a stable branch
+- In ./expeditor/config.yml add the version_constraint for the new branch, update the version_constraint for master to match the new planned major version and add a constraint for the new stable version / branch
+
+### Fork Chef master to a stable branch
Before bumping the major version of Chef Infra we want to fork off the current master to a new stable branch, which will be used to build hotfix releases. We support the N-1 version of Chef Infra Client for a year after the release of a new major version. For example Chef Infra Client 16 was released in April 2020, at which point Chef Infra Client 15 became the N-1 release. Chef Infra Client 15 will then be maintained with critical bug and security fixes until April 2021.
@@ -31,7 +40,7 @@ On your local machine fork the current master branch to a new stable branch. For
Once you've forked to a new stable branch such as `chef-15` you'll want to create a new branch so you can build a PR, which will get this branch ready for release:
-- In ./expeditor/config.yml add the version_constraint for the new branch, update the version_constraint for master to match the new major version, and remove all the update_dep.sh subscriptions which don't work against stable branches.
+- In ./expeditor/config.yml remove all the update_dep.sh subscriptions which don't work against stable branches.
- In readme.md update the buildkite badge to point to the new stable branch image and link instead of pointing to master.
- In kitchen-tests/Gemfile update the Ohai branch to point to the new Ohai stable
- In kitchen-tests/kitchen.yml update chef_version to be your new stable version and not current. Ex: 15
@@ -41,11 +50,12 @@ Once you've forked to a new stable branch such as `chef-15` you'll want to creat
Example PR for Chef 15: https://github.com/chef/chef/pull/9236
+Note: Make sure you're making this PR against the new stable branch and not master!
+
## Bump master for the new major release
Create a PR that performs the following:
- Update the version in the VERSION file
- Update chef.gemspec to point to the new ohai major release
-- Update .expeditor/config.yml to include the new version_constraints you setup on your stable branch and an updated project alias
- run `rake dependencies:update`
diff --git a/docs/dev/how_to/releasing_chef_infra.md b/docs/dev/how_to/releasing_chef_infra.md
index 68070c420b..b0db85432e 100644
--- a/docs/dev/how_to/releasing_chef_infra.md
+++ b/docs/dev/how_to/releasing_chef_infra.md
@@ -16,10 +16,10 @@ The importance of our release notes cannot be understated. As developers, we und
#### Overall Release Notes Structure
-1. `New Resources`: If we ship new resources, we want to make sure to brag about those resources. Use this section to give the elevator pitch for the new resource, including an example of how it might be used if available.
-2. `Updated Resources`: It's important to let users know about new functionality in resources they may already be using. Cover any important bug fixes or new properties/actions here.
-3. `Major new features`: Document new features with a high level bullet. This is a great opportunity to show off our work and sell users on new workflows.
-4. `Updated InSpec Releases`: We should always call out the updated Chef InSpec release and include a description of new functionality.
+1. `Major new features`: Document new features with a high level bullet. This is a great opportunity to show off our work and sell users on new workflows.
+2. `Updated InSpec Releases`: We should always call out the updated Chef InSpec release and include a description of new functionality.
+3. `New Resources`: If we ship new resources, we want to make sure to brag about those resources. Use this section to give the elevator pitch for the new resource, including an example of how it might be used if available.
+4. `Updated Resources`: It's important to let users know about new functionality in resources they may already be using. Cover any important bug fixes or new properties/actions here.
5. `Security Updates`: Call out any updated components we are shipping and include links to the CVEs if available.
### Update the Docs Site
@@ -29,7 +29,7 @@ If there are any new or updated resources, the docs site will need to be updated
#### Resource Documentation Automation
1. Run `rake docs_site:resources` to generate content to a `docs_site` directory
-2. Compare the relevant generated files with the docs site content in the `chef_master/source` directory. The generated files are missing some content, such as action descriptions, and don't have perfect formatting, so this is a bit of an art form.
+2. Compare the relevant generated files to the content in the `content/resources` directory within the [chef-web-docs repo](https://github.com/chef/chef-web-docs/). The generated files are missing some content, such as action descriptions, and don't have perfect formatting, so this is a bit of an art form.
## Release Chef Infra Client
@@ -37,11 +37,11 @@ If there are any new or updated resources, the docs site will need to be updated
Chef employees can promote a build to stable from Slack. This is done with expeditor using a chatops command in the following format:
-`/expeditor promote chef/chef:master 15.1.9`
+`/expeditor promote chef/chef:master 17.1.9`
or for a previous release branch:
-`/expeditor promote chef/chef:chef-14 14.13.9`
+`/expeditor promote chef/chef:chef-16 16.13.9`
### Announce the Build
diff --git a/docs/dev/how_to/running_adhoc_builds.md b/docs/dev/how_to/running_adhoc_builds.md
new file mode 100644
index 0000000000..ec05994502
--- /dev/null
+++ b/docs/dev/how_to/running_adhoc_builds.md
@@ -0,0 +1,45 @@
+# Running Buildkite Ad Hoc Builds
+
+Buildkite ad hoc builds are a great way to ensure that complex changes or changes that impact package creation will build across all supported platforms.
+
+## What's wrong with just PR testing
+
+The testing of Chef Infra Client occurs at the PR level and the build level, each incrementally improving developer confidence in the safety of a change.
+
+Pull Request testing in GitHub is our first layer of defense but is far from perfect. It's optimized for speed and to work within the constraints of GitHub and public CI instances. This means this testing occurs against Ruby containers and no builds are created. Any change to build dependencies or configuration untested at the PR level.
+
+Build testing occurs post-merge and involves the creation of packages for the platforms we support and the execution of RSpec tests using those packages. Since these RSpec tests are performed within the packages, we test against the dependencies we ship to the customer.
+
+When making changes that impact the package you always want to validate the build process by running an ad hoc Buildkite build. Ad hoc builds take the same path as merged changes but without the need to merge code.
+
+## Starting the Build
+
+Ad hoc Buildkite builds can be created against any branch in the `chef/chef` GitHub repository by Chef employees with access to the private Buildkite account. If you don't have access to this account or the ability to push branches to `chef/chef` ask in `#releng-support`.
+
+Buildkite pipelines for running ad hoc builds are automatically created for each branch configured in the Expeditor `config.yml` file. They follow a standard naming system:
+
+### Master
+
+https://buildkite.com/chef/chef-chef-master-omnibus-adhoc/
+
+### Chef 16
+
+https://buildkite.com/chef/chef-chef-chef-16-omnibus-adhoc/
+
+Once in the ad hoc pipeline page, select "New Build" in the upper right hand corner. Then provide a message to display in the Buildkite UI, the commit, and branch name.
+
+## Downloading the Build
+
+Once your build completes you can download it from either Artifactory or via mixlib-install.
+
+### Artifactory
+
+Builds are available in Artifactory at http://artifactory.chef.co/ui/builds/chef
+
+### mixlib-install
+
+Successful ad hoc builds are promoted to the unstable channel and can be downloaded using mixlib-install (part of Chef Workstation)
+
+`mixlib-install download chef -c unstable`
+
+Keep in mind this just downloads the last build so you may end up the wrong build if the ad hoc isn't the last build to promote to unstable.
diff --git a/docs/dev/how_to/updating_dependencies.md b/docs/dev/how_to/updating_dependencies.md
index 82bfc346bb..638d409e4c 100644
--- a/docs/dev/how_to/updating_dependencies.md
+++ b/docs/dev/how_to/updating_dependencies.md
@@ -4,7 +4,7 @@ If you want to change our constraints (change which packages and versions we acc
* [Gemfile](../../../Gemfile) and [Gemfile.lock](../../../Gemfile.lock): All gem version constraints (update with `bundle update`)
* [omnibus_overrides.rb](../../../omnibus_overrides.rb): Pinned versions of omnibus packages.
-* [omnibus/Gemfile](../../../omnibus/Gemfile) and [omnibus/Gemfile.lock](../../../omnibus/Gemfile.lock): Gems for the omnibus build system itself.
+* [omnibus/Gemfile](../../../omnibus/Gemfile) and [omnibus/Gemfile.lock](../../../omnibus/Gemfile.lock): Gems for the omnibus build system itself.
In order to update everything, run `rake dependencies`. Note that the [Gemfile.lock](Gemfile.lock) pins Windows platform gems, and to fully regenerate the lockfile, you must use the following commands, or run `rake dependencies:update_gemfile_lock`:
diff --git a/docs/dev/policy/release_and_support_schedule.md b/docs/dev/policy/release_and_support_schedule.md
index 2f2c378448..c3d8ea531b 100644
--- a/docs/dev/policy/release_and_support_schedule.md
+++ b/docs/dev/policy/release_and_support_schedule.md
@@ -50,6 +50,7 @@ This stage indicates that a release version is no longer in active development a
### End of Life (EOL)
This stage indicates a previously deprecated release version, which is no longer supported
+
- No additional releases will be made
- Documentation will be archived
- Cookbooks and other community tooling may no longer function using this version of Chef Infra
diff --git a/habitat/plan.ps1 b/habitat/plan.ps1
index 8269e436a5..fac1ada42f 100644
--- a/habitat/plan.ps1
+++ b/habitat/plan.ps1
@@ -12,7 +12,7 @@ $pkg_bin_dirs=@(
)
$pkg_deps=@(
"core/cacerts"
- "chef/ruby27-plus-devkit"
+ "chef/ruby30-plus-devkit"
"chef/chef-powershell-shim"
)
diff --git a/habitat/plan.sh b/habitat/plan.sh
index 64f424dfac..ec0b0f6a09 100644
--- a/habitat/plan.sh
+++ b/habitat/plan.sh
@@ -1,4 +1,4 @@
-_chef_client_ruby="core/ruby27"
+_chef_client_ruby="core/ruby30"
pkg_name="chef-infra-client"
pkg_origin="chef"
pkg_maintainer="The Chef Maintainers <humans@chef.io>"
@@ -94,14 +94,10 @@ do_build() {
( cd "$CACHE_PATH" || exit_with "unable to enter hab-cache directory" 1
build_line "Installing gem dependencies ..."
bundle install --jobs=3 --retry=3
+ build_line "Installing gems from git repos properly ..."
+ ruby ./post-bundle-install.rb
build_line "Installing this project's gems ..."
bundle exec rake install
- for gem in $GEM_HOME/bundler/gems/*; do
- ( cd $gem
- build_line "Installing gems from git repos properly ..."
- rake install
- )
- done
)
}
diff --git a/habitat/tests/spec.ps1 b/habitat/tests/spec.ps1
index 34b3a07beb..0754d31727 100644
--- a/habitat/tests/spec.ps1
+++ b/habitat/tests/spec.ps1
@@ -18,7 +18,7 @@ try {
SETX GEM_PATH $($gemPath.Split("=")[1]) /m
hab pkg binlink --force $PackageIdentifier
- /hab/bin/rspec --tag ~executables --tag ~choco_installed spec/functional
+ /hab/bin/rspec --tag ~executables --tag ~choco_installed --pattern 'spec/functional/**/*_spec.rb' --exclude-pattern 'spec/functional/knife/**/*.rb'
if (-not $?) { throw "functional testing failed"}
} finally {
Pop-Location
diff --git a/habitat/tests/test.pester.ps1 b/habitat/tests/test.pester.ps1
index 56f31e9a2f..c8cf993e2f 100644
--- a/habitat/tests/test.pester.ps1
+++ b/habitat/tests/test.pester.ps1
@@ -52,13 +52,6 @@ Describe "chef-infra-client" {
}
}
- Context "knife" {
- It "is an executable" {
- hab pkg exec $PackageIdentifier knife.bat --version
- $? | Should be $true
- }
- }
-
Context "chef-solo" {
It "is an executable" {
hab pkg exec $PackageIdentifier chef-solo.bat --version
diff --git a/habitat/tests/test.sh b/habitat/tests/test.sh
index c28ab8b2cf..655e8db2d8 100755
--- a/habitat/tests/test.sh
+++ b/habitat/tests/test.sh
@@ -28,10 +28,10 @@ echo "--- :mag_right: Testing ${pkg_ident} executables"
actual_version=$(hab pkg exec "${pkg_ident}" chef-client -- --version | sed 's/.*: //')
[[ "$package_version" = "$actual_version" ]] || error "chef-client is not the expected version. Expected '$package_version', got '$actual_version'"
-for executable in 'chef-client' 'ohai' 'chef-shell' 'chef-apply' 'knife' 'chef-solo'; do
+for executable in 'chef-client' 'ohai' 'chef-shell' 'chef-apply' 'chef-solo'; do
echo -en "\t$executable = "
hab pkg exec "${pkg_ident}" "${executable}" -- --version || error "${executable} failed to execute properly"
done
echo "--- :mag_right: Testing ${pkg_ident} functionality"
-hab pkg exec "${pkg_ident}" rspec --tag ~executables spec/functional || error 'failures during rspec tests'
+hab pkg exec "${pkg_ident}" rspec --tag ~executables --pattern 'spec/functional/**/*_spec.rb' --exclude-pattern 'spec/functional/knife/**/*.rb' || error 'failures during rspec tests'
diff --git a/kitchen-tests/Gemfile b/kitchen-tests/Gemfile
index bc100ffb62..19f6e7d93e 100644
--- a/kitchen-tests/Gemfile
+++ b/kitchen-tests/Gemfile
@@ -2,10 +2,10 @@ source "https://rubygems.org"
gem "rake" # required to build some native extensions
gem "chef", path: ".."
+gem "knife", path: "../knife"
gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master" # avoids failures when we bump chef major
gem "berkshelf", git: "https://github.com/berkshelf/berkshelf.git", branch: "master"
-gem "kitchen-dokken", "~> 2.0"
+gem "kitchen-dokken", ">= 2.0"
gem "kitchen-inspec", git: "https://github.com/chef/kitchen-inspec.git", branch: "master"
gem "inspec"
gem "test-kitchen", git: "https://github.com/test-kitchen/test-kitchen.git", branch: "master"
-gem "kitchen-azurerm", git: "https://github.com/test-kitchen/kitchen-azurerm.git", branch: "master" \ No newline at end of file
diff --git a/kitchen-tests/cookbooks/end_to_end/attributes/default.rb b/kitchen-tests/cookbooks/end_to_end/attributes/default.rb
index e910b9e7d4..72430e9367 100644
--- a/kitchen-tests/cookbooks/end_to_end/attributes/default.rb
+++ b/kitchen-tests/cookbooks/end_to_end/attributes/default.rb
@@ -67,13 +67,6 @@ default["chef_client"]["config"]["verbose_logging"] = false
default["chef_client"]["chef_license"] = "accept-no-persist"
#
-# resolver cookbook overrides
-#
-
-default["resolver"]["nameservers"] = [ "8.8.8.8", "8.8.4.4" ]
-default["resolver"]["search"] = "chef.io"
-
-#
# nscd cookbook overrides
#
diff --git a/kitchen-tests/cookbooks/end_to_end/files/certs/ca.cert.pem b/kitchen-tests/cookbooks/end_to_end/files/certs/ca.cert.pem
new file mode 100644
index 0000000000..2bb30a7b18
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/certs/ca.cert.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGRTCCBC2gAwIBAgIUKR+fpIUUvQMvhGba1Ky/ScpiysUwDQYJKoZIhvcNAQEL
+BQAwgakxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRs
+ZTEZMBcGA1UECgwQTm9ydGh3aW5kIEJha2luZzEUMBIGA1UECwwLRW5naW5lZXJp
+bmcxITAfBgNVBAMMGE5vcnRod2luZCBCYWtpbmcgUm9vdCBDQTEnMCUGCSqGSIb3
+DQEJARYYam9obi5tY2NyYWVAcHJvZ3Jlc3MuY29tMB4XDTIwMTIxODE3MTQxNloX
+DTQwMTIxMzE3MTQxNlowgakxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4G
+A1UEBwwHU2VhdHRsZTEZMBcGA1UECgwQTm9ydGh3aW5kIEJha2luZzEUMBIGA1UE
+CwwLRW5naW5lZXJpbmcxITAfBgNVBAMMGE5vcnRod2luZCBCYWtpbmcgUm9vdCBD
+QTEnMCUGCSqGSIb3DQEJARYYam9obi5tY2NyYWVAcHJvZ3Jlc3MuY29tMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqQScpelNtPUrdX8K7kp8s0vLw9wB
+VEEQ0hV/hIFWjLYORmFLP4MISsL/ESu9rdGvZktCkURIivSUF9iAIaxaydUFGNLo
+lJY5LMb7Llezyy5C+W+xUUGlj9KIw6YBdXEFnaMrPDRJuPfoJREHn37U75KLlrM0
+SkYuAT5LDPXluJvB1iPpGixuaq8rmr+fSjhBZxnutJTFpnDcivzwPYmMsUrP6Hu7
+WB0Z8YrN0kv2Idw5LQikPoapnwgK4ygSiv0kIEKqsmLUG3V7NRyCE2SwMiBbRpyf
+Ehhy5JmxcIIJNAAFRkbPeHs3THC9mZJXJe5IGV91V0Jjixn9E3as+k7JwqEwViO0
+43hEg9dvrw5kshmjZxU6/9qB7WR8DsCHPZF3x6n5Z23BDYTXFcKqza17LtfbGpKL
+tPE/E5vYYogpXmNNEI55NcpTvexHrMpAasbqFysLSH0W9XKo7bmCKlaJbrMOPsyp
+WLD5jbjQm6ieNB2D992VnQkOm66Hd6FldoJoUhF4MIQ+2fDDfUDTsqG/dPO6UBZL
+vAnZAEQVkKq/1OWUizmx5WmC8b1Oyu3i+ghDFVuh3yKr/0RWdQdduGj98uMg6/jo
+46VYq5f9F9phE5A3NW1VBX5foOXyTp5xFbMJmN9MSyrXq/NCcdw9GYAGLlPjCVyc
+UpbazMzUyehfpzsCAwEAAaNjMGEwHQYDVR0OBBYEFLEKaqLFZyyT1nYDzMg/Vryr
++jVJMB8GA1UdIwQYMBaAFLEKaqLFZyyT1nYDzMg/Vryr+jVJMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSfXsBf5I3
+Lt5EDV73sC5hV3mcP7F+Betu0PeVCdDCKEIOve3hrV6cSpA8xoRY7kqWJ+bbR0HZ
+/fX8bCOt3Em5SZXGQMSirEoZ+pYPz+YOZ2RKPcf/wZIshpLzZ23Z0D06Cyxt+M2f
+fqRvEXXR5k4gPuikm+u/X16olBRI/ULW5IA3eri818JD840yjwxTu7a97rVKEhZR
+PSORvvgWgtA7HPfibgJ7DBjbY9B8YSiq0RxaJsmSmh/zZ2i0SMjztLcTvmWs0o8m
+3E42zVDXyy9A0fr9AoasyH17nHjKlAL6v6TfGvFDNgn5fIYELOrf+l1CD5Ij+coZ
+w4QiVKREiPA26CNC9kYWqBXhAKEr31DvgVSSlZTDF35QpE0DofYKRRTQ8P68h8hM
+vKqG7Wa3/9ZCeTK15CU9q8blZtcjF2dV1GKCs7WPCPct9DdQpkuSyuc9CQgiLhCR
+ZxgxpXX15AOa/RI8qRla4MBw3j3YP9Z5q6NsG239NdyckPUGqJUIs+oyaBRcxA0o
+QHG4JUWPBlOTxwOzfmMSZtCfcNuNOWK39s5pJiSyLPvaPCj3D79OKkskuO00lVAK
+Es5m/VexGB/XnM9vTLn72YESxUfl0+nP+vyAqKletXnwf8C6wt004TgM/YIoC2zR
+l4wX7Vl8hG7lYg3yEBoDQM3Ipq2V8S9G5A==
+-----END CERTIFICATE-----
diff --git a/kitchen-tests/cookbooks/end_to_end/files/certs/chef.northwindbaking.com.chained.cert.pem b/kitchen-tests/cookbooks/end_to_end/files/certs/chef.northwindbaking.com.chained.cert.pem
new file mode 100644
index 0000000000..846bbf9dcb
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/certs/chef.northwindbaking.com.chained.cert.pem
@@ -0,0 +1,73 @@
+-----BEGIN CERTIFICATE-----
+MIIGkDCCBHigAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwgZ8xCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJXQTEZMBcGA1UECgwQTm9ydGh3aW5kIEJha2luZzEUMBIGA1UE
+CwwLRW5naW5lZXJpbmcxKTAnBgNVBAMMIE5vcnRod2luZCBCYWtpbmcgSW50ZXJt
+ZWRpYXRlIENBMScwJQYJKoZIhvcNAQkBFhhqb2huLm1jY3JhZUBwcm9ncmVzcy5j
+b20wHhcNMjAxMjE4MjM0MTI2WhcNMjExMjI4MjM0MTI2WjCBqTELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMRkwFwYDVQQKDBBOb3J0
+aHdpbmQgQmFraW5nMRQwEgYDVQQLDAtFbmdpbmVlcmluZzEhMB8GA1UEAwwYY2hl
+Zi5ub3J0aHdpbmRiYWtpbmcuY29tMScwJQYJKoZIhvcNAQkBFhhqb2huLm1jY3Jh
+ZUBwcm9ncmVzcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq
+YMuo4lujFqZJq0awJ90RPVz28hmonIHi0lvHFj61q6yqYnTMg2ZzvEF4oJkkkPgJ
+C6TgLcYM+cJlqoqOdemLJzs0fiYXO5mlmFslHKAkoLAGDHFQcgXrFpsFW7QjDxcC
+XhgYHVD4usl+btMTKjvH8Vf1ElQZM1KEWIQRCuCXbz4tGow9tBsYV+HUJygHse5f
+SY5tITEUwmfrFUHjOTqBophBWFRd/hFcmV4IGgEKEYk/POl6AsOCxnr3QBPj8B0d
+ou/ETgCZ74c5yujfkm5GhN7iBawEGCl/38Xr6xxc7M+CkQMJajkpRjvXAtwWImwW
+MgFcazlpXYrxcp8izEJLAgMBAAGjggHIMIIBxDAJBgNVHRMEAjAAMBEGCWCGSAGG
++EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2Vy
+dmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBReJLWsKMwg+qRi6C3rlJJ/wFjIXDCB
+1wYDVR0jBIHPMIHMgBTnmVEOPRX1Padx4GQtoVyXKFUigKGBr6SBrDCBqTELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMRkwFwYDVQQK
+DBBOb3J0aHdpbmQgQmFraW5nMRQwEgYDVQQLDAtFbmdpbmVlcmluZzEhMB8GA1UE
+AwwYTm9ydGh3aW5kIEJha2luZyBSb290IENBMScwJQYJKoZIhvcNAQkBFhhqb2hu
+Lm1jY3JhZUBwcm9ncmVzcy5jb22CAhAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE
+DDAKBggrBgEFBQcDATBRBgNVHREESjBIghd3d3cubm9ydGh3aW5kYmFraW5nLmNv
+bYITbm9ydGh3aW5kYmFraW5nLmNvbYIYY2hlZi5ub3J0aHdpbmRiYWtpbmcuY29t
+MA0GCSqGSIb3DQEBCwUAA4ICAQBaCLsFRcwRqBafvBSaCfyaufigYE1tFZ9JSdZz
+KpJeRz9xNoF79Hh+WlQb8tmS8/1w2OI4oRxkd9Gm+IzShSGzmOwRoAvTNp4p4znM
+K79HOLxqQ5YZ4iYaSPzfHH7FNnzlA09Vm05Rj71W24wvJT7O0UO0BbCJDhFYpfQ7
+1DkVPG0ytIgz8jZI/mLLL9pwLO1vA7M36meL8XjnjXfQ1xt+N3NqaM5/t3ZLeMWH
+Hi96tVP7CaK4N+uCKkg0zGoeamvLUPVQm8wCrCM2k6rNYJnmvwzLp8NnCTjCQzst
+BnM35c/rnpljfFz9Qwjzkqvs5NLVZhu/YltWTpEIRSFScr3Wq4LYxF2TEgHu7QQ9
+HW4vY7vGByiWRNzD3A1ZAHPFg3sj+Fcx+XHI8F9gyUZ13wdZswmTGGAc1RfSgd/e
+X7g/rixCSuPiKeqBQB62JkYH2nGcdNXHvJorMbw+aUI+Vg8i/tIgOxzHgxeY5uW8
+s7PSkLBX6kM3Oi4UaglDzv7FiezPG4uAKH/aYIEdv8bQMvjlrzfcjsYuJQfHTbu8
+cRpZpyG1lLp8fISe3RbxBX+1YplYZlTmeg0KRm88/ifg+Ru7z2mUMeEoDw5cpxKV
+lJy3OLXr+EBa+nJyg/AextAmlJBwDg65Fi6rQUd14FvVK9jHkV/eO9fT8WuQaBBS
+mbiXvA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGLDCCBBSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgakxCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEZMBcGA1UECgwQTm9ydGh3
+aW5kIEJha2luZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxITAfBgNVBAMMGE5vcnRo
+d2luZCBCYWtpbmcgUm9vdCBDQTEnMCUGCSqGSIb3DQEJARYYam9obi5tY2NyYWVA
+cHJvZ3Jlc3MuY29tMB4XDTIwMTIxODE3MjczNloXDTMwMTIxNjE3MjczNlowgZ8x
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEZMBcGA1UECgwQTm9ydGh3aW5kIEJh
+a2luZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxKTAnBgNVBAMMIE5vcnRod2luZCBC
+YWtpbmcgSW50ZXJtZWRpYXRlIENBMScwJQYJKoZIhvcNAQkBFhhqb2huLm1jY3Jh
+ZUBwcm9ncmVzcy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDd
+pjPv+HSfwiVhdCrA3xpMB3UM68GuFXKr9EPzTKexZVW+NPnFO7wSzxNZ2x9+IjRa
+7xPhMdBm3nBSXLpg87NCzS5Z+bQBHPi8M2RlOXyDXYz/Nqh2UIAaodmlGEa7Fwkr
+JgG9RBfuPZumtTWU1aTS4ldg3IyO/XpiNB+6p/XdgJOtYWKvfPO6KFjWkxouCSN3
+antHE9ZpB4L78rO+DK+n+UkXUDMLUFejxy2MCj1zmMqk9Qlt0RIobE/uvC05FNdE
+9xmhQ4rROteY68miy938hIKLlRs/A+7Q8wUTgrTuPxQVi62+WHxdl/zewa9s7zlk
+brQoIXhv6kQQ6ZiHRbjFvAsXcBD6tt0YKeILPMpRAPhgZJFjiS8qWz/28Nn3l2vt
+6nQgVmauw/dJ3wyiNWQ/WuX8HpwzXZ76IiTBzpzaYA4qX1k/PfJQxRPCJBqjmWfa
+bUb1PN6KM5/XYEBY2/VMFk/DdBo+PP8aDAOC37lmyyXYRRwyTyQxNaNUNdNXTWSv
+mrqq8q4IvW4qPpSn4tf7bjiTAa/xNBIPTS+axXojOQ8wBJ4r0rnjLZaLkd5015p3
+WM0bFqvHRtNUgpKd7WAbz9ZZXbzJS/ofxtAHu1JcDYJZ0UlfFBJLPB8Px2dcvWu9
+8gqnTZzMXhFYOf3Kfg54w6Rx0xDof1lTDOgbZDNarQIDAQABo2YwZDAdBgNVHQ4E
+FgQU55lRDj0V9T2nceBkLaFclyhVIoAwHwYDVR0jBBgwFoAUsQpqosVnLJPWdgPM
+yD9WvKv6NUkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQELBQADggIBAIktkQHQruRdIcqRSTEeety+ws21E8cqZyCvbBS6iYiW
+lGkXE1KtpT997doEYIqldL/q+PUhY3ruLMVEzd/zl16APVLIfQ94Y9/V+bQoiPTj
+PTpNv713ItSrtFZGIqC1vOFfIwHkTETNpODNM0DXv79IEyz14/sVSsG/kE+qOHyw
+B8DFgzAZWQ4Pa+17CePNeDuUjpE7lkThTL4qVZVZBFKG9UYya/xCvqRL3CKJ8nmD
+EPZtl9fy+FdMswGaTBPb8mEuO/d2p7rv3cMHD0GMn6k052yTc9/XDrKu49rOq/cp
+zRBn/By/vuk3kLPjxyj+kLJWdyAA6I78HgX4/785v0nDki/kM7H+q2UjIFotu4FG
+O5VEgNM4GIWxF8Pjm9OdHnudopk4o+ODk8cAEdePIAl+jWY6bry0nCTKwvhPbtL+
+m1b3ZjZxe3tzHQUbAuCK9B8QDDwJJhsRNij+AcefD8Orwbh/5b9slzJcfcOxVd3L
+9+ARIuOhUy4BbFWclxAPj56VHDti3yDi2JkjsfWHpZO/JXjzXBARMAHzR2KuT/IJ
+lxYL48dtoY/DGqiwoUbcTIa4DSONkf1BTzvcK3AyISBUd5+/IO5SlMXvM6om7EsZ
+KxD5nMoV3VepQUz7ZZEqcWx46kiWY/C8SOpAhcMd2ElKtbbBKd2tPKhKhKxCcLNe
+-----END CERTIFICATE-----
diff --git a/kitchen-tests/cookbooks/end_to_end/files/certs/intermediate.cert.pem b/kitchen-tests/cookbooks/end_to_end/files/certs/intermediate.cert.pem
new file mode 100644
index 0000000000..d7e6b6d0a2
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/certs/intermediate.cert.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGLDCCBBSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgakxCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEZMBcGA1UECgwQTm9ydGh3
+aW5kIEJha2luZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxITAfBgNVBAMMGE5vcnRo
+d2luZCBCYWtpbmcgUm9vdCBDQTEnMCUGCSqGSIb3DQEJARYYam9obi5tY2NyYWVA
+cHJvZ3Jlc3MuY29tMB4XDTIwMTIxODE3MjczNloXDTMwMTIxNjE3MjczNlowgZ8x
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEZMBcGA1UECgwQTm9ydGh3aW5kIEJh
+a2luZzEUMBIGA1UECwwLRW5naW5lZXJpbmcxKTAnBgNVBAMMIE5vcnRod2luZCBC
+YWtpbmcgSW50ZXJtZWRpYXRlIENBMScwJQYJKoZIhvcNAQkBFhhqb2huLm1jY3Jh
+ZUBwcm9ncmVzcy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDd
+pjPv+HSfwiVhdCrA3xpMB3UM68GuFXKr9EPzTKexZVW+NPnFO7wSzxNZ2x9+IjRa
+7xPhMdBm3nBSXLpg87NCzS5Z+bQBHPi8M2RlOXyDXYz/Nqh2UIAaodmlGEa7Fwkr
+JgG9RBfuPZumtTWU1aTS4ldg3IyO/XpiNB+6p/XdgJOtYWKvfPO6KFjWkxouCSN3
+antHE9ZpB4L78rO+DK+n+UkXUDMLUFejxy2MCj1zmMqk9Qlt0RIobE/uvC05FNdE
+9xmhQ4rROteY68miy938hIKLlRs/A+7Q8wUTgrTuPxQVi62+WHxdl/zewa9s7zlk
+brQoIXhv6kQQ6ZiHRbjFvAsXcBD6tt0YKeILPMpRAPhgZJFjiS8qWz/28Nn3l2vt
+6nQgVmauw/dJ3wyiNWQ/WuX8HpwzXZ76IiTBzpzaYA4qX1k/PfJQxRPCJBqjmWfa
+bUb1PN6KM5/XYEBY2/VMFk/DdBo+PP8aDAOC37lmyyXYRRwyTyQxNaNUNdNXTWSv
+mrqq8q4IvW4qPpSn4tf7bjiTAa/xNBIPTS+axXojOQ8wBJ4r0rnjLZaLkd5015p3
+WM0bFqvHRtNUgpKd7WAbz9ZZXbzJS/ofxtAHu1JcDYJZ0UlfFBJLPB8Px2dcvWu9
+8gqnTZzMXhFYOf3Kfg54w6Rx0xDof1lTDOgbZDNarQIDAQABo2YwZDAdBgNVHQ4E
+FgQU55lRDj0V9T2nceBkLaFclyhVIoAwHwYDVR0jBBgwFoAUsQpqosVnLJPWdgPM
+yD9WvKv6NUkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQELBQADggIBAIktkQHQruRdIcqRSTEeety+ws21E8cqZyCvbBS6iYiW
+lGkXE1KtpT997doEYIqldL/q+PUhY3ruLMVEzd/zl16APVLIfQ94Y9/V+bQoiPTj
+PTpNv713ItSrtFZGIqC1vOFfIwHkTETNpODNM0DXv79IEyz14/sVSsG/kE+qOHyw
+B8DFgzAZWQ4Pa+17CePNeDuUjpE7lkThTL4qVZVZBFKG9UYya/xCvqRL3CKJ8nmD
+EPZtl9fy+FdMswGaTBPb8mEuO/d2p7rv3cMHD0GMn6k052yTc9/XDrKu49rOq/cp
+zRBn/By/vuk3kLPjxyj+kLJWdyAA6I78HgX4/785v0nDki/kM7H+q2UjIFotu4FG
+O5VEgNM4GIWxF8Pjm9OdHnudopk4o+ODk8cAEdePIAl+jWY6bry0nCTKwvhPbtL+
+m1b3ZjZxe3tzHQUbAuCK9B8QDDwJJhsRNij+AcefD8Orwbh/5b9slzJcfcOxVd3L
+9+ARIuOhUy4BbFWclxAPj56VHDti3yDi2JkjsfWHpZO/JXjzXBARMAHzR2KuT/IJ
+lxYL48dtoY/DGqiwoUbcTIa4DSONkf1BTzvcK3AyISBUd5+/IO5SlMXvM6om7EsZ
+KxD5nMoV3VepQUz7ZZEqcWx46kiWY/C8SOpAhcMd2ElKtbbBKd2tPKhKhKxCcLNe
+-----END CERTIFICATE-----
diff --git a/kitchen-tests/cookbooks/end_to_end/files/certs/steveb.pfx b/kitchen-tests/cookbooks/end_to_end/files/certs/steveb.pfx
new file mode 100644
index 0000000000..097c7075ff
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/files/certs/steveb.pfx
Binary files differ
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_packages.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_packages.rb
index 58ede61f52..68c31e633b 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/_packages.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/_packages.rb
@@ -7,7 +7,7 @@
# this is just a list of package that exist on every O/S we test, and often aren't installed by default. you don't
# have to get too clever here, you can delete packages if they don't exist everywhere we test.
-pkgs = %w{lsof tcpdump strace zsh dmidecode ltrace bc curl wget subversion traceroute htop tmux }
+pkgs = %w{lsof tcpdump strace zsh dmidecode ltrace bc curl wget subversion traceroute tmux }
# this deliberately calls the multipackage API N times in order to do one package installation in order to exercise the
# multipackage cookbook.
@@ -15,10 +15,11 @@ pkgs.each do |pkg|
multipackage pkgs
end
-gems = %w{chef-ruby-lvm community_cookbook_releaser}
+# make sure customers can install knife back into the client for now
+# and also make sure chef_gem works in general
+gem_name = rhel6? ? "community_cookbook_releaser" : "knife"
-gems.each do |gem|
- chef_gem gem do
- compile_time false
- end
+chef_gem gem_name do
+ action :install
+ compile_time false
end
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_windows_defender.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_defender.rb
new file mode 100644
index 0000000000..90a0403ae3
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_defender.rb
@@ -0,0 +1,25 @@
+#
+# Cookbook:: end_to_end
+# Recipe:: _windows_defender
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+#
+
+windows_defender "Configure Windows Defender" do
+ realtime_protection true
+ intrusion_protection_system true
+ lock_ui true
+ scan_archives true
+ scan_scripts true
+ scan_email true
+ scan_removable_drives true
+ scan_network_files false
+ scan_mapped_drives false
+ action :enable
+end
+
+windows_defender_exclusion "Exclude PNG files" do
+ extensions "png"
+ process_paths 'c:\\windows\\system32'
+ action :add
+end
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb
new file mode 100644
index 0000000000..64a05c05c9
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb
@@ -0,0 +1,38 @@
+#
+# Cookbook:: end_to_end
+# Recipe:: _windows_printer
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+#
+
+windows_printer_port "10.4.64.39" do
+ port_name "My awesome port"
+ snmp_enabled true
+ port_protocol 2
+end
+
+# change the port above
+windows_printer_port "10.4.64.39" do
+ port_name "My awesome port"
+ snmp_enabled false
+ port_protocol 2
+end
+
+# delete a port that doesn't exist
+windows_printer_port "10.4.64.37" do
+ action :delete
+end
+
+# create a printer that will also create the port
+windows_printer "HP LaserJet 6th Floor" do
+ ipv4_address "10.4.64.40"
+ driver_name "Generic / Text Only"
+end
+
+# create a printer that uses an existing port
+windows_printer "HP LaserJet 5th Floor" do
+ ipv4_address "10.4.64.41"
+ driver_name "Generic / Text Only"
+ port_name "My awesome port"
+ create_port false
+end
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
index 8991cf1594..1589402fb8 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
@@ -37,12 +37,18 @@ end
include_recipe "::_packages"
-include_recipe "ntp"
+include_recipe "ntp" unless fedora? # fedora 34+ doesn't have NTP
-include_recipe "resolver"
+resolver_config "/etc/resolv.conf" do
+ nameservers [ "8.8.8.8", "8.8.4.4" ]
+ search [ "chef.io" ]
+end
+
+users_from_databag = search("users", "*:*")
users_manage "sysadmin" do
group_id 2300
+ users users_from_databag
action [:create]
end
@@ -52,7 +58,7 @@ include_recipe "openssh"
include_recipe "nscd"
-include_recipe "logrotate"
+logrotate_package "logrotate"
include_recipe "git"
@@ -68,6 +74,18 @@ include_recipe "git"
end
end
+%w{001 002 003}.each do |control|
+ inspec_waiver_file_entry "fake_inspec_control_#{control}" do
+ expiration "2025-07-01"
+ justification "Waiving this control for the purposes of testing"
+ action :add
+ end
+end
+
+inspec_waiver_file_entry "fake_inspec_control_002" do
+ action :remove
+end
+
user_ulimit "tomcat" do
filehandle_soft_limit 8192
filehandle_hard_limit 8192
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/macos.rb b/kitchen-tests/cookbooks/end_to_end/recipes/macos.rb
index 876fac6bde..361a054576 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/macos.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/macos.rb
@@ -23,20 +23,39 @@ timezone "America/Los_Angeles"
include_recipe "ntp"
-include_recipe "resolver"
+resolver_config "/etc/resolv.conf" do
+ nameservers [ "8.8.8.8", "8.8.4.4" ]
+ search [ "chef.io" ]
+end
+
+users_from_databag = search("users", "*:*")
users_manage "remove sysadmin" do
group_name "sysadmin"
group_id 2300
+ users users_from_databag
action [:remove]
end
users_manage "create sysadmin" do
group_name "sysadmin"
group_id 2300
+ users users_from_databag
action [:create]
end
+%w{001 002 003}.each do |control|
+ inspec_waiver_file_entry "fake_inspec_control_#{control}" do
+ expiration "2025-07-01"
+ justification "Waiving this control for the purposes of testing"
+ action :add
+ end
+end
+
+inspec_waiver_file_entry "fake_inspec_control_002" do
+ action :remove
+end
+
ssh_known_hosts_entry "github.com"
include_recipe "::_chef_client_config"
@@ -77,12 +96,14 @@ homebrew_update "update" do
action :update
end
-homebrew_package "vim"
+homebrew_package "nethack"
-homebrew_package "vim" do
+homebrew_package "nethack" do
action :purge
end
+homebrew_cask "do-not-disturb"
+
include_recipe "::_dmg_package"
include_recipe "::_macos_userdefaults"
include_recipe "::_ohai_hint"
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb b/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb
index 58ccec9b26..20140ff7e0 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb
@@ -27,9 +27,29 @@ timezone "Pacific Standard time"
include_recipe "ntp"
+windows_security_policy "NewGuestName" do
+ secvalue "down_with_guests"
+ action :set
+end
+
windows_security_policy "EnableGuestAccount" do
- secoption "EnableGuestAccount"
secvalue "1"
+ action :set
+end
+
+windows_security_policy "LockoutBadCount" do
+ secvalue "15"
+ action :set
+end
+
+windows_security_policy "LockoutDuration" do
+ secvalue "120"
+ action :set
+end
+
+windows_security_policy "ResetLockoutCount" do
+ secvalue "90"
+ action :set
end
windows_firewall_profile "Domain" do
@@ -42,6 +62,18 @@ windows_firewall_profile "Public" do
action :disable
end
+%w{001 002 003}.each do |control|
+ inspec_waiver_file_entry "fake_inspec_control_#{control}" do
+ expiration "2025-07-01"
+ justification "Waiving this control for the purposes of testing"
+ action :add
+ end
+end
+
+inspec_waiver_file_entry "fake_inspec_control_002" do
+ action :remove
+end
+
windows_audit_policy "Update Some Advanced Audit Policies to Success and Failure" do
subcategory ["Application Generated", "Application Group Management", "Audit Policy Change"]
success true
@@ -66,18 +98,23 @@ windows_audit_policy "Update Some Advanced Audit Policies to No Auditing" do
failure false
end
-users_manage "remove sysadmin" do
- group_name "sysadmin"
- group_id 2300
- action [:remove]
-end
-
-# FIXME: create is not idempotent. it fails with a windows error if this already exists.
-users_manage "create sysadmin" do
- group_name "sysadmin"
- group_id 2300
- action [:create]
-end
+# FIXME: upstream users cookbooks is currently broken on windows
+# users_from_databag = search("users", "*:*")
+#
+# users_manage "remove sysadmin" do
+# group_name "sysadmin"
+# group_id 2300
+# users users_from_databag
+# action [:remove]
+# end
+#
+# # FIXME: create is not idempotent. it fails with a windows error if this already exists.
+# users_manage "create sysadmin" do
+# group_name "sysadmin"
+# group_id 2300
+# users users_from_databag
+# action [:create]
+# end
include_recipe "::_chef_client_config"
include_recipe "::_chef_client_trusted_certificate"
@@ -105,4 +142,40 @@ include_recipe "::_ohai_hint"
hostname "new-hostname" do
windows_reboot false
-end \ No newline at end of file
+end
+
+user "phil" do
+ uid "8019"
+end
+
+user "phil" do
+ action :remove
+end
+
+directory 'C:\mordor' do
+ rights :full_control, "everyone"
+end
+
+cookbook_file "c:\\mordor\\steveb.pfx" do
+ source "/certs/steveb.pfx"
+ action :create_if_missing
+end
+
+windows_certificate "c:/mordor/steveb.pfx" do
+ pfx_password "1234"
+ action :create
+ user_store true
+ store_name "MY"
+end
+
+cookbook_file "c:\\mordor\\ca.cert.pem" do
+ source "/certs/ca.cert.pem"
+ action :create_if_missing
+end
+
+windows_certificate "c:/mordor/ca.cert.pem" do
+ store_name "ROOT"
+end
+
+include_recipe "::_windows_printer"
+include_recipe "::_windows_defender"
diff --git a/kitchen-tests/data_bags/users/adam.json b/kitchen-tests/data_bags/users/adam.json
index f96d7c213f..ecd4b73e74 100644
--- a/kitchen-tests/data_bags/users/adam.json
+++ b/kitchen-tests/data_bags/users/adam.json
@@ -5,5 +5,5 @@
"shell": "/bin/zsh",
"groups": [ "sysadmin" ],
"comment": "Adam Jacob",
- "password": "*"
+ "password": "$6$QQk10qmDjMv.o$wHIjLH9JOxUmaJTsxYFttFhP1jZZtTk/ovhpasmQJS5mfimeFs8HMRWGWM8uBB5dhEmP6svqhRdJE5k1oWRPF1"
}
diff --git a/kitchen-tests/kitchen.azure.yml b/kitchen-tests/kitchen.azure.yml
index c44c76579b..5eb8154517 100644
--- a/kitchen-tests/kitchen.azure.yml
+++ b/kitchen-tests/kitchen.azure.yml
@@ -12,6 +12,7 @@ provisioner:
deprecations_as_errors: true
chef_license: accept-no-persist
product_name: chef
+ slow_resource_report: true
client_rb:
diff_disabled: true
always_dump_stacktrace: true
diff --git a/kitchen-tests/kitchen.yml b/kitchen-tests/kitchen.yml
index 53641a6602..d2ec39f01d 100644
--- a/kitchen-tests/kitchen.yml
+++ b/kitchen-tests/kitchen.yml
@@ -14,16 +14,20 @@ provisioner:
diff_disabled: true
always_dump_stacktrace: true
chef_license: "accept-no-persist"
+ slow_resource_report: true
lifecycle:
pre_converge:
- remote: echo "Chef container's Chef / Ohai release:"
- remote: /opt/chef/bin/chef-client -v
- remote: /opt/chef/bin/ohai -v
- - remote: /opt/chef/embedded/bin/bundle -v
- remote: /opt/chef/embedded/bin/gem install appbundler appbundle-updater --no-doc
- - remote: /opt/chef/embedded/bin/appbundle-updater chef ohai <%= File.readlines('../Gemfile.lock', File.expand_path(File.dirname(__FILE__))).find { |l| l =~ /^\s+ohai \((\d+\.\d+\.\d+)\)/ }; 'v' + $1 %> --tarball --github chef/ohai
+ - remote: scl enable devtoolset-8 '/opt/chef/embedded/bin/appbundle-updater chef chef <%= ENV['BUILDKITE_COMMIT'] || %x(git rev-parse HEAD).chomp %> --tarball --github chef/chef'
+ includes:
+ - centos-6
- remote: /opt/chef/embedded/bin/appbundle-updater chef chef <%= ENV['BUILDKITE_COMMIT'] || %x(git rev-parse HEAD).chomp %> --tarball --github chef/chef
+ excludes:
+ - centos-6
- remote: echo "Installed Chef / Ohai release:"
- remote: /opt/chef/bin/chef-client -v
- remote: /opt/chef/bin/ohai -v
@@ -33,13 +37,6 @@ verifier:
format: progress
platforms:
-- name: amazonlinux
- driver:
- image: dokken/amazonlinux
- pid_one_command: /sbin/init
- intermediate_instructions:
- - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
-
- name: amazonlinux-2
driver:
image: dokken/amazonlinux-2
@@ -63,15 +60,23 @@ platforms:
- RUN /usr/bin/apt-get update
- RUN /usr/bin/apt-get install ifupdown -y # we need this for /etc/network/interfaces & ifconfig resource
+- name: debian-11
+ driver:
+ image: dokken/debian-11
+ pid_one_command: /bin/systemd
+ intermediate_instructions:
+ - RUN /usr/bin/apt-get update
+ - RUN /usr/bin/apt-get install ifupdown -y # we need this for /etc/network/interfaces & ifconfig resource
+
- name: centos-6
driver:
image: dokken/centos-6
pid_one_command: /sbin/init
intermediate_instructions:
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
- - RUN yum install -y centos-release-scl
- - RUN yum install -y devtoolset-7
- - RUN scl enable devtoolset-7 bash
+ - RUN wget -O /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo https://www.centos.org/keys/RPM-GPG-KEY-CentOS-SIG-SCLo
+ - RUN printf "[centos-sclo-rh]\nname=CentOS-6 - SCLo rh\nbaseurl=http://vault.centos.org/centos/6/sclo/x86_64/rh\ngpgcheck=1\nenabled=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo" > /etc/yum.repos.d/CentOS-SCLo-rh.repo
+ - RUN yum install -y devtoolset-8
- name: centos-7
driver:
@@ -86,7 +91,7 @@ platforms:
image: dokken/centos-8
pid_one_command: /usr/lib/systemd/systemd
intermediate_instructions:
- - RUN yum -y install e2fsprogs
+ - RUN dnf -y install e2fsprogs
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
- name: oraclelinux-7
@@ -102,8 +107,8 @@ platforms:
image: dokken/oraclelinux-8
pid_one_command: /usr/lib/systemd/systemd
intermediate_instructions:
- - RUN yum -y install e2fsprogs
- - RUN yum -y reinstall systemd
+ - RUN dnf -y install e2fsprogs
+ - RUN dnf -y reinstall systemd
- RUN mkdir /etc/sysconfig/network-scripts # missing from the oracle image
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
@@ -114,13 +119,6 @@ platforms:
intermediate_instructions:
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
-- name: ubuntu-16.04
- driver:
- image: dokken/ubuntu-16.04
- pid_one_command: /bin/systemd
- intermediate_instructions:
- - RUN /usr/bin/apt-get update
-
- name: ubuntu-18.04
driver:
image: dokken/ubuntu-18.04
@@ -137,9 +135,9 @@ platforms:
- RUN /usr/bin/apt-get update
- RUN /usr/bin/apt-get install ifupdown -y # we need this for /etc/network/interfaces & ifconfig resource testing
-- name: ubuntu-20.10
+- name: ubuntu-21.04
driver:
- image: dokken/ubuntu-20.10
+ image: dokken/ubuntu-21.04
pid_one_command: /bin/systemd
intermediate_instructions:
- RUN /usr/bin/apt-get update
diff --git a/knife/Gemfile b/knife/Gemfile
new file mode 100644
index 0000000000..0eda3c8e5f
--- /dev/null
+++ b/knife/Gemfile
@@ -0,0 +1,26 @@
+source "https://rubygems.org"
+
+gem "knife", path: "."
+
+group(:development, :test) do
+ gem "cheffish", ">= 14" # testing only , but why didn't this need to explicit in chef?
+ gem "webmock" # testing only
+ gem "rake"
+ gem "rspec"
+ gem "chef-bin", path: "../chef-bin"
+end
+
+group(:omnibus_package, :pry) do
+ gem "pry"
+ gem "pry-byebug"
+ gem "pry-stack_explorer"
+end
+
+group(:chefstyle) do
+ gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "master"
+end
+
+gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master"
+gem "chef", path: ".."
+gem "chef-utils", path: File.expand_path("../chef-utils", __dir__) if File.exist?(File.expand_path("../chef-utils", __dir__))
+gem "chef-config", path: File.expand_path("../chef-config", __dir__) if File.exist?(File.expand_path("../chef-config", __dir__))
diff --git a/knife/LICENSE b/knife/LICENSE
new file mode 100644
index 0000000000..11069edd79
--- /dev/null
+++ b/knife/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/knife/Rakefile b/knife/Rakefile
new file mode 100644
index 0000000000..40e6670490
--- /dev/null
+++ b/knife/Rakefile
@@ -0,0 +1,33 @@
+# Copyright:: Copyright (c) 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 "rubygems"
+require "bundler/gem_tasks"
+Bundler::GemHelper.install_tasks
+
+begin
+ require "rspec/core/rake_task"
+
+ desc "Run all knife specs"
+ RSpec::Core::RakeTask.new(:spec) do |t|
+ t.verbose = false
+ t.rspec_opts = %w{--profile}
+ t.pattern = FileList["spec/**/*_spec.rb"]
+
+ end
+rescue LoadError
+ puts "rspec not available. bundle install first to make sure all dependencies are installed."
+end
diff --git a/knife/bin/knife b/knife/bin/knife
new file mode 100755
index 0000000000..aebc0f72d7
--- /dev/null
+++ b/knife/bin/knife
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+#
+# ./knife - Chef CLI interface
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright 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.
+
+$:.unshift(File.expand_path(File.join(__dir__, "..", "lib")))
+require "chef/application/knife"
+
+Chef::Application::Knife.new.run
diff --git a/knife/knife.gemspec b/knife/knife.gemspec
new file mode 100644
index 0000000000..9482dc00f9
--- /dev/null
+++ b/knife/knife.gemspec
@@ -0,0 +1,60 @@
+$:.unshift(File.dirname(__FILE__) + "/lib")
+require_relative "lib/chef/knife/version"
+
+Gem::Specification.new do |s|
+ s.name = "knife"
+ s.version = Chef::Knife::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.extra_rdoc_files = ["LICENSE"]
+ s.summary = "The knife CLI for Chef Infra."
+ s.description = s.summary
+ s.license = "Apache-2.0"
+ s.author = "Adam Jacob"
+ s.email = "adam@chef.io" # These seem a bit out of date, and this address probably doesn't go anywhere anymore?
+ s.homepage = "https://www.chef.io"
+
+ s.required_ruby_version = ">= 2.7.0"
+
+ s.add_dependency "chef-config", ">= #{Chef::Knife::VERSION.split(".").first}"
+ s.add_dependency "chef-utils", ">= #{Chef::Knife::VERSION.split(".").first}"
+ s.add_dependency "chef", ">= #{Chef::Knife::VERSION.split(".").first}"
+ s.add_dependency "train-core", "~> 3.2", ">= 3.2.28" # 3.2.28 fixes sudo prompts. See https://github.com/chef/chef/pull/9635
+ s.add_dependency "train-winrm", ">= 0.2.5"
+ s.add_dependency "license-acceptance", ">= 1.0.5", "< 3"
+ s.add_dependency "mixlib-cli", ">= 2.1.1", "< 3.0"
+ s.add_dependency "mixlib-archive", ">= 0.4", "< 2.0"
+ s.add_dependency "ohai", "~> 17.0"
+ s.add_dependency "ffi", ">= 1.15" # 1.14 versions are broken on i386 windows
+ s.add_dependency "ffi-yajl", "~> 2.2"
+ s.add_dependency "net-ssh", ">= 5.1", "< 7"
+ s.add_dependency "net-ssh-multi", "~> 1.2", ">= 1.2.1"
+ s.add_dependency "ed25519", "~> 1.2" # ed25519 ssh key support
+ s.add_dependency "bcrypt_pbkdf", "~> 1.1" # ed25519 ssh key support
+ s.add_dependency "x25519" # ed25519 KEX module
+ s.add_dependency "highline", ">= 1.6.9", "< 3" # Used in UI to present a list, no other usage.
+
+ s.add_dependency "tty-prompt", "~> 0.21" # knife ui.ask prompt
+ s.add_dependency "tty-screen", "~> 0.6" # knife list
+ s.add_dependency "tty-table", "~> 0.11" # knife render table output.
+ s.add_dependency "pastel" # knife ui.color
+ s.add_dependency "erubis", "~> 2.7"
+ s.add_dependency "chef-vault" # knife vault
+
+ s.add_development_dependency "chefstyle"
+
+ s.bindir = "bin"
+ s.executables = %w{ knife }
+
+ s.require_paths = %w{ lib }
+ s.files = %w{Gemfile Rakefile LICENSE knife.gemspec} +
+ Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
+
+ s.metadata = {
+ "bug_tracker_uri" => "https://github.com/chef/chef/issues",
+ "changelog_uri" => "https://github.com/chef/chef/blob/master/CHANGELOG.md",
+ "documentation_uri" => "https://docs.chef.io/",
+ "homepage_uri" => "https://www.chef.io",
+ "mailing_list_uri" => "https://discourse.chef.io/",
+ "source_code_uri" => "https://github.com/chef/chef/",
+ }
+end
diff --git a/knife/lib/chef/application/knife.rb b/knife/lib/chef/application/knife.rb
new file mode 100644
index 0000000000..9893effbe2
--- /dev/null
+++ b/knife/lib/chef/application/knife.rb
@@ -0,0 +1,234 @@
+#
+# Author:: Adam Jacob (<adam@chef.io)
+# Copyright:: Copyright (c) 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 "chef/application"
+require_relative "../knife"
+require "mixlib/log"
+require "ohai/config"
+module Net
+ autoload :HTTP, "net/http"
+end
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef::Application::Knife < Chef::Application
+
+ NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., knife SUB-COMMAND)\n".freeze
+
+ banner "Usage: knife sub-command (options)"
+
+ option :config_file,
+ short: "-c CONFIG",
+ long: "--config CONFIG",
+ description: "The configuration file to use.",
+ proc: lambda { |path| File.expand_path(path, Dir.pwd) }
+
+ option :config_option,
+ long: "--config-option OPTION=VALUE",
+ description: "Override a single configuration option.",
+ proc: lambda { |option, existing|
+ (existing ||= []) << option
+ existing
+ }
+
+ verbosity_level = 0
+ option :verbosity,
+ short: "-V",
+ long: "--verbose",
+ description: "More verbose output. Use twice (-VV) for additional verbosity and three times (-VVV) for maximum verbosity.",
+ proc: Proc.new { verbosity_level += 1 },
+ default: 0
+
+ option :color,
+ long: "--[no-]color",
+ boolean: true,
+ default: true,
+ description: "Use colored output, defaults to enabled."
+
+ option :environment,
+ short: "-E ENVIRONMENT",
+ long: "--environment ENVIRONMENT",
+ description: "Set the #{ChefUtils::Dist::Infra::PRODUCT} environment (except for in searches, where this will be flagrantly ignored)."
+
+ option :editor,
+ short: "-e EDITOR",
+ long: "--editor EDITOR",
+ description: "Set the editor to use for interactive commands.",
+ default: ENV["EDITOR"]
+
+ option :disable_editing,
+ short: "-d",
+ long: "--disable-editing",
+ description: "Do not open EDITOR, just accept the data as is.",
+ boolean: true,
+ default: false
+
+ option :help,
+ short: "-h",
+ long: "--help",
+ description: "Show this help message.",
+ on: :tail,
+ boolean: true
+
+ option :node_name,
+ short: "-u USER",
+ long: "--user USER",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} API client username."
+
+ option :client_key,
+ short: "-k KEY",
+ long: "--key KEY",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} API client key.",
+ proc: lambda { |path| File.expand_path(path, Dir.pwd) }
+
+ option :chef_server_url,
+ short: "-s URL",
+ long: "--server-url URL",
+ description: "#{ChefUtils::Dist::Server::PRODUCT} URL."
+
+ option :yes,
+ short: "-y",
+ long: "--yes",
+ description: "Say yes to all prompts for confirmation."
+
+ option :defaults,
+ long: "--defaults",
+ description: "Accept default values for all questions."
+
+ option :print_after,
+ long: "--print-after",
+ description: "Show the data after a destructive operation."
+
+ option :format,
+ short: "-F FORMAT",
+ long: "--format FORMAT",
+ description: "Which format to use for output.",
+ in: %w{summary text json yaml pp},
+ default: "summary"
+
+ option :local_mode,
+ short: "-z",
+ long: "--local-mode",
+ description: "Point knife commands at local repository instead of #{ChefUtils::Dist::Server::PRODUCT}.",
+ boolean: true
+
+ option :chef_zero_host,
+ long: "--chef-zero-host HOST",
+ description: "Host to start #{ChefUtils::Dist::Zero::PRODUCT} on."
+
+ option :chef_zero_port,
+ long: "--chef-zero-port PORT",
+ description: "Port (or port range) to start #{ChefUtils::Dist::Zero::PRODUCT} on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
+
+ option :listen,
+ long: "--[no-]listen",
+ description: "Whether a local mode (-z) server binds to a port.",
+ boolean: false
+
+ option :version,
+ short: "-v",
+ long: "--version",
+ description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
+ boolean: true,
+ proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
+ exit: 0
+
+ option :fips,
+ long: "--[no-]fips",
+ description: "Enable FIPS mode.",
+ boolean: true,
+ default: nil
+
+ option :profile,
+ long: "--profile PROFILE",
+ description: "The credentials profile to select."
+
+ # Run knife
+ def run
+ ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
+ Mixlib::Log::Formatter.show_time = false
+ validate_and_parse_options
+ quiet_traps
+ Chef::Knife.run(ARGV, options)
+ exit 0
+ end
+
+ private
+
+ def quiet_traps
+ trap("TERM") do
+ exit 1
+ end
+
+ trap("INT") do
+ exit 2
+ end
+ end
+
+ def validate_and_parse_options
+ # Checking ARGV validity *before* parse_options because parse_options
+ # mangles ARGV in some situations
+ if no_command_given?
+ print_help_and_exit(1, NO_COMMAND_GIVEN)
+ elsif no_subcommand_given?
+ if want_help? || want_version?
+ print_help_and_exit(0)
+ else
+ print_help_and_exit(2, NO_COMMAND_GIVEN)
+ end
+ end
+ end
+
+ def no_subcommand_given?
+ ARGV[0] =~ /^-/
+ end
+
+ def no_command_given?
+ ARGV.empty?
+ end
+
+ def want_help?
+ ARGV[0] =~ /^(--help|-h)$/
+ end
+
+ def want_version?
+ ARGV[0] =~ /^(--version|-v)$/
+ end
+
+ def print_help_and_exit(exitcode = 1, fatal_message = nil)
+ Chef::Log.error(fatal_message) if fatal_message
+
+ begin
+ parse_options
+ rescue OptionParser::InvalidOption => e
+ puts "#{e}\n"
+ end
+
+ if want_help?
+ puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{Chef::VERSION}"
+ puts
+ puts "Docs: #{ChefUtils::Dist::Org::KNIFE_DOCS}"
+ puts "Patents: #{ChefUtils::Dist::Org::PATENTS}"
+ puts
+ end
+
+ puts opt_parser
+ puts
+ Chef::Knife.list_commands
+ exit exitcode
+ end
+
+end
diff --git a/knife/lib/chef/chef_fs/knife.rb b/knife/lib/chef/chef_fs/knife.rb
new file mode 100644
index 0000000000..55473da8cd
--- /dev/null
+++ b/knife/lib/chef/chef_fs/knife.rb
@@ -0,0 +1,162 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require "pathname" unless defined?(Pathname)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "chef-utils/parallel_map" unless defined?(ChefUtils::ParallelMap)
+
+using ChefUtils::ParallelMap
+
+class Chef
+ module ChefFS
+ class Knife < Chef::Knife
+ # Workaround for CHEF-3932
+ def self.deps
+ super do
+ require "chef/config" unless defined?(Chef::Config)
+ require "chef/chef_fs/config" unless defined?(Chef::ChefFS::Config)
+ require "chef/chef_fs/file_pattern" unless defined?(Chef::ChefFS::FilePattern)
+ require "chef/chef_fs/path_utils" unless defined?(Chef::ChefFS::PathUtils)
+ yield
+ end
+ end
+
+ def self.inherited(c)
+ super
+
+ # Ensure we always get to do our includes, whether subclass calls deps or not
+ c.deps do
+ end
+ end
+
+ option :repo_mode,
+ long: "--repo-mode MODE",
+ description: "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything"
+
+ option :chef_repo_path,
+ long: "--chef-repo-path PATH",
+ description: "Overrides the location of #{ChefUtils::Dist::Infra::PRODUCT} repo. Default is specified by chef_repo_path in the config"
+
+ option :concurrency,
+ long: "--concurrency THREADS",
+ description: "Maximum number of simultaneous requests to send (default: 10)"
+
+ def configure_chef
+ super
+ Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
+ Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency]
+
+ # --chef-repo-path forcibly overrides all other paths
+ if config[:chef_repo_path]
+ Chef::Config[:chef_repo_path] = config[:chef_repo_path]
+ Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
+ Chef::Config.delete("#{variable_name}_path".to_sym)
+ end
+ end
+
+ @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config, ui)
+
+ ChefUtils::DefaultThreadPool.instance.threads = (Chef::Config[:concurrency] || 10) - 1
+ end
+
+ def chef_fs
+ @chef_fs_config.chef_fs
+ end
+
+ def create_chef_fs
+ @chef_fs_config.create_chef_fs
+ end
+
+ def local_fs
+ @chef_fs_config.local_fs
+ end
+
+ def create_local_fs
+ @chef_fs_config.create_local_fs
+ end
+
+ def pattern_args
+ @pattern_args ||= pattern_args_from(name_args)
+ end
+
+ def pattern_args_from(args)
+ args.map { |arg| pattern_arg_from(arg) }
+ end
+
+ def pattern_arg_from(arg)
+ inferred_path = nil
+ if Chef::ChefFS::PathUtils.is_absolute?(arg)
+ # We should be able to use this as-is - but the user might have incorrectly provided
+ # us with a path that is based off of the OS root path instead of the Chef-FS root.
+ # Do a quick and dirty sanity check.
+ if possible_server_path = @chef_fs_config.server_path(arg)
+ ui.warn("The absolute path provided is suspicious: #{arg}")
+ ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
+ ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
+ end
+ # Use the original path because we can't be sure.
+ inferred_path = arg
+ elsif arg[0, 1] == "~"
+ # Let's be nice and fix it if possible - but warn the user.
+ ui.warn("A path relative to a user home directory has been provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif Pathname.new(arg).absolute?
+ # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
+ # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
+ ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif @chef_fs_config.base_path.nil?
+ # These are all relative paths. We can't resolve and root paths unless we are in the
+ # chef repo.
+ ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
+ ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
+ exit(1)
+ else
+ inferred_path = Chef::ChefFS::PathUtils.join(@chef_fs_config.base_path, arg)
+ end
+ Chef::ChefFS::FilePattern.new(inferred_path)
+ end
+
+ def format_path(entry)
+ @chef_fs_config.format_path(entry)
+ end
+
+ def parallelize(inputs, options = {}, &block)
+ inputs.parallel_map(&block)
+ end
+
+ def discover_repo_dir(dir)
+ %w{.chef cookbooks data_bags environments roles}.each do |subdir|
+ return dir if File.directory?(File.join(dir, subdir))
+ end
+ # If this isn't it, check the parent
+ parent = File.dirname(dir)
+ if parent && parent != dir
+ discover_repo_dir(parent)
+ else
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife.rb b/knife/lib/chef/knife.rb
new file mode 100644
index 0000000000..e5f8c36e32
--- /dev/null
+++ b/knife/lib/chef/knife.rb
@@ -0,0 +1,673 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Christopher Brown (<cb@chef.io>)
+# Copyright:: Copyright (c) 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 "forwardable" unless defined?(Forwardable)
+require_relative "knife/version"
+require "mixlib/cli" unless defined?(Mixlib::CLI)
+require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "chef/workstation_config_loader" unless defined?(Chef::WorkstationConfigLoader)
+require "chef/mixin/convert_to_class_name" unless defined?(Chef::ConvertToClassName)
+require "chef/mixin/default_paths" unless defined?(Chef::Mixin::DefaultPaths)
+require_relative "knife/core/subcommand_loader"
+require_relative "knife/core/ui"
+require "chef/local_mode" unless defined?(Chef::LocalMode)
+require "chef/server_api" unless defined?(Chef::ServerAPI)
+require "http/authenticator" unless defined?(Chef::HTTP::Authenticator)
+require "http/http_request" unless defined?(Chef::HTTP::HTTPRequest)
+require "http" unless defined?(Chef::HTTP)
+# End
+
+require "pp" unless defined?(PP)
+
+require_relative "application/knife"
+
+class Chef
+ class Knife
+
+ Chef::HTTP::HTTPRequest.user_agent = "#{ChefUtils::Dist::Infra::PRODUCT} Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}"
+
+ include Mixlib::CLI
+ include ChefUtils::DSL::DefaultPaths
+ extend Chef::Mixin::ConvertToClassName
+ extend Forwardable
+
+ # @note Backwards Compat:
+ # Ideally, we should not vomit all of these methods into this base class;
+ # instead, they should be accessed by hitting the ui object directly.
+ def_delegator :@ui, :stdout
+ def_delegator :@ui, :stderr
+ def_delegator :@ui, :stdin
+ def_delegator :@ui, :msg
+ def_delegator :@ui, :ask_question
+ def_delegator :@ui, :pretty_print
+ def_delegator :@ui, :output
+ def_delegator :@ui, :format_list_for_display
+ def_delegator :@ui, :format_for_display
+ def_delegator :@ui, :format_cookbook_list_for_display
+ def_delegator :@ui, :edit_data
+ def_delegator :@ui, :edit_hash
+ def_delegator :@ui, :edit_object
+ def_delegator :@ui, :confirm
+
+ attr_accessor :name_args
+ attr_accessor :ui
+
+ # knife acl subcommands are grouped in this category using this constant to verify.
+ OPSCODE_HOSTED_CHEF_ACCESS_CONTROL = %w{acl group user}.freeze
+
+ # knife opc subcommands are grouped in this category using this constant to verify.
+ CHEF_ORGANIZATION_MANAGEMENT = %w{opc}.freeze
+
+ # Configure mixlib-cli to always separate defaults from user-supplied CLI options
+ def self.use_separate_defaults?
+ true
+ end
+
+ def self.ui
+ @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
+ end
+
+ def self.msg(msg = "")
+ ui.msg(msg)
+ end
+
+ def self.reset_config_loader!
+ @@chef_config_dir = nil
+ @config_loader = nil
+ end
+
+ def self.reset_subcommands!
+ @@subcommands = {}
+ @subcommands_by_category = nil
+ end
+
+ def self.inherited(subclass)
+ super
+ unless subclass.unnamed?
+ subcommands[subclass.snake_case_name] = subclass
+ subcommand_files[subclass.snake_case_name] +=
+ if subclass.superclass.to_s == "Chef::ChefFS::Knife"
+ # ChefFS-based commands have a superclass that defines an
+ # inherited method which calls super. This means that the
+ # top of the call stack is not the class definition for
+ # our subcommand. Try the second entry in the call stack.
+ [path_from_caller(caller[1])]
+ else
+ [path_from_caller(caller[0])]
+ end
+ end
+ end
+
+ # Explicitly set the category for the current command to +new_category+
+ # The category is normally determined from the first word of the command
+ # name, but some commands make more sense using two or more words
+ # @param new_category [String] value to set the category to (see examples)
+ #
+ # @example Data bag commands would be in the 'data' category by default. To
+ # put them in the 'data bag' category:
+ # category('data bag')
+ def self.category(new_category)
+ @category = new_category
+ end
+
+ def self.subcommand_category
+ @category || snake_case_name.split("_").first unless unnamed?
+ end
+
+ def self.snake_case_name
+ convert_to_snake_case(name.split("::").last) unless unnamed?
+ end
+
+ def self.common_name
+ snake_case_name.split("_").join(" ")
+ end
+
+ # Does this class have a name? (Classes created via Class.new don't)
+ def self.unnamed?
+ name.nil? || name.empty?
+ end
+
+ def self.subcommand_loader
+ @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir)
+ end
+
+ def self.load_commands
+ @commands_loaded ||= subcommand_loader.load_commands
+ end
+
+ def self.guess_category(args)
+ subcommand_loader.guess_category(args)
+ end
+
+ def self.subcommand_class_from(args)
+ if args.size == 1 && args[0].strip.casecmp("rehash") == 0
+ # To prevent issues with the rehash file not pointing to the correct plugins,
+ # we always use the glob loader when regenerating the rehash file
+ @subcommand_loader = Chef::Knife::SubcommandLoader.gem_glob_loader(chef_config_dir)
+ end
+ subcommand_loader.command_class_from(args) || subcommand_not_found!(args)
+ end
+
+ def self.subcommands
+ @@subcommands ||= {}
+ end
+
+ def self.subcommand_files
+ @@subcommand_files ||= Hash.new([])
+ end
+
+ def self.subcommands_by_category
+ unless @subcommands_by_category
+ @subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
+ subcommands.each do |snake_cased, klass|
+ @subcommands_by_category[klass.subcommand_category] << snake_cased
+ end
+ end
+ @subcommands_by_category
+ end
+
+ # Shared with subclasses
+ @@chef_config_dir = nil
+
+ def self.config_loader
+ @config_loader ||= WorkstationConfigLoader.new(nil, Chef::Log)
+ end
+
+ def self.load_config(explicit_config_file, profile)
+ config_loader.explicit_config_file = explicit_config_file
+ config_loader.profile = profile
+ config_loader.load
+
+ ui.warn("No knife configuration file found. See https://docs.chef.io/config_rb/ for details.") if config_loader.no_config_found?
+
+ config_loader
+ rescue Exceptions::ConfigurationError => e
+ ui.error(ui.color("CONFIGURATION ERROR:", :red) + e.message)
+ exit 1
+ end
+
+ def self.chef_config_dir
+ @@chef_config_dir ||= config_loader.chef_config_dir
+ end
+
+ # Run knife for the given +args+ (ARGV), adding +options+ to the list of
+ # CLI options that the subcommand knows how to handle.
+ #
+ # @param args [Array] The arguments. Usually ARGV
+ # @param options [Mixlib::CLI option parser hash] These +options+ are how
+ # subcommands know about global knife CLI options
+ #
+ def self.run(args, options = {})
+ # Fallback debug logging. Normally the logger isn't configured until we
+ # read the config, but this means any logging that happens before the
+ # config file is read may be lost. If the KNIFE_DEBUG variable is set, we
+ # setup the logger for debug logging to stderr immediately to catch info
+ # from early in the setup process.
+ if ENV["KNIFE_DEBUG"]
+ Chef::Log.init($stderr)
+ Chef::Log.level(:debug)
+ end
+
+ subcommand_class = subcommand_class_from(args)
+ subcommand_class.options = options.merge!(subcommand_class.options)
+ subcommand_class.load_deps
+ instance = subcommand_class.new(args)
+ instance.configure_chef
+ instance.run_with_pretty_exceptions
+ end
+
+ def self.dependency_loaders
+ @dependency_loaders ||= []
+ end
+
+ def self.deps(&block)
+ dependency_loaders << block
+ end
+
+ def self.load_deps
+ dependency_loaders.each(&:call)
+ end
+
+ OFFICIAL_PLUGINS = %w{lpar openstack push rackspace vcenter}.freeze
+
+ class << self
+ def list_commands(preferred_category = nil)
+ category_desc = preferred_category ? preferred_category + " " : ""
+ msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
+ subcommand_loader.list_commands(preferred_category).sort.each do |category, commands|
+ next if /deprecated/i.match?(category)
+
+ msg "** #{category.upcase} COMMANDS **"
+ commands.sort.each do |command|
+ subcommand_loader.load_command(command)
+ msg subcommands[command].banner if subcommands[command]
+ end
+ msg
+ end
+ end
+
+ private
+
+ # @api private
+ def path_from_caller(caller_line)
+ caller_line.split(/:\d+/).first
+ end
+
+ # Error out and print usage. probably because the arguments given by the
+ # user could not be resolved to a subcommand.
+ # @api private
+ def subcommand_not_found!(args)
+ ui.fatal("Cannot find subcommand for: '#{args.join(" ")}'")
+
+ # Mention rehash when the subcommands cache(plugin_manifest.json) is used
+ if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader)
+ ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
+ end
+
+ if CHEF_ORGANIZATION_MANAGEMENT.include?(args[0])
+ list_commands("CHEF ORGANIZATION MANAGEMENT")
+ elsif category_commands = guess_category(args)
+ list_commands(category_commands)
+ elsif OFFICIAL_PLUGINS.include?(args[0]) # command was an uninstalled official chef knife plugin
+ ui.info("Use `#{ChefUtils::Dist::Infra::EXEC} gem install knife-#{args[0]}` to install the plugin into Chef Workstation")
+ else
+ list_commands
+ end
+
+ exit 10
+ end
+
+ # @api private
+ def reset_config_path!
+ @@chef_config_dir = nil
+ end
+
+ end
+
+ reset_config_path!
+
+ # Create a new instance of the current class configured for the given
+ # arguments and options
+ def initialize(argv = [])
+ super() # having to call super in initialize is the most annoying anti-pattern :(
+ @ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, config)
+
+ command_name_words = self.class.snake_case_name.split("_")
+
+ # Mixlib::CLI ignores the embedded name_args
+ @name_args = parse_options(argv)
+ @name_args.delete(command_name_words.join("-"))
+ @name_args.reject! { |name_arg| command_name_words.delete(name_arg) }
+
+ # knife node run_list add requires that we have extra logic to handle
+ # the case that command name words could be joined by an underscore :/
+ command_name_joined = command_name_words.join("_")
+ @name_args.reject! { |name_arg| command_name_joined == name_arg }
+
+ # Similar handling for hyphens.
+ command_name_joined = command_name_words.join("-")
+ @name_args.reject! { |name_arg| command_name_joined == name_arg }
+
+ if config[:help]
+ msg opt_parser
+ exit 1
+ end
+
+ # Grab a copy before config merge occurs, so that we can later identify
+ # where a given config value is sourced from.
+ @original_config = config.dup
+
+ # copy Mixlib::CLI over so that it can be configured in config.rb/knife.rb
+ # config file
+ Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity]
+ end
+
+ def parse_options(args)
+ super
+ rescue OptionParser::InvalidOption => e
+ puts "Error: " + e.to_s
+ show_usage
+ exit(1)
+ end
+
+ # This is all set and default mixlib-config values. We only need the default
+ # values here (the set values are explicitly mixed in again later), but there is
+ # no mixlib-config API to get a Hash back with only the default values.
+ #
+ # Assumption: since config_file_defaults is the lowest precedence it doesn't matter
+ # that we include the set values here, but this is a hack and makes the name of the
+ # method a lie. FIXME: make the name not a lie by adding an API to mixlib-config.
+ #
+ # @api private
+ #
+ def config_file_defaults
+ Chef::Config[:knife].save(true) # this is like "dup" to a (real) Hash, and includes default values (and user set values)
+ end
+
+ # This is only the user-set mixlib-config values. We do not include the defaults
+ # here so that the config defaults do not override the cli defaults.
+ #
+ # @api private
+ #
+ def config_file_settings
+ Chef::Config[:knife].save(false) # this is like "dup" to a (real) Hash, and does not include default values (just user set values)
+ end
+
+ # config is merged in this order (inverse of precedence)
+ # config_file_defaults - Chef::Config[:knife] defaults from chef-config (XXX: this also includes the settings, but they get overwritten)
+ # default_config - mixlib-cli defaults (accessor from mixlib-cli)
+ # config_file_settings - Chef::Config[:knife] user settings from the client.rb file
+ # config - mixlib-cli settings (accessor from mixlib-cli)
+ #
+ def merge_configs
+ # Update our original_config - if someone has created a knife command
+ # instance directly, they are likely ot have set cmd.config values directly
+ # as well, at which point our saved original config is no longer up to date.
+ @original_config = config.dup
+ # other code may have a handle to the config object, so use Hash#replace to deliberately
+ # update-in-place.
+ config.replace(config_file_defaults.merge(default_config).merge(config_file_settings).merge(config))
+ end
+
+ #
+ # Determine the source of a given configuration key
+ #
+ # @argument key [Symbol] a configuration key
+ # @return [Symbol,NilClass] return the source of the config key,
+ # one of:
+ # - :cli - this was explicitly provided on the CLI
+ # - :config - this came from Chef::Config[:knife] explicitly being set
+ # - :cli_default - came from a declared CLI `option`'s `default` value.
+ # - :config_default - this came from Chef::Config[:knife]'s defaults
+ # - nil - if the key could not be found in any source.
+ # This can happen when it is invalid, or has been
+ # set directly into #config without then calling #merge_config
+ def config_source(key)
+ return :cli if @original_config.include? key
+ return :config if config_file_settings.key? key
+ return :cli_default if default_config.include? key
+ return :config_default if config_file_defaults.key? key # must come after :config check
+
+ nil
+ end
+
+ # Catch-all method that does any massaging needed for various config
+ # components, such as expanding file paths and converting verbosity level
+ # into log level.
+ def apply_computed_config
+ Chef::Config[:color] = config[:color]
+
+ case Chef::Config[:verbosity]
+ when 0, nil
+ Chef::Config[:log_level] = :warn
+ when 1
+ Chef::Config[:log_level] = :info
+ when 2
+ Chef::Config[:log_level] = :debug
+ else
+ Chef::Config[:log_level] = :trace
+ end
+
+ Chef::Config[:log_level] = :trace if ENV["KNIFE_DEBUG"]
+
+ Chef::Config[:node_name] = config[:node_name] if config[:node_name]
+ Chef::Config[:client_key] = config[:client_key] if config[:client_key]
+ Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
+ Chef::Config[:environment] = config[:environment] if config[:environment]
+
+ Chef::Config.local_mode = config[:local_mode] if config.key?(:local_mode)
+
+ Chef::Config.listen = config[:listen] if config.key?(:listen)
+
+ if Chef::Config.local_mode && !Chef::Config.key?(:cookbook_path) && !Chef::Config.key?(:chef_repo_path)
+ Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
+ end
+ Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host]
+ Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
+
+ # Expand a relative path from the config directory. Config from command
+ # line should already be expanded, and absolute paths will be unchanged.
+ if Chef::Config[:client_key] && config[:config_file]
+ Chef::Config[:client_key] = File.expand_path(Chef::Config[:client_key], File.dirname(config[:config_file]))
+ end
+
+ Mixlib::Log::Formatter.show_time = false
+ Chef::Log.init(Chef::Config[:log_location])
+ Chef::Log.level(Chef::Config[:log_level] || :error)
+ end
+
+ def configure_chef
+ # knife needs to send logger output to STDERR by default
+ Chef::Config[:log_location] = STDERR
+ config_loader = self.class.load_config(config[:config_file], config[:profile])
+ config[:config_file] = config_loader.config_location
+
+ # For CLI options like `--config-option key=value`. These have to get
+ # parsed and applied separately.
+ extra_config_options = config.delete(:config_option)
+
+ merge_configs
+ apply_computed_config
+
+ # This has to be after apply_computed_config so that Mixlib::Log is configured
+ Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file]
+
+ begin
+ Chef::Config.apply_extra_config_options(extra_config_options)
+ rescue ChefConfig::UnparsableConfigOption => e
+ ui.error e.message
+ show_usage
+ exit(1)
+ end
+
+ Chef::Config.export_proxies
+ end
+
+ def show_usage
+ stdout.puts("USAGE: " + opt_parser.to_s)
+ end
+
+ def run_with_pretty_exceptions(raise_exception = false)
+ unless respond_to?(:run)
+ ui.error "You need to add a #run method to your knife command before you can use it"
+ end
+ ENV["PATH"] = default_paths if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity]
+ maybe_setup_fips
+ Chef::LocalMode.with_server_connectivity do
+ run
+ end
+ rescue Exception => e
+ raise if raise_exception || ( Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 2 )
+
+ humanize_exception(e)
+ exit 100
+ end
+
+ def humanize_exception(e)
+ case e
+ when SystemExit
+ raise # make sure exit passes through.
+ when Net::HTTPClientException, Net::HTTPFatalError
+ humanize_http_exception(e)
+ when OpenSSL::SSL::SSLError
+ ui.error "Could not establish a secure connection to the server."
+ ui.info "Use `knife ssl check` to troubleshoot your SSL configuration."
+ ui.info "If your server uses a self-signed certificate, you can use"
+ ui.info "`knife ssl fetch` to make knife trust the server's certificates."
+ ui.info ""
+ ui.info "Original Exception: #{e.class.name}: #{e.message}"
+ when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
+ ui.error "Network Error: #{e.message}"
+ ui.info "Check your knife configuration and network settings"
+ when NameError, NoMethodError
+ ui.error "knife encountered an unexpected error"
+ ui.info "This may be a bug in the '#{self.class.common_name}' knife command or plugin"
+ ui.info "Please collect the output of this command with the `-VVV` option before filing a bug report."
+ ui.info "Exception: #{e.class.name}: #{e.message}"
+ when Chef::Exceptions::PrivateKeyMissing
+ ui.error "Your private key could not be loaded from #{api_key}"
+ ui.info "Check your configuration file and ensure that your private key is readable"
+ when Chef::Exceptions::InvalidRedirect
+ ui.error "Invalid Redirect: #{e.message}"
+ ui.info "Change your server location in config.rb/knife.rb to the server's FQDN to avoid unwanted redirections."
+ else
+ ui.error "#{e.class.name}: #{e.message}"
+ end
+ end
+
+ def humanize_http_exception(e)
+ response = e.response
+ case response
+ when Net::HTTPUnauthorized
+ ui.error "Failed to authenticate to #{server_url} as #{username} with key #{api_key}"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPForbidden
+ ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action."
+ proxy_env_vars = ENV.to_hash.keys.map(&:downcase) & %w{http_proxy https_proxy ftp_proxy socks_proxy no_proxy}
+ unless proxy_env_vars.empty?
+ ui.error "There are proxy servers configured, your server url may need to be added to NO_PROXY."
+ end
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPBadRequest
+ ui.error "The data in your request was invalid"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPNotFound
+ ui.error "The object you are looking for could not be found"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPInternalServerError
+ ui.error "internal server error"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPBadGateway
+ ui.error "bad gateway"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPServiceUnavailable
+ ui.error "Service temporarily unavailable"
+ ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPNotAcceptable
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+ ui.error "The API version that Knife is using is not supported by the server you sent this request to."
+ ui.info "The request that Knife sent was using API version #{client_api_version}."
+ ui.info "The server you sent the request to supports a min API version of #{min_server_version} and a max API version of #{max_server_version}."
+ ui.info "Please either update your #{ChefUtils::Dist::Infra::PRODUCT} or the server to be a compatible set."
+ else
+ ui.error response.message
+ ui.info "Response: #{format_rest_error(response)}"
+ end
+ end
+
+ def username
+ Chef::Config[:node_name]
+ end
+
+ def api_key
+ Chef::Config[:client_key]
+ end
+
+ # Parses JSON from the error response sent by Chef Server and returns the
+ # error message
+ #--
+ # TODO: this code belongs in Chef::REST
+ def format_rest_error(response)
+ Array(Chef::JSONCompat.from_json(response.body)["error"]).join("; ")
+ rescue Exception
+ response.body
+ end
+
+ # FIXME: yard with @yield
+ def create_object(object, pretty_name = nil, object_class: nil)
+ output = if object_class
+ edit_data(object, object_class: object_class)
+ else
+ edit_hash(object)
+ end
+
+ if Kernel.block_given?
+ output = yield(output)
+ else
+ output.save
+ end
+
+ pretty_name ||= output
+
+ msg("Created #{pretty_name}")
+
+ output(output) if config[:print_after]
+ end
+
+ # FIXME: yard with @yield
+ def delete_object(klass, name, delete_name = nil)
+ confirm("Do you really want to delete #{name}")
+
+ if Kernel.block_given?
+ object = yield
+ else
+ object = klass.load(name)
+ object.destroy
+ end
+
+ output(format_for_display(object)) if config[:print_after]
+
+ obj_name = delete_name ? "#{delete_name}[#{name}]" : object
+ msg("Deleted #{obj_name}")
+ end
+
+ # helper method for testing if a field exists
+ # and returning the usage and proper error if not
+ def test_mandatory_field(field, fieldname)
+ if field.nil?
+ show_usage
+ ui.fatal("You must specify a #{fieldname}")
+ exit 1
+ end
+ end
+
+ def rest
+ @rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
+ end
+
+ def noauth_rest
+ @rest ||= begin
+ require "chef/http/simple_json" unless defined?(Chef::HTTP::SimpleJSON)
+ Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url])
+ end
+ end
+
+ def server_url
+ Chef::Config[:chef_server_url]
+ end
+
+ def maybe_setup_fips
+ unless config[:fips].nil?
+ Chef::Config[:fips] = config[:fips]
+ end
+ Chef::Config.init_openssl
+ end
+
+ def root_rest
+ @root_rest ||= begin
+ require "chef/server_api" unless defined? Chef::ServerAPI
+ Chef::ServerAPI.new(Chef::Config[:chef_server_root])
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/acl_add.rb b/knife/lib/chef/knife/acl_add.rb
index 144a18fcb1..144a18fcb1 100644
--- a/lib/chef/knife/acl_add.rb
+++ b/knife/lib/chef/knife/acl_add.rb
diff --git a/lib/chef/knife/acl_base.rb b/knife/lib/chef/knife/acl_base.rb
index 0835d1ac05..0835d1ac05 100644
--- a/lib/chef/knife/acl_base.rb
+++ b/knife/lib/chef/knife/acl_base.rb
diff --git a/lib/chef/knife/acl_bulk_add.rb b/knife/lib/chef/knife/acl_bulk_add.rb
index 4992fe2afa..4992fe2afa 100644
--- a/lib/chef/knife/acl_bulk_add.rb
+++ b/knife/lib/chef/knife/acl_bulk_add.rb
diff --git a/lib/chef/knife/acl_bulk_remove.rb b/knife/lib/chef/knife/acl_bulk_remove.rb
index 0f35f1e2fb..0f35f1e2fb 100644
--- a/lib/chef/knife/acl_bulk_remove.rb
+++ b/knife/lib/chef/knife/acl_bulk_remove.rb
diff --git a/lib/chef/knife/acl_remove.rb b/knife/lib/chef/knife/acl_remove.rb
index 13f089ff3e..13f089ff3e 100644
--- a/lib/chef/knife/acl_remove.rb
+++ b/knife/lib/chef/knife/acl_remove.rb
diff --git a/lib/chef/knife/acl_show.rb b/knife/lib/chef/knife/acl_show.rb
index d3a5002b30..d3a5002b30 100644
--- a/lib/chef/knife/acl_show.rb
+++ b/knife/lib/chef/knife/acl_show.rb
diff --git a/knife/lib/chef/knife/bootstrap.rb b/knife/lib/chef/knife/bootstrap.rb
new file mode 100644
index 0000000000..d57614cb3d
--- /dev/null
+++ b/knife/lib/chef/knife/bootstrap.rb
@@ -0,0 +1,1191 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "data_bag_secret_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "license_acceptance/cli_flags/mixlib_cli"
+module LicenseAcceptance
+ autoload :Acceptor, "license_acceptance/acceptor"
+end
+
+class Chef
+ class Knife
+ class Bootstrap < Knife
+ include DataBagSecretOptions
+ include LicenseAcceptance::CLIFlags::MixlibCLI
+
+ SUPPORTED_CONNECTION_PROTOCOLS ||= %w{ssh winrm}.freeze
+ WINRM_AUTH_PROTOCOL_LIST ||= %w{plaintext kerberos ssl negotiate}.freeze
+
+ # Common connectivity options
+ option :connection_user,
+ short: "-U USERNAME",
+ long: "--connection-user USERNAME",
+ description: "Authenticate to the target host with this user account."
+
+ option :connection_password,
+ short: "-P PASSWORD",
+ long: "--connection-password PASSWORD",
+ description: "Authenticate to the target host with this password."
+
+ option :connection_port,
+ short: "-p PORT",
+ long: "--connection-port PORT",
+ description: "The port on the target node to connect to."
+
+ option :connection_protocol,
+ short: "-o PROTOCOL",
+ long: "--connection-protocol PROTOCOL",
+ description: "The protocol to use to connect to the target node.",
+ in: SUPPORTED_CONNECTION_PROTOCOLS
+
+ option :max_wait,
+ short: "-W SECONDS",
+ long: "--max-wait SECONDS",
+ description: "The maximum time to wait for the initial connection to be established."
+
+ option :session_timeout,
+ long: "--session-timeout SECONDS",
+ description: "The number of seconds to wait for each connection operation to be acknowledged while running bootstrap.",
+ default: 60
+
+ # WinRM Authentication
+ option :winrm_ssl_peer_fingerprint,
+ long: "--winrm-ssl-peer-fingerprint FINGERPRINT",
+ description: "SSL certificate fingerprint expected from the target."
+
+ option :ca_trust_file,
+ short: "-f CA_TRUST_PATH",
+ long: "--ca-trust-file CA_TRUST_PATH",
+ description: "The Certificate Authority (CA) trust file used for SSL transport."
+
+ option :winrm_no_verify_cert,
+ long: "--winrm-no-verify-cert",
+ description: "Do not verify the SSL certificate of the target node for WinRM.",
+ boolean: true
+
+ option :winrm_ssl,
+ long: "--winrm-ssl",
+ description: "Use SSL in the WinRM connection."
+
+ option :winrm_auth_method,
+ short: "-w AUTH-METHOD",
+ long: "--winrm-auth-method AUTH-METHOD",
+ description: "The WinRM authentication method to use.",
+ in: WINRM_AUTH_PROTOCOL_LIST
+
+ option :winrm_basic_auth_only,
+ long: "--winrm-basic-auth-only",
+ description: "For WinRM basic authentication when using the 'ssl' auth method.",
+ boolean: true
+
+ # This option was provided in knife bootstrap windows winrm,
+ # but it is ignored in knife-windows/WinrmSession, and so remains unimplemented here.
+ # option :kerberos_keytab_file,
+ # :short => "-T KEYTAB_FILE",
+ # :long => "--keytab-file KEYTAB_FILE",
+ # :description => "The Kerberos keytab file used for authentication"
+
+ option :kerberos_realm,
+ short: "-R KERBEROS_REALM",
+ long: "--kerberos-realm KERBEROS_REALM",
+ description: "The Kerberos realm used for authentication."
+
+ option :kerberos_service,
+ short: "-S KERBEROS_SERVICE",
+ long: "--kerberos-service KERBEROS_SERVICE",
+ description: "The Kerberos service used for authentication."
+
+ ## SSH Authentication
+ option :ssh_gateway,
+ short: "-G GATEWAY",
+ long: "--ssh-gateway GATEWAY",
+ description: "The SSH gateway."
+
+ option :ssh_gateway_identity,
+ long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
+ description: "The SSH identity file used for gateway authentication."
+
+ option :ssh_forward_agent,
+ short: "-A",
+ long: "--ssh-forward-agent",
+ description: "Enable SSH agent forwarding.",
+ boolean: true
+
+ option :ssh_identity_file,
+ short: "-i IDENTITY_FILE",
+ long: "--ssh-identity-file IDENTITY_FILE",
+ description: "The SSH identity file used for authentication."
+
+ option :ssh_verify_host_key,
+ long: "--ssh-verify-host-key VALUE",
+ description: "Verify host key. Default is 'always'.",
+ in: %w{always accept_new accept_new_or_local_tunnel never},
+ default: "always"
+
+ #
+ # bootstrap options
+ #
+
+ # client.rb content via chef-full/bootstrap_context
+ option :bootstrap_version,
+ long: "--bootstrap-version VERSION",
+ description: "The version of #{ChefUtils::Dist::Infra::PRODUCT} to install."
+
+ option :channel,
+ long: "--channel CHANNEL",
+ description: "Install from the given channel. Default is 'stable'.",
+ default: "stable",
+ in: %w{stable current unstable}
+
+ # client.rb content via chef-full/bootstrap_context
+ option :bootstrap_proxy,
+ long: "--bootstrap-proxy PROXY_URL",
+ description: "The proxy server for the node being bootstrapped."
+
+ # client.rb content via bootstrap_context
+ option :bootstrap_proxy_user,
+ long: "--bootstrap-proxy-user PROXY_USER",
+ description: "The proxy authentication username for the node being bootstrapped."
+
+ # client.rb content via bootstrap_context
+ option :bootstrap_proxy_pass,
+ long: "--bootstrap-proxy-pass PROXY_PASS",
+ description: "The proxy authentication password for the node being bootstrapped."
+
+ # client.rb content via bootstrap_context
+ option :bootstrap_no_proxy,
+ long: "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
+ description: "Do not proxy locations for the node being bootstrapped"
+
+ # client.rb content via bootstrap_context
+ option :bootstrap_template,
+ short: "-t TEMPLATE",
+ long: "--bootstrap-template TEMPLATE",
+ description: "Bootstrap #{ChefUtils::Dist::Infra::PRODUCT} using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
+
+ # client.rb content via bootstrap_context
+ option :node_ssl_verify_mode,
+ long: "--node-ssl-verify-mode [peer|none]",
+ description: "Whether or not to verify the SSL cert for all HTTPS requests.",
+ proc: Proc.new { |v|
+ valid_values = %w{none peer}
+ unless valid_values.include?(v)
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
+ end
+
+ v
+ }
+
+ # bootstrap_context - client.rb
+ option :node_verify_api_cert,
+ long: "--[no-]node-verify-api-cert",
+ description: "Verify the SSL cert for HTTPS requests to the #{ChefUtils::Dist::Server::PRODUCT} API.",
+ boolean: true
+
+ # runtime - sudo settings (train handles sudo)
+ option :use_sudo,
+ long: "--sudo",
+ description: "Execute the bootstrap via sudo.",
+ boolean: true
+
+ # runtime - sudo settings (train handles sudo)
+ option :preserve_home,
+ long: "--sudo-preserve-home",
+ description: "Preserve non-root user HOME environment variable with sudo.",
+ boolean: true
+
+ # runtime - sudo settings (train handles sudo)
+ option :use_sudo_password,
+ long: "--use-sudo-password",
+ description: "Execute the bootstrap via sudo with password.",
+ boolean: false
+
+ # runtime - su user
+ option :su_user,
+ long: "--su-user NAME",
+ description: "The su - USER name to perform bootstrap command using a non-root user."
+
+ # runtime - su user password
+ option :su_password,
+ long: "--su-password PASSWORD",
+ description: "The su USER password for authentication."
+
+ # runtime - client_builder
+ option :chef_node_name,
+ short: "-N NAME",
+ long: "--node-name NAME",
+ description: "The node name for your new node."
+
+ # runtime - client_builder - set runlist when creating node
+ option :run_list,
+ short: "-r RUN_LIST",
+ long: "--run-list RUN_LIST",
+ description: "Comma separated list of roles/recipes to apply.",
+ proc: lambda { |o| o.split(/[\s,]+/) },
+ default: []
+
+ # runtime - client_builder - set policy name when creating node
+ option :policy_name,
+ long: "--policy-name POLICY_NAME",
+ description: "Policyfile name to use (--policy-group must also be given).",
+ default: nil
+
+ # runtime - client_builder - set policy group when creating node
+ option :policy_group,
+ long: "--policy-group POLICY_GROUP",
+ description: "Policy group name to use (--policy-name must also be given).",
+ default: nil
+
+ # runtime - client_builder - node tags
+ option :tags,
+ long: "--tags TAGS",
+ description: "Comma separated list of tags to apply to the node.",
+ proc: lambda { |o| o.split(/[\s,]+/) },
+ default: []
+
+ # bootstrap template
+ option :first_boot_attributes,
+ short: "-j JSON_ATTRIBS",
+ long: "--json-attributes",
+ description: "A JSON string to be added to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
+ proc: lambda { |o| Chef::JSONCompat.parse(o) },
+ default: nil
+
+ # bootstrap template
+ option :first_boot_attributes_from_file,
+ long: "--json-attribute-file FILE",
+ description: "A JSON file to be used to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
+ proc: lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
+ default: nil
+
+ # bootstrap template
+ # Create ohai hints in /etc/chef/ohai/hints, fname=hintname, content=value
+ option :hints,
+ long: "--hint HINT_NAME[=HINT_FILE]",
+ description: "Specify an Ohai hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
+ proc: Proc.new { |hint, accumulator|
+ accumulator ||= {}
+ name, path = hint.split("=", 2)
+ accumulator[name] = path ? Chef::JSONCompat.parse(::File.read(path)) : {}
+ accumulator
+ }
+
+ # bootstrap override: url of a an installer shell script to use in place of omnitruck
+ # Note that the bootstrap template _only_ references this out of Chef::Config, and not from
+ # the provided options to knife bootstrap, so we set the Chef::Config option here.
+ option :bootstrap_url,
+ long: "--bootstrap-url URL",
+ description: "URL to a custom installation script."
+
+ option :bootstrap_product,
+ long: "--bootstrap-product PRODUCT",
+ description: "Product to install.",
+ default: "chef"
+
+ option :msi_url, # Windows target only
+ short: "-m URL",
+ long: "--msi-url URL",
+ description: "Location of the #{ChefUtils::Dist::Infra::PRODUCT} MSI. The default templates will prefer to download from this location. The MSI will be downloaded from #{ChefUtils::Dist::Org::WEBSITE} if not provided (Windows).",
+ default: ""
+
+ # bootstrap override: Do this instead of our own setup.sh from omnitruck. Causes bootstrap_url to be ignored.
+ option :bootstrap_install_command,
+ long: "--bootstrap-install-command COMMANDS",
+ description: "Custom command to install #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ # bootstrap template: Run this command first in the bootstrap script
+ option :bootstrap_preinstall_command,
+ long: "--bootstrap-preinstall-command COMMANDS",
+ description: "Custom commands to run before installing #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ # bootstrap template
+ option :bootstrap_wget_options,
+ long: "--bootstrap-wget-options OPTIONS",
+ description: "Add options to wget when installing #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ # bootstrap template
+ option :bootstrap_curl_options,
+ long: "--bootstrap-curl-options OPTIONS",
+ description: "Add options to curl when install #{ChefUtils::Dist::Infra::PRODUCT}."
+
+ # chef_vault_handler
+ option :bootstrap_vault_file,
+ long: "--bootstrap-vault-file VAULT_FILE",
+ description: "A JSON file with a list of vault(s) and item(s) to be updated."
+
+ # chef_vault_handler
+ option :bootstrap_vault_json,
+ long: "--bootstrap-vault-json VAULT_JSON",
+ description: "A JSON string with the vault(s) and item(s) to be updated."
+
+ # chef_vault_handler
+ option :bootstrap_vault_item,
+ long: "--bootstrap-vault-item VAULT_ITEM",
+ description: 'A single vault and item to update as "vault:item".',
+ proc: Proc.new { |i, accumulator|
+ (vault, item) = i.split(":")
+ accumulator ||= {}
+ accumulator[vault] ||= []
+ accumulator[vault].push(item)
+ accumulator
+ }
+
+ # Deprecated options. These must be declared after
+ # regular options because they refer to the replacement
+ # option definitions implicitly.
+ deprecated_option :auth_timeout,
+ replacement: :max_wait,
+ long: "--max-wait SECONDS"
+
+ deprecated_option :forward_agent,
+ replacement: :ssh_forward_agent,
+ boolean: true, long: "--forward-agent"
+
+ deprecated_option :host_key_verify,
+ replacement: :ssh_verify_host_key,
+ boolean: true, long: "--[no-]host-key-verify",
+ value_mapper: Proc.new { |verify| verify ? "always" : "never" }
+
+ deprecated_option :prerelease,
+ replacement: :channel,
+ long: "--prerelease",
+ boolean: true, value_mapper: Proc.new { "current" }
+
+ deprecated_option :ssh_user,
+ replacement: :connection_user,
+ long: "--ssh-user USERNAME"
+
+ deprecated_option :ssh_password,
+ replacement: :connection_password,
+ long: "--ssh-password PASSWORD"
+
+ deprecated_option :ssh_port,
+ replacement: :connection_port,
+ long: "--ssh-port PASSWORD"
+
+ deprecated_option :ssl_peer_fingerprint,
+ replacement: :winrm_ssl_peer_fingerprint,
+ long: "--ssl-peer-fingerprint FINGERPRINT"
+
+ deprecated_option :winrm_user,
+ replacement: :connection_user,
+ long: "--winrm-user USERNAME", short: "-x USERNAME"
+
+ deprecated_option :winrm_password,
+ replacement: :connection_password,
+ long: "--winrm-password PASSWORD"
+
+ deprecated_option :winrm_port,
+ replacement: :connection_port,
+ long: "--winrm-port PORT"
+
+ deprecated_option :winrm_authentication_protocol,
+ replacement: :winrm_auth_method,
+ long: "--winrm-authentication-protocol PROTOCOL"
+
+ deprecated_option :winrm_session_timeout,
+ replacement: :session_timeout,
+ long: "--winrm-session-timeout MINUTES"
+
+ deprecated_option :winrm_ssl_verify_mode,
+ replacement: :winrm_no_verify_cert,
+ long: "--winrm-ssl-verify-mode MODE"
+
+ deprecated_option :winrm_transport, replacement: :winrm_ssl,
+ long: "--winrm-transport TRANSPORT",
+ value_mapper: Proc.new { |value| value == "ssl" }
+
+ attr_reader :connection
+
+ deps do
+ require "erubis" unless defined?(Erubis)
+ require "net/ssh" unless defined?(Net::SSH)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+ require_relative "bootstrap/chef_vault_handler"
+ require_relative "bootstrap/client_builder"
+ require_relative "bootstrap/train_connector"
+ end
+
+ banner "knife bootstrap [PROTOCOL://][USER@]FQDN (options)"
+
+ def client_builder
+ @client_builder ||= Chef::Knife::Bootstrap::ClientBuilder.new(
+ chef_config: Chef::Config,
+ config: config,
+ ui: ui
+ )
+ end
+
+ def chef_vault_handler
+ @chef_vault_handler ||= Chef::Knife::Bootstrap::ChefVaultHandler.new(
+ config: config,
+ ui: ui
+ )
+ end
+
+ # Determine if we need to accept the Chef Infra license locally in order to successfully bootstrap
+ # the remote node. Remote 'chef-client' run will fail if it is >= 15 and the license is not accepted locally.
+ def check_license
+ Chef::Log.debug("Checking if we need to accept Chef license to bootstrap node")
+ version = config[:bootstrap_version] || Chef::VERSION.split(".").first
+ acceptor = LicenseAcceptance::Acceptor.new(logger: Chef::Log, provided: Chef::Config[:chef_license])
+ if acceptor.license_required?("chef", version)
+ Chef::Log.debug("License acceptance required for chef version: #{version}")
+ license_id = acceptor.id_from_mixlib("chef")
+ acceptor.check_and_persist(license_id, version)
+ Chef::Config[:chef_license] ||= acceptor.acceptance_value
+ end
+ end
+
+ # The default bootstrap template to use to bootstrap a server.
+ # This is a public API hook which knife plugins use or inherit and override.
+ #
+ # @return [String] Default bootstrap template
+ def default_bootstrap_template
+ if connection.windows?
+ "windows-chef-client-msi"
+ else
+ "chef-full"
+ end
+ end
+
+ def host_descriptor
+ Array(@name_args).first
+ end
+
+ # The server_name is the DNS or IP we are going to connect to, it is not necessarily
+ # the node name, the fqdn, or the hostname of the server. This is a public API hook
+ # which knife plugins use or inherit and override.
+ #
+ # @return [String] The DNS or IP that bootstrap will connect to
+ def server_name
+ if host_descriptor
+ @server_name ||= host_descriptor.split("@").reverse[0]
+ end
+ end
+
+ # @return [String] The CLI specific bootstrap template or the default
+ def bootstrap_template
+ # Allow passing a bootstrap template or use the default
+ config[:bootstrap_template] || default_bootstrap_template
+ end
+
+ def find_template
+ template = bootstrap_template
+
+ # Use the template directly if it's a path to an actual file
+ if File.exist?(template)
+ Chef::Log.trace("Using the specified bootstrap template: #{File.dirname(template)}")
+ return template
+ end
+
+ # Otherwise search the template directories until we find the right one
+ bootstrap_files = []
+ bootstrap_files << File.join(__dir__, "bootstrap/templates", "#{template}.erb")
+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
+ ChefConfig::PathHelper.home(".chef", "bootstrap", "#{template}.erb") { |p| bootstrap_files << p }
+ bootstrap_files << Gem.find_files(File.join("chef", "knife", "bootstrap", "#{template}.erb"))
+ bootstrap_files.flatten!
+
+ template_file = Array(bootstrap_files).find do |bootstrap_template|
+ Chef::Log.trace("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
+ File.exist?(bootstrap_template)
+ end
+
+ unless template_file
+ ui.info("Can not find bootstrap definition for #{template}")
+ raise Errno::ENOENT
+ end
+
+ Chef::Log.trace("Found bootstrap template: #{template_file}")
+
+ template_file
+ end
+
+ def secret
+ @secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ end
+
+ # Establish bootstrap context for template rendering.
+ # Requires connection to be a live connection in order to determine
+ # the correct platform.
+ def bootstrap_context
+ @bootstrap_context ||=
+ if connection.windows?
+ require_relative "core/windows_bootstrap_context"
+ Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config, secret)
+ else
+ require_relative "core/bootstrap_context"
+ Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret)
+ end
+ end
+
+ def first_boot_attributes
+ @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {}
+ end
+
+ def render_template
+ @config[:first_boot_attributes] = first_boot_attributes
+ template_file = find_template
+ template = IO.read(template_file).chomp
+ Erubis::Eruby.new(template).evaluate(bootstrap_context)
+ end
+
+ def run
+ check_license if ChefUtils::Dist::Org::ENFORCE_LICENSE
+
+ plugin_setup!
+ validate_name_args!
+ validate_protocol!
+ validate_first_boot_attributes!
+ validate_winrm_transport_opts!
+ validate_policy_options!
+ plugin_validate_options!
+
+ winrm_warn_no_ssl_verification
+ warn_on_short_session_timeout
+
+ plugin_create_instance!
+ $stdout.sync = true
+ connect!
+ register_client
+
+ content = render_template
+ bootstrap_path = upload_bootstrap(content)
+ perform_bootstrap(bootstrap_path)
+ plugin_finalize
+ ensure
+ connection.del_file!(bootstrap_path) if connection && bootstrap_path
+ end
+
+ def register_client
+ # chef-vault integration must use the new client-side hawtness, otherwise to use the
+ # new client-side hawtness, just delete your validation key.
+ if chef_vault_handler.doing_chef_vault? ||
+ (Chef::Config[:validation_key] &&
+ !File.exist?(File.expand_path(Chef::Config[:validation_key])))
+
+ unless config[:chef_node_name]
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
+ exit 1
+ end
+ client_builder.run
+ chef_vault_handler.run(client_builder.client)
+
+ bootstrap_context.client_pem = client_builder.client_path
+ else
+ ui.warn "Performing legacy client registration with the validation key at #{Chef::Config[:validation_key]}..."
+ ui.warn "Remove the key file or remove the 'validation_key' configuration option from your config.rb (knife.rb) to use more secure user credentials for client registration."
+ end
+ end
+
+ def perform_bootstrap(remote_bootstrap_script_path)
+ ui.info("Bootstrapping #{ui.color(server_name, :bold)}")
+ cmd = bootstrap_command(remote_bootstrap_script_path)
+ bootstrap_run_command(cmd)
+ end
+
+ # Actual bootstrap command to be run on the node.
+ # Handles recursive calls if su USER failed to authenticate.
+ def bootstrap_run_command(cmd)
+ r = connection.run_command(cmd) do |data, channel|
+ ui.msg("#{ui.color(" [#{connection.hostname}]", :cyan)} #{data}")
+ channel.send_data("#{config[:su_password] || config[:connection_password]}\n") if data.match?("Password:")
+ end
+
+ if r.exit_status != 0
+ ui.error("The following error occurred on #{server_name}:")
+ ui.error("#{r.stdout} #{r.stderr}".strip)
+ exit(r.exit_status)
+ end
+ rescue Train::UserError => e
+ limit ||= 0
+ if e.reason == :bad_su_user_password && limit < 3
+ limit += 1
+ ui.warn("Failed to authenticate su - #{config[:su_user]} to #{server_name}")
+ config[:su_password] = ui.ask("Enter password for su - #{config[:su_user]}@#{server_name}:", echo: false)
+ retry
+ else
+ raise
+ end
+ end
+
+ def connect!
+ ui.info("Connecting to #{ui.color(server_name, :bold)} using #{connection_protocol}")
+ opts ||= connection_opts.dup
+ do_connect(opts)
+ rescue Train::Error => e
+ # We handle these by message text only because train only loads the
+ # transports and protocols that it needs - so the exceptions may not be defined,
+ # and we don't want to require files internal to train.
+ if e.message =~ /fingerprint (\S+) is unknown for "(.+)"/ # Train::Transports::SSHFailed
+ fingerprint = $1
+ hostname, ip = $2.split(",")
+ # TODO: convert the SHA256 base64 value to hex with colons
+ # 'ssh' example output:
+ # RSA key fingerprint is e5:cb:c0:e2:21:3b:12:52:f8:ce:cb:00:24:e2:0c:92.
+ # ECDSA key fingerprint is 5d:67:61:08:a9:d7:01:fd:5e:ae:7e:09:40:ef:c0:3c.
+ # will exit 3 on N
+ ui.confirm <<~EOM
+ The authenticity of host '#{hostname} (#{ip})' can't be established.
+ fingerprint is #{fingerprint}.
+
+ Are you sure you want to continue connecting
+ EOM
+ # FIXME: this should save the key to known_hosts but doesn't appear to be
+ config[:ssh_verify_host_key] = :accept_new
+ conn_opts = connection_opts(reset: true)
+ opts.merge! conn_opts
+ retry
+ elsif (ssh? && e.cause && e.cause.class == Net::SSH::AuthenticationFailed) || (ssh? && e.class == Train::ClientError && e.reason == :no_ssh_password_or_key_available)
+ if connection.password_auth?
+ raise
+ else
+ ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
+ password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
+ end
+
+ opts.merge! force_ssh_password_opts(password)
+ retry
+ else
+ raise
+ end
+ rescue RuntimeError => e
+ if winrm? && e.message == "password is a required option"
+ if connection.password_auth?
+ raise
+ else
+ ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
+ password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
+ end
+
+ opts.merge! force_winrm_password_opts(password)
+ retry
+ else
+ raise
+ end
+ end
+
+ def handle_ssh_error(e); end
+
+ # url values override CLI flags, if you provide both
+ # we'll use the one that you gave in the URL.
+ def connection_protocol
+ return @connection_protocol if @connection_protocol
+
+ from_url = host_descriptor =~ %r{^(.*)://} ? $1 : nil
+ from_knife = config[:connection_protocol]
+ @connection_protocol = from_url || from_knife || "ssh"
+ end
+
+ def do_connect(conn_options)
+ @connection = TrainConnector.new(host_descriptor, connection_protocol, conn_options)
+ connection.connect!
+ rescue Train::UserError => e
+ limit ||= 1
+ if !conn_options.key?(:pty) && e.reason == :sudo_no_tty
+ ui.warn("#{e.message} - trying with pty request")
+ conn_options[:pty] = true # ensure we can talk to systems with requiretty set true in sshd config
+ retry
+ elsif config[:use_sudo_password] && (e.reason == :sudo_password_required || e.reason == :bad_sudo_password) && limit < 3
+ ui.warn("Failed to authenticate #{conn_options[:user]} to #{server_name} - #{e.message} \n sudo: #{limit} incorrect password attempt")
+ sudo_password = ui.ask("Enter sudo password for #{conn_options[:user]}@#{server_name}:", echo: false)
+ limit += 1
+ conn_options[:sudo_password] = sudo_password
+
+ retry
+ else
+ raise
+ end
+ end
+
+ # Fail if both first_boot_attributes and first_boot_attributes_from_file
+ # are set.
+ def validate_first_boot_attributes!
+ if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
+ raise Chef::Exceptions::BootstrapCommandInputError
+ end
+
+ true
+ end
+
+ # FIXME: someone needs to clean this up properly: https://github.com/chef/chef/issues/9645
+ # This code is deliberately left without an abstraction around deprecating the config options to avoid knife plugins from
+ # using those methods (which will need to be deprecated and break them) via inheritance (ruby does not have a true `private`
+ # so the lack of any inheritable implementation is because of that).
+ #
+ def winrm_auth_method
+ config.key?(:winrm_auth_method) ? config[:winrm_auth_method] : config.key?(:winrm_authentications_protocol) ? config[:winrm_authentication_protocol] : "negotiate" # rubocop:disable Style/NestedTernaryOperator
+ end
+
+ def ssh_verify_host_key
+ config.key?(:ssh_verify_host_key) ? config[:ssh_verify_host_key] : config.key?(:host_key_verify) ? config[:host_key_verify] : "always" # rubocop:disable Style/NestedTernaryOperator
+ end
+
+ # Fail if using plaintext auth without ssl because
+ # this can expose keys in plaintext on the wire.
+ # TODO test for this method
+ # TODO check that the protocol is valid.
+ def validate_winrm_transport_opts!
+ return true unless winrm?
+
+ if Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))
+ if winrm_auth_method == "plaintext" &&
+ config[:winrm_ssl] != true
+ ui.error <<~EOM
+ Validatorless bootstrap over unsecure winrm channels could expose your
+ key to network sniffing.
+ Please use a 'winrm_auth_method' other than 'plaintext',
+ or enable ssl on #{server_name} then use the ---winrm-ssl flag
+ to connect.
+ EOM
+
+ exit 1
+ end
+ end
+ true
+ end
+
+ # fail if the server_name is nil
+ def validate_name_args!
+ if server_name.nil?
+ ui.error("Must pass an FQDN or ip to bootstrap")
+ exit 1
+ end
+ end
+
+ # Ensure options are valid by checking policyfile values.
+ #
+ # The method call will cause the program to exit(1) if:
+ # * Only one of --policy-name and --policy-group is specified
+ # * Policyfile options are set and --run-list is set as well
+ #
+ # @return [TrueClass] If options are valid.
+ def validate_policy_options!
+ if incomplete_policyfile_options?
+ ui.error("--policy-name and --policy-group must be specified together")
+ exit 1
+ elsif policyfile_and_run_list_given?
+ ui.error("Policyfile options and --run-list are exclusive")
+ exit 1
+ end
+ end
+
+ # Ensure a valid protocol is provided for target host connection
+ #
+ # The method call will cause the program to exit(1) if:
+ # * Conflicting protocols are given via the target URI and the --protocol option
+ # * The protocol is not a supported protocol
+ #
+ # @return [TrueClass] If options are valid.
+ def validate_protocol!
+ from_cli = config[:connection_protocol]
+ if from_cli && connection_protocol != from_cli
+ # Hanging indent to align with the ERROR: prefix
+ ui.error <<~EOM
+ The URL '#{host_descriptor}' indicates protocol is '#{connection_protocol}'
+ while the --protocol flag specifies '#{from_cli}'. Please include
+ only one or the other.
+ EOM
+ exit 1
+ end
+
+ unless SUPPORTED_CONNECTION_PROTOCOLS.include?(connection_protocol)
+ ui.error <<~EOM
+ Unsupported protocol '#{connection_protocol}'.
+
+ Supported protocols are: #{SUPPORTED_CONNECTION_PROTOCOLS.join(" ")}
+ EOM
+ exit 1
+ end
+ true
+ end
+
+ # Validate any additional options
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to validate any additional options before any other actions are executed
+ #
+ # @return [TrueClass] If options are valid or exits
+ def plugin_validate_options!
+ true
+ end
+
+ # Create the server that we will bootstrap, if necessary
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to call out to an API to build an instance of the server we wish to bootstrap
+ #
+ # @return [TrueClass] If instance successfully created, or exits
+ def plugin_create_instance!
+ true
+ end
+
+ # Perform any setup necessary by the plugin
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to create connection objects
+ #
+ # @return [TrueClass] If instance successfully created, or exits
+ def plugin_setup!; end
+
+ # Perform any teardown or cleanup necessary by the plugin
+ #
+ # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to display a message or perform any cleanup
+ #
+ # @return [void]
+ def plugin_finalize; end
+
+ # If session_timeout is too short, it is likely
+ # a holdover from "--winrm-session-timeout" which used
+ # minutes as its unit, instead of seconds.
+ # Warn the human so that they are not surprised.
+ #
+ def warn_on_short_session_timeout
+ if session_timeout && session_timeout <= 15
+ ui.warn <<~EOM
+ You provided '--session-timeout #{session_timeout}' second(s).
+ Did you mean '--session-timeout #{session_timeout * 60}' seconds?
+ EOM
+ end
+ end
+
+ def winrm_warn_no_ssl_verification
+ return unless winrm?
+
+ # REVIEWER NOTE
+ # The original check from knife plugin did not include winrm_ssl_peer_fingerprint
+ # Reference:
+ # https://github.com/chef/knife-windows/blob/92d151298142be4a4750c5b54bb264f8d5b81b8a/lib/chef/knife/winrm_knife_base.rb#L271-L273
+ # TODO Seems like we should also do a similar warning if ssh_verify_host == false
+ if config[:ca_trust_file].nil? &&
+ config[:winrm_no_verify_cert] &&
+ config[:winrm_ssl_peer_fingerprint].nil?
+ ui.warn <<~WARN
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ SSL validation of HTTPS requests for the WinRM transport is disabled.
+ HTTPS WinRM connections are still encrypted, but knife is not able
+ to detect forged replies or spoofing attacks.
+
+ To work around this issue you can use the flag `--winrm-no-verify-cert`
+ or add an entry like this to your knife configuration file:
+
+ # Verify all WinRM HTTPS connections
+ knife[:winrm_no_verify_cert] = true
+
+ You can also specify a ca_trust_file via --ca-trust-file,
+ or the expected fingerprint of the target host's certificate
+ via --winrm-ssl-peer-fingerprint.
+ WARN
+ end
+ end
+
+ # @return a configuration hash suitable for connecting to the remote
+ # host via train
+ def connection_opts(reset: false)
+ return @connection_opts unless @connection_opts.nil? || reset == true
+
+ @connection_opts = {}
+ @connection_opts.merge! base_opts
+ @connection_opts.merge! host_verify_opts
+ @connection_opts.merge! gateway_opts
+ @connection_opts.merge! sudo_opts
+ @connection_opts.merge! winrm_opts
+ @connection_opts.merge! ssh_opts
+ @connection_opts.merge! ssh_identity_opts
+ @connection_opts
+ end
+
+ def winrm?
+ connection_protocol == "winrm"
+ end
+
+ def ssh?
+ connection_protocol == "ssh"
+ end
+
+ # Common configuration for all protocols
+ def base_opts
+ port = config_for_protocol(:port)
+ user = config_for_protocol(:user)
+ {}.tap do |opts|
+ opts[:logger] = Chef::Log
+ opts[:password] = config[:connection_password] if config.key?(:connection_password)
+ opts[:user] = user if user
+ opts[:max_wait_until_ready] = config[:max_wait].to_f unless config[:max_wait].nil?
+ # TODO - when would we need to provide rdp_port vs port? Or are they not mutually exclusive?
+ opts[:port] = port if port
+ end
+ end
+
+ def host_verify_opts
+ if winrm?
+ { self_signed: config[:winrm_no_verify_cert] === true }
+ elsif ssh?
+ # Fall back to the old knife config key name for back compat.
+ { verify_host_key: ssh_verify_host_key }
+ else
+ {}
+ end
+ end
+
+ def ssh_opts
+ opts = {}
+ return opts if winrm?
+
+ opts[:non_interactive] = true # Prevent password prompts from underlying net/ssh
+ opts[:forward_agent] = (config[:ssh_forward_agent] === true)
+ opts[:connection_timeout] = session_timeout
+ opts
+ end
+
+ def ssh_identity_opts
+ opts = {}
+ return opts if winrm?
+
+ identity_file = config[:ssh_identity_file]
+ if identity_file
+ opts[:key_files] = [identity_file]
+ # We only set keys_only based on the explicit ssh_identity_file;
+ # someone may use a gateway key and still expect password auth
+ # on the target. Similarly, someone may have a default key specified
+ # in knife config, but have provided a password on the CLI.
+
+ # REVIEW NOTE: this is a new behavior. Originally, ssh_identity_file
+ # could only be populated from CLI options, so there was no need to check
+ # for this. We will also set keys_only to false only if there are keys
+ # and no password.
+ # If both are present, train(via net/ssh) will prefer keys, falling back to password.
+ # Reference: https://github.com/chef/chef/blob/master/lib/chef/knife/ssh.rb#L272
+ opts[:keys_only] = config.key?(:connection_password) == false
+ else
+ opts[:key_files] = []
+ opts[:keys_only] = false
+ end
+
+ gateway_identity_file = config[:ssh_gateway] ? config[:ssh_gateway_identity] : nil
+ unless gateway_identity_file.nil?
+ opts[:key_files] << gateway_identity_file
+ end
+
+ opts
+ end
+
+ def gateway_opts
+ opts = {}
+ if config[:ssh_gateway]
+ split = config[:ssh_gateway].split("@", 2)
+ if split.length == 1
+ gw_host = split[0]
+ else
+ gw_user = split[0]
+ gw_host = split[1]
+ end
+ gw_host, gw_port = gw_host.split(":", 2)
+ # TODO - validate convertible port in config validation?
+ gw_port = Integer(gw_port) rescue nil
+ opts[:bastion_host] = gw_host
+ opts[:bastion_user] = gw_user
+ opts[:bastion_port] = gw_port
+ end
+ opts
+ end
+
+ # use_sudo - tells bootstrap to use the sudo command to run bootstrap
+ # use_sudo_password - tells bootstrap to use the sudo command to run bootstrap
+ # and to use the password specified with --password
+ # TODO: I'd like to make our sudo options sane:
+ # --sudo (bool) - use sudo
+ # --sudo-password PASSWORD (default: :password) - use this password for sudo
+ # --sudo-options "opt,opt,opt" to pass into sudo
+ # --sudo-command COMMAND sudo command other than sudo
+ # REVIEW NOTE: knife bootstrap did not pull sudo values from Chef::Config,
+ # should we change that for consistency?
+ def sudo_opts
+ return {} if winrm?
+
+ opts = { sudo: false }
+ if config[:use_sudo]
+ opts[:sudo] = true
+ if config[:use_sudo_password]
+ opts[:sudo_password] = config[:connection_password]
+ end
+ if config[:preserve_home]
+ opts[:sudo_options] = "-H"
+ end
+ end
+ opts
+ end
+
+ def winrm_opts
+ return {} unless winrm?
+
+ opts = {
+ winrm_transport: winrm_auth_method, # winrm gem and train calls auth method 'transport'
+ winrm_basic_auth_only: config[:winrm_basic_auth_only] || false,
+ ssl: config[:winrm_ssl] === true,
+ ssl_peer_fingerprint: config[:winrm_ssl_peer_fingerprint],
+ }
+
+ if winrm_auth_method == "kerberos"
+ opts[:kerberos_service] = config[:kerberos_service] if config[:kerberos_service]
+ opts[:kerberos_realm] = config[:kerberos_realm] if config[:kerberos_service]
+ end
+
+ if config[:ca_trust_file]
+ opts[:ca_trust_path] = config[:ca_trust_file]
+ end
+
+ opts[:operation_timeout] = session_timeout
+
+ opts
+ end
+
+ # Config overrides to force password auth.
+ def force_ssh_password_opts(password)
+ {
+ password: password,
+ non_interactive: false,
+ keys_only: false,
+ key_files: [],
+ auth_methods: %i{password keyboard_interactive},
+ }
+ end
+
+ def force_winrm_password_opts(password)
+ {
+ password: password,
+ }
+ end
+
+ # This is for deprecating config options. The fallback_key can be used
+ # to pull an old knife config option out of the config file when the
+ # cli value has been renamed. This is different from the deprecated
+ # cli values, since these are for config options that have no corresponding
+ # cli value.
+ #
+ # DO NOT USE - this whole API is considered deprecated
+ #
+ # @api deprecated
+ #
+ def config_value(key, fallback_key = nil, default = nil)
+ Chef.deprecated(:knife_bootstrap_apis, "Use of config_value is deprecated. Knife plugin authors should access the config hash directly, which does correct merging of cli and config options.")
+ if config.key?(key)
+ # the first key is the primary key so we check the merged hash first
+ config[key]
+ elsif config.key?(fallback_key)
+ # we get the old config option here (the deprecated cli option shouldn't exist)
+ config[fallback_key]
+ else
+ default
+ end
+ end
+
+ def upload_bootstrap(content)
+ script_name = connection.windows? ? "bootstrap.bat" : "bootstrap.sh"
+ remote_path = connection.normalize_path(File.join(connection.temp_dir, script_name))
+ connection.upload_file_content!(content, remote_path)
+ remote_path
+ end
+
+ # build the command string for bootstrapping
+ # @return String
+ def bootstrap_command(remote_path)
+ if connection.windows?
+ "cmd.exe /C #{remote_path}"
+ else
+ cmd = "sh #{remote_path}"
+
+ if config[:su_user]
+ # su - USER is subject to required an interactive console
+ # Otherwise, it will raise: su: must be run from a terminal
+ set_transport_options(pty: true)
+ cmd = "su - #{config[:su_user]} -c '#{cmd}'"
+ cmd = "sudo " << cmd if config[:use_sudo]
+ end
+
+ cmd
+ end
+ end
+
+ private
+
+ # To avoid cluttering the CLI options, some flags (such as port and user)
+ # are shared between protocols. However, there is still a need to allow the operator
+ # to specify defaults separately, since they may not be the same values for different
+ # protocols.
+
+ # These keys are available in Chef::Config, and are prefixed with the protocol name.
+ # For example, :user CLI option will map to :winrm_user and :ssh_user Chef::Config keys,
+ # based on the connection protocol in use.
+
+ # @api private
+ def config_for_protocol(option)
+ if option == :port
+ config[:connection_port] || config[knife_key_for_protocol(option)]
+ else
+ config[:connection_user] || config[knife_key_for_protocol(option)]
+ end
+ end
+
+ # @api private
+ def knife_key_for_protocol(option)
+ "#{connection_protocol}_#{option}".to_sym
+ end
+
+ # True if policy_name and run_list are both given
+ def policyfile_and_run_list_given?
+ run_list_given? && policyfile_options_given?
+ end
+
+ def run_list_given?
+ !config[:run_list].nil? && !config[:run_list].empty?
+ end
+
+ def policyfile_options_given?
+ !!config[:policy_name]
+ end
+
+ # True if one of policy_name or policy_group was given, but not both
+ def incomplete_policyfile_options?
+ (!!config[:policy_name] ^ config[:policy_group])
+ end
+
+ # session_timeout option has a default that may not arrive, particularly if
+ # we're being invoked from a plugin that doesn't merge_config.
+ def session_timeout
+ timeout = config[:session_timeout]
+ return options[:session_timeout][:default] if timeout.nil?
+
+ timeout.to_i
+ end
+
+ # Train::Transports::SSH::Connection#transport_options
+ # Append the options to connection transport_options
+ #
+ # @param opts [Hash] the opts to be added to connection transport_options.
+ # @return [Hash] transport_options if the opts contains any option to be set.
+ #
+ def set_transport_options(opts)
+ return unless opts.is_a?(Hash) || !opts.empty?
+
+ connection&.connection&.transport_options&.merge! opts
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/bootstrap/chef_vault_handler.rb b/knife/lib/chef/knife/bootstrap/chef_vault_handler.rb
new file mode 100644
index 0000000000..d3fff12eec
--- /dev/null
+++ b/knife/lib/chef/knife/bootstrap/chef_vault_handler.rb
@@ -0,0 +1,160 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+class Chef
+ class Knife
+ class Bootstrap < Knife
+ class ChefVaultHandler
+
+ # @return [Hash] knife merged config, typically @config
+ attr_accessor :config
+
+ # @return [Chef::Knife::UI] ui object for output
+ attr_accessor :ui
+
+ # @return [Chef::ApiClient] vault client
+ attr_reader :client
+
+ # @param config [Hash] knife merged config, typically @config
+ # @param ui [Chef::Knife::UI] ui object for output
+ def initialize(config: {}, knife_config: nil, ui: nil)
+ @config = config
+ unless knife_config.nil?
+ @config = knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
+ end
+ @ui = ui
+ end
+
+ # Updates the chef vault items for the newly created client.
+ #
+ # @param client [Chef::ApiClient] vault client
+ def run(client)
+ return unless doing_chef_vault?
+
+ sanity_check
+
+ @client = client
+
+ update_bootstrap_vault_json!
+ end
+
+ # Iterate through all the vault items to update. Items may be either a String
+ # or an Array of Strings:
+ #
+ # {
+ # "vault1": "item",
+ # "vault2": [ "item1", "item2", "item2" ]
+ # }
+ #
+ def update_bootstrap_vault_json!
+ vault_json.each do |vault, items|
+ [ items ].flatten.each do |item|
+ update_vault(vault, item)
+ end
+ end
+ end
+
+ # @return [Boolean] if we've got chef vault options to act on or not
+ def doing_chef_vault?
+ !!(bootstrap_vault_json || bootstrap_vault_file || bootstrap_vault_item)
+ end
+
+ private
+
+ # warn if the user has given mutual conflicting options
+ def sanity_check
+ if bootstrap_vault_item && (bootstrap_vault_json || bootstrap_vault_file)
+ ui.warn "--vault-item given with --vault-list or --vault-file, ignoring the latter"
+ end
+
+ if bootstrap_vault_json && bootstrap_vault_file
+ ui.warn "--vault-list given with --vault-file, ignoring the latter"
+ end
+ end
+
+ # @return [String] string with serialized JSON representing the chef vault items
+ def bootstrap_vault_json
+ config[:bootstrap_vault_json]
+ end
+
+ # @return [String] JSON text in a file representing the chef vault items
+ def bootstrap_vault_file
+ config[:bootstrap_vault_file]
+ end
+
+ # @return [Hash] Ruby object representing the chef vault items to create
+ def bootstrap_vault_item
+ config[:bootstrap_vault_item]
+ end
+
+ # Helper to return a ruby object representing all the data bags and items
+ # to update via chef-vault.
+ #
+ # @return [Hash] deserialized ruby hash with all the vault items
+ def vault_json
+ @vault_json ||=
+ if bootstrap_vault_item
+ bootstrap_vault_item
+ else
+ json = bootstrap_vault_json || File.read(bootstrap_vault_file)
+ Chef::JSONCompat.from_json(json)
+ end
+ end
+
+ # Update an individual vault item and save it
+ #
+ # @param vault [String] name of the chef-vault encrypted data bag
+ # @param item [String] name of the chef-vault encrypted item
+ def update_vault(vault, item)
+ require_chef_vault!
+ bootstrap_vault_item = load_chef_bootstrap_vault_item(vault, item)
+ bootstrap_vault_item.clients(client)
+ bootstrap_vault_item.save
+ end
+
+ # Hook to stub out ChefVault
+ #
+ # @param vault [String] name of the chef-vault encrypted data bag
+ # @param item [String] name of the chef-vault encrypted item
+ # @return [ChefVault::Item] ChefVault::Item object
+ def load_chef_bootstrap_vault_item(vault, item)
+ ChefVault::Item.load(vault, item)
+ end
+
+ public :load_chef_bootstrap_vault_item # for stubbing
+
+ # Helper to very lazily require the chef-vault gem
+ def require_chef_vault!
+ @require_chef_vault ||=
+ begin
+ error_message = "Knife bootstrap requires version 2.6.0 or higher of the chef-vault gem to configure vault items"
+ require "chef-vault"
+ if Gem::Version.new(ChefVault::VERSION) < Gem::Version.new("2.6.0")
+ raise error_message
+ end
+
+ true
+ rescue LoadError
+ raise error_message
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/bootstrap/client_builder.rb b/knife/lib/chef/knife/bootstrap/client_builder.rb
new file mode 100644
index 0000000000..b1e69d90db
--- /dev/null
+++ b/knife/lib/chef/knife/bootstrap/client_builder.rb
@@ -0,0 +1,212 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/node" unless defined?(Chef::Node)
+require "chef/server_api" unless defined?(Chef::ServerAPI)
+require "chef/api_client" unless defined?(Chef::APIClient)
+require "chef/api_client/registration" unless defined?(Chef::APIClient::Registration)
+require "tmpdir" unless defined?(Dir.mktmpdir)
+
+class Chef
+ class Knife
+ class Bootstrap < Knife
+ class ClientBuilder
+
+ # @return [Hash] knife merged config, typically @config
+ attr_accessor :config
+ # @return [Hash] chef config object
+ attr_accessor :chef_config
+ # @return [Chef::Knife::UI] ui object for output
+ attr_accessor :ui
+ # @return [Chef::ApiClient] client saved on run
+ attr_reader :client
+
+ # @param config [Hash] Hash of knife config settings
+ # @param chef_config [Hash] Hash of chef config settings
+ # @param ui [Chef::Knife::UI] UI object for output
+ def initialize(config: {}, knife_config: nil, chef_config: {}, ui: nil)
+ @config = config
+ unless knife_config.nil?
+ @config = knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
+ end
+ @chef_config = chef_config
+ @ui = ui
+ end
+
+ # Main entry. Prompt the user to clean up any old client or node objects. Then create
+ # the new client, then create the new node.
+ def run
+ sanity_check
+
+ ui.info("Creating new client for #{node_name}")
+
+ @client = create_client!
+
+ ui.info("Creating new node for #{node_name}")
+
+ create_node!
+ end
+
+ # Tempfile to use to write newly created client credentials to.
+ #
+ # This method is public so that the knife bootstrapper can read then and pass the value into
+ # the handler for chef vault which needs the client cert we create here.
+ #
+ # We hang onto the tmpdir as an ivar as well so that it will not get GC'd and removed
+ #
+ # @return [String] path to the generated client.pem
+ def client_path
+ @client_path ||=
+ begin
+ @tmpdir = Dir.mktmpdir
+ File.join(@tmpdir, "#{node_name}.pem")
+ end
+ end
+
+ private
+
+ # @return [String] node name from the config
+ def node_name
+ config[:chef_node_name]
+ end
+
+ # @return [String] environment from the config
+ def environment
+ config[:environment]
+ end
+
+ # @return [String] run_list from the config
+ def run_list
+ config[:run_list]
+ end
+
+ # @return [String] policy_name from the config
+ def policy_name
+ config[:policy_name]
+ end
+
+ # @return [String] policy_group from the config
+ def policy_group
+ config[:policy_group]
+ end
+
+ # @return [Hash,Array] Object representation of json first-boot attributes from the config
+ def first_boot_attributes
+ config[:first_boot_attributes]
+ end
+
+ # @return [String] chef server url from the Chef::Config
+ def chef_server_url
+ chef_config[:chef_server_url]
+ end
+
+ # Accesses the run_list and coerces it into an Array, changing nils into
+ # the empty Array, and splitting strings representations of run_lists into
+ # Arrays.
+ #
+ # @return [Array] run_list coerced into an array
+ def normalized_run_list
+ case run_list
+ when nil
+ []
+ when String
+ run_list.split(/\s*,\s*/)
+ when Array
+ run_list
+ end
+ end
+
+ # Create the client object and save it to the Chef API
+ def create_client!
+ Chef::ApiClient::Registration.new(node_name, client_path, http_api: rest).run
+ end
+
+ # Create the node object (via the lazy accessor) and save it to the Chef API
+ def create_node!
+ node.save
+ end
+
+ # Create a new Chef::Node. Supports creating the node with its name, run_list, attributes
+ # and environment. This injects a rest object into the Chef::Node which uses the client key
+ # for authentication so that the client creates the node and therefore we get the acls setup
+ # correctly.
+ #
+ # @return [Chef::Node] new chef node to create
+ def node
+ @node ||=
+ begin
+ node = Chef::Node.new(chef_server_rest: client_rest)
+ node.name(node_name)
+ node.run_list(normalized_run_list)
+ node.normal_attrs = first_boot_attributes if first_boot_attributes
+ node.environment(environment) if environment
+ node.policy_name = policy_name if policy_name
+ node.policy_group = policy_group if policy_group
+ (config[:tags] || []).each do |tag|
+ node.tags << tag
+ end
+ node
+ end
+ end
+
+ # Check for the existence of a node and/or client already on the server. If the node
+ # already exists, we must delete it in order to proceed so that we can create a new node
+ # object with the permissions of the new client. There is a use case for creating a new
+ # client and wiring it up to a precreated node object, but we do currently support that.
+ #
+ # We prompt the user about what to do and will fail hard if we do not get confirmation to
+ # delete any prior node/client objects.
+ def sanity_check
+ if resource_exists?("nodes/#{node_name}")
+ ui.confirm("Node #{node_name} exists, overwrite it")
+ rest.delete("nodes/#{node_name}")
+ end
+ if resource_exists?("clients/#{node_name}")
+ ui.confirm("Client #{node_name} exists, overwrite it")
+ rest.delete("clients/#{node_name}")
+ end
+ end
+
+ # Check if an relative path exists on the chef server
+ #
+ # @param relative_path [String] URI path relative to the chef organization
+ # @return [Boolean] if the relative path exists or returns a 404
+ def resource_exists?(relative_path)
+ rest.get(relative_path)
+ true
+ rescue Net::HTTPClientException => e
+ raise unless e.response.code == "404"
+
+ false
+ end
+
+ # @return [Chef::ServerAPI] REST client using the client credentials
+ def client_rest
+ @client_rest ||= Chef::ServerAPI.new(chef_server_url, client_name: node_name, signing_key_filename: client_path)
+ end
+
+ # @return [Chef::ServerAPI] REST client using the cli user's knife credentials
+ # this uses the users's credentials
+ def rest
+ @rest ||= Chef::ServerAPI.new(chef_server_url)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/bootstrap/templates/README.md b/knife/lib/chef/knife/bootstrap/templates/README.md
index 7f28f8f40f..7f28f8f40f 100644
--- a/lib/chef/knife/bootstrap/templates/README.md
+++ b/knife/lib/chef/knife/bootstrap/templates/README.md
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/knife/lib/chef/knife/bootstrap/templates/chef-full.erb
index 2e0c80eaef..2e0c80eaef 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/knife/lib/chef/knife/bootstrap/templates/chef-full.erb
diff --git a/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb b/knife/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb
index 7aa7be49f8..7aa7be49f8 100644
--- a/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb
+++ b/knife/lib/chef/knife/bootstrap/templates/windows-chef-client-msi.erb
diff --git a/knife/lib/chef/knife/bootstrap/train_connector.rb b/knife/lib/chef/knife/bootstrap/train_connector.rb
new file mode 100644
index 0000000000..f155e32225
--- /dev/null
+++ b/knife/lib/chef/knife/bootstrap/train_connector.rb
@@ -0,0 +1,334 @@
+# Copyright:: Copyright (c) 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 "train"
+require "tempfile" unless defined?(Tempfile)
+require "uri" unless defined?(URI)
+require "securerandom" unless defined?(SecureRandom)
+
+class Chef
+ class Knife
+ class Bootstrap < Knife
+ class TrainConnector
+ SSH_CONFIG_OVERRIDE_KEYS ||= %i{user port proxy}.freeze
+
+ MKTEMP_WIN_COMMAND ||= <<~EOM.freeze
+ $parent = [System.IO.Path]::GetTempPath();
+ [string] $name = [System.Guid]::NewGuid();
+ $tmp = New-Item -ItemType Directory -Path (Join-Path $parent $name);
+ $tmp.FullName
+ EOM
+
+ DEFAULT_REMOTE_TEMP ||= "/tmp".freeze
+
+ def initialize(host_url, default_protocol, opts)
+ @host_url = host_url
+ @default_protocol = default_protocol
+ @opts_in = opts
+ end
+
+ def config
+ @config ||= begin
+ uri_opts = opts_from_uri(@host_url, @default_protocol)
+ transport_config(@host_url, @opts_in.merge(uri_opts))
+ end
+ end
+
+ def connection
+ @connection ||= begin
+ Train.validate_backend(config)
+ train = Train.create(config[:backend], config)
+ # Note that the train connection is not currently connected
+ # to the remote host, but it's ready to go.
+ train.connection
+ end
+ end
+
+ #
+ # Establish a connection to the configured host.
+ #
+ # @raise [TrainError]
+ # @raise [TrainUserError]
+ #
+ # @return [TrueClass] true if the connection could be established.
+ def connect!
+ # Force connection to establish
+ connection.wait_until_ready
+ true
+ end
+
+ #
+ # @return [String] the configured hostname
+ def hostname
+ config[:host]
+ end
+
+ # Answers the question, "is this connection configured for password auth?"
+ # @return [Boolean] true if the connection is configured with password auth
+ def password_auth?
+ config.key? :password
+ end
+
+ # Answers the question, "Am I connected to a linux host?"
+ #
+ # @return [Boolean] true if the connected host is linux.
+ def linux?
+ connection.platform.linux?
+ end
+
+ # Answers the question, "Am I connected to a unix host?"
+ #
+ # @note this will always return true for a linux host
+ # because train classifies linux as a unix
+ #
+ # @return [Boolean] true if the connected host is unix or linux
+ def unix?
+ connection.platform.unix?
+ end
+
+ #
+ # Answers the question, "Am I connected to a Windows host?"
+ #
+ # @return [Boolean] true if the connected host is Windows
+ def windows?
+ connection.platform.windows?
+ end
+
+ #
+ # Creates a temporary directory on the remote host if it
+ # hasn't already. Caches directory location. For *nix,
+ # it will ensure that the directory is owned by the logged-in user
+ #
+ # @return [String] the temporary path created on the remote host.
+ def temp_dir
+ @tmpdir ||= if windows?
+ run_command!(MKTEMP_WIN_COMMAND).stdout.split.last
+ else
+ # Get a 6 chars string using secure random
+ # eg. /tmp/chef_XXXXXX.
+ # Use mkdir to create TEMP dir to get rid of mktemp
+ dir = "#{DEFAULT_REMOTE_TEMP}/chef_#{SecureRandom.alphanumeric(6)}"
+ run_command!("mkdir -p '#{dir}'")
+ # Ensure that dir has the correct owner. We are possibly
+ # running with sudo right now - so this directory would be owned by root.
+ # File upload is performed over SCP as the current logged-in user,
+ # so we'll set ownership to ensure that works.
+ run_command!("chown #{config[:user]} '#{dir}'") if config[:sudo]
+
+ dir
+ end
+ end
+
+ #
+ # Uploads a file from "local_path" to "remote_path"
+ #
+ # @param local_path [String] The path to a file on the local file system
+ # @param remote_path [String] The destination path on the remote file system.
+ # @return NilClass
+ def upload_file!(local_path, remote_path)
+ connection.upload(local_path, remote_path)
+ nil
+ end
+
+ #
+ # Uploads the provided content into the file "remote_path" on the remote host.
+ #
+ # @param content [String] The content to upload into remote_path
+ # @param remote_path [String] The destination path on the remote file system.
+ # @return NilClass
+ def upload_file_content!(content, remote_path)
+ t = Tempfile.new("chef-content")
+ t.binmode
+ t << content
+ t.close
+ upload_file!(t.path, remote_path)
+ nil
+ ensure
+ t.close
+ t.unlink
+ end
+
+ #
+ # Force-deletes the file at "path" from the remote host.
+ #
+ # @param path [String] The path of the file on the remote host
+ def del_file!(path)
+ if windows?
+ run_command!("If (Test-Path \"#{path}\") { Remove-Item -Force -Path \"#{path}\" }")
+ else
+ run_command!("rm -f \"#{path}\"")
+ end
+ nil
+ end
+
+ #
+ # normalizes path across OS's - always use forward slashes, which
+ # Windows and *nix understand.
+ #
+ # @param path [String] The path to normalize
+ #
+ # @return [String] the normalized path
+ def normalize_path(path)
+ path.tr("\\", "/")
+ end
+
+ #
+ # Runs a command on the remote host.
+ #
+ # @param command [String] The command to run.
+ # @param data_handler [Proc] An optional block. When provided, inbound data will be
+ # published via `data_handler.call(data)`. This can allow
+ # callers to receive and render updates from remote command execution.
+ #
+ # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
+ def run_command(command, &data_handler)
+ connection.run_command(command, &data_handler)
+ end
+
+ #
+ # Runs a command the remote host
+ #
+ # @param command [String] The command to run.
+ # @param data_handler [Proc] An optional block. When provided, inbound data will be
+ # published via `data_handler.call(data)`. This can allow
+ # callers to receive and render updates from remote command execution.
+ #
+ # @raise Chef::Knife::Bootstrap::RemoteExecutionFailed if an error occurs (non-zero exit status)
+ # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
+ def run_command!(command, &data_handler)
+ result = run_command(command, &data_handler)
+ if result.exit_status != 0
+ raise RemoteExecutionFailed.new(hostname, command, result)
+ end
+
+ result
+ end
+
+ private
+
+ # For a given url and set of options, create a config
+ # hash suitable for passing into train.
+ def transport_config(host_url, opts_in)
+ # These baseline opts are not protocol-specific
+ opts = { target: host_url,
+ www_form_encoded_password: true,
+ transport_retries: 2,
+ transport_retry_sleep: 1,
+ backend: opts_in[:backend],
+ logger: opts_in[:logger] }
+
+ # Accepts options provided by caller if they're not already configured,
+ # but note that they will be constrained to valid options for the backend protocol
+ opts.merge!(opts_from_caller(opts, opts_in))
+
+ # WinRM has some additional computed options
+ opts.merge!(opts_inferred_from_winrm(opts, opts_in))
+
+ # Now that everything is populated, fill in anything missing
+ # that may be found in user ssh config
+ opts.merge!(missing_opts_from_ssh_config(opts, opts_in))
+
+ Train.target_config(opts)
+ end
+
+ # Some winrm options are inferred based on other options.
+ # Return a hash of winrm options based on configuration already built.
+ def opts_inferred_from_winrm(config, opts_in)
+ return {} unless config[:backend] == "winrm"
+
+ opts_out = {}
+
+ if opts_in[:ssl]
+ opts_out[:ssl] = true
+ opts_out[:self_signed] = opts_in[:self_signed] || false
+ end
+
+ # See note here: https://github.com/mwrock/WinRM#example
+ if %w{ssl plaintext}.include?(opts_in[:winrm_auth_method])
+ opts_out[:winrm_disable_sspi] = true
+ end
+ opts_out
+ end
+
+ # Returns a hash containing valid options for the current
+ # transport protocol that are not already present in config
+ def opts_from_caller(config, opts_in)
+ # Train.options gives us the supported config options for the
+ # backend provider (ssh, winrm). We'll use that
+ # to filter out options that don't belong
+ # to the transport type we're using.
+ valid_opts = Train.options(config[:backend])
+ opts_in.select do |key, _v|
+ valid_opts.key?(key) && !config.key?(key)
+ end
+ end
+
+ # Extract any of username/password/host/port/transport
+ # that are in the URI and return them as a config has
+ def opts_from_uri(uri, default_protocol)
+ # Train.unpack_target_from_uri only works for complete URIs in
+ # form of proto://[user[:pass]@]host[:port]/
+ # So we'll add the protocol prefix if it's not supplied.
+ uri_to_check = if URI::DEFAULT_PARSER.make_regexp.match(uri)
+ uri
+ else
+ "#{default_protocol}://#{uri}"
+ end
+
+ Train.unpack_target_from_uri(uri_to_check)
+ end
+
+ # This returns a hash that consists of settings
+ # populated from SSH configuration that are not already present
+ # in the configuration passed in.
+ # This is necessary because train will default these values
+ # itself - causing SSH config data to be ignored
+ def missing_opts_from_ssh_config(config, opts_in)
+ return {} unless config[:backend] == "ssh"
+
+ host_cfg = ssh_config_for_host(config[:host])
+ opts_out = {}
+ opts_in.each do |key, _value|
+ if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key)
+ opts_out[key] = host_cfg[key]
+ end
+ end
+ opts_out
+ end
+
+ # Having this as a method makes it easier to mock
+ # SSH Config for testing.
+ def ssh_config_for_host(host)
+ require "net/ssh" unless defined?(Net::SSH)
+ Net::SSH::Config.for(host)
+ end
+ end
+
+ class RemoteExecutionFailed < StandardError
+ attr_reader :exit_status, :command, :hostname, :stdout, :stderr
+
+ def initialize(hostname, command, result)
+ @hostname = hostname
+ @exit_status = result.exit_status
+ @stderr = result.stderr
+ @stdout = result.stdout
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_bulk_delete.rb b/knife/lib/chef/knife/client_bulk_delete.rb
new file mode 100644
index 0000000000..cc200a8bee
--- /dev/null
+++ b/knife/lib/chef/knife/client_bulk_delete.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientBulkDelete < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ option :delete_validators,
+ short: "-D",
+ long: "--delete-validators",
+ description: "Force deletion of clients if they're validators."
+
+ banner "knife client bulk delete REGEX (options)"
+
+ def run
+ if name_args.length < 1
+ ui.fatal("You must supply a regular expression to match the results against")
+ exit 42
+ end
+ all_clients = Chef::ApiClientV1.list(true)
+
+ matcher = /#{name_args[0]}/
+ clients_to_delete = {}
+ validators_to_delete = {}
+ all_clients.each do |name, client|
+ next unless name&.match?(matcher)
+
+ if client.validator
+ validators_to_delete[client.name] = client
+ else
+ clients_to_delete[client.name] = client
+ end
+ end
+
+ if clients_to_delete.empty? && validators_to_delete.empty?
+ ui.info "No clients match the expression /#{name_args[0]}/"
+ exit 0
+ end
+
+ check_and_delete_validators(validators_to_delete)
+ check_and_delete_clients(clients_to_delete)
+ end
+
+ def check_and_delete_validators(validators)
+ unless validators.empty?
+ unless config[:delete_validators]
+ ui.msg("The following clients are validators and will not be deleted:")
+ print_clients(validators)
+ ui.msg("You must specify --delete-validators to delete the validator clients")
+ else
+ ui.msg("The following validators will be deleted:")
+ print_clients(validators)
+ if ui.confirm_without_exit("Are you sure you want to delete these validators")
+ destroy_clients(validators)
+ end
+ end
+ end
+ end
+
+ def check_and_delete_clients(clients)
+ unless clients.empty?
+ ui.msg("The following clients will be deleted:")
+ print_clients(clients)
+ ui.confirm("Are you sure you want to delete these clients")
+ destroy_clients(clients)
+ end
+ end
+
+ def destroy_clients(clients)
+ clients.sort.each do |name, client|
+ client.destroy
+ ui.msg("Deleted client #{name}")
+ end
+ end
+
+ def print_clients(clients)
+ ui.msg("")
+ ui.msg(ui.list(clients.keys.sort, :columns_down))
+ ui.msg("")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_create.rb b/knife/lib/chef/knife/client_create.rb
new file mode 100644
index 0000000000..c79ff25d5e
--- /dev/null
+++ b/knife/lib/chef/knife/client_create.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class ClientCreate < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the private key to a file if the #{ChefUtils::Dist::Server::PRODUCT} generated one."
+
+ option :validator,
+ long: "--validator",
+ description: "Create the client as a validator.",
+ boolean: true
+
+ option :public_key,
+ short: "-p FILE",
+ long: "--public-key",
+ description: "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ short: "-k",
+ long: "--prevent-keygen",
+ description: "Prevent #{ChefUtils::Dist::Server::PRODUCT} from generating a default key pair for you. Cannot be passed with --public-key.",
+ boolean: true
+
+ banner "knife client create CLIENTNAME (options)"
+
+ def client
+ @client_field ||= Chef::ApiClientV1.new
+ end
+
+ def create_client(client)
+ # should not be using save :( bad behavior
+ Chef::ApiClientV1.from_hash(client).save
+ end
+
+ def run
+ test_mandatory_field(@name_args[0], "client name")
+ client.name @name_args[0]
+
+ if config[:public_key] && config[:prevent_keygen]
+ show_usage
+ ui.fatal("You cannot pass --public-key and --prevent-keygen")
+ exit 1
+ end
+
+ if !config[:prevent_keygen] && !config[:public_key]
+ client.create_key(true)
+ end
+
+ if config[:validator]
+ client.validator(true)
+ end
+
+ if config[:public_key]
+ client.public_key File.read(File.expand_path(config[:public_key]))
+ end
+
+ output = edit_hash(client)
+ final_client = create_client(output)
+ ui.info("Created #{final_client}")
+
+ # output private_key if one
+ if final_client.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(final_client.private_key)
+ end
+ else
+ puts final_client.private_key
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_delete.rb b/knife/lib/chef/knife/client_delete.rb
new file mode 100644
index 0000000000..874f2ba642
--- /dev/null
+++ b/knife/lib/chef/knife/client_delete.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientDelete < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ option :delete_validators,
+ short: "-D",
+ long: "--delete-validators",
+ description: "Force deletion of client if it's a validator."
+
+ banner "knife client delete [CLIENT [CLIENT]] (options)"
+
+ def run
+ if @name_args.length == 0
+ show_usage
+ ui.fatal("You must specify at least one client name")
+ exit 1
+ end
+
+ @name_args.each do |client_name|
+ delete_client(client_name)
+ end
+ end
+
+ def delete_client(client_name)
+ delete_object(Chef::ApiClientV1, client_name, "client") do
+ object = Chef::ApiClientV1.load(client_name)
+ if object.validator
+ unless config[:delete_validators]
+ ui.fatal("You must specify --delete-validators to delete the validator client #{client_name}")
+ exit 2
+ end
+ end
+ object.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_edit.rb b/knife/lib/chef/knife/client_edit.rb
new file mode 100644
index 0000000000..4f58228901
--- /dev/null
+++ b/knife/lib/chef/knife/client_edit.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientEdit < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ banner "knife client edit CLIENT (options)"
+
+ def run
+ @client_name = @name_args[0]
+
+ if @client_name.nil?
+ show_usage
+ ui.fatal("You must specify a client name")
+ exit 1
+ end
+
+ original_data = Chef::ApiClientV1.load(@client_name).to_h
+ edited_client = edit_hash(original_data)
+ if original_data != edited_client
+ client = Chef::ApiClientV1.from_hash(edited_client)
+ client.save
+ ui.msg("Saved #{client}.")
+ else
+ ui.msg("Client unchanged, not saving.")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_create.rb b/knife/lib/chef/knife/client_key_create.rb
index 192d724473..192d724473 100644
--- a/lib/chef/knife/client_key_create.rb
+++ b/knife/lib/chef/knife/client_key_create.rb
diff --git a/lib/chef/knife/client_key_delete.rb b/knife/lib/chef/knife/client_key_delete.rb
index 2d486ffcbd..2d486ffcbd 100644
--- a/lib/chef/knife/client_key_delete.rb
+++ b/knife/lib/chef/knife/client_key_delete.rb
diff --git a/lib/chef/knife/client_key_edit.rb b/knife/lib/chef/knife/client_key_edit.rb
index d178aafc17..d178aafc17 100644
--- a/lib/chef/knife/client_key_edit.rb
+++ b/knife/lib/chef/knife/client_key_edit.rb
diff --git a/lib/chef/knife/client_key_list.rb b/knife/lib/chef/knife/client_key_list.rb
index afc04335d9..afc04335d9 100644
--- a/lib/chef/knife/client_key_list.rb
+++ b/knife/lib/chef/knife/client_key_list.rb
diff --git a/lib/chef/knife/client_key_show.rb b/knife/lib/chef/knife/client_key_show.rb
index 14e1f0ca7a..14e1f0ca7a 100644
--- a/lib/chef/knife/client_key_show.rb
+++ b/knife/lib/chef/knife/client_key_show.rb
diff --git a/knife/lib/chef/knife/client_list.rb b/knife/lib/chef/knife/client_list.rb
new file mode 100644
index 0000000000..f4a4c7e9ad
--- /dev/null
+++ b/knife/lib/chef/knife/client_list.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientList < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ banner "knife client list (options)"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ output(format_list_for_display(Chef::ApiClientV1.list))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_reregister.rb b/knife/lib/chef/knife/client_reregister.rb
new file mode 100644
index 0000000000..3408392d95
--- /dev/null
+++ b/knife/lib/chef/knife/client_reregister.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientReregister < Knife
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ banner "knife client reregister CLIENT (options)"
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the key to a file."
+
+ def run
+ @client_name = @name_args[0]
+
+ if @client_name.nil?
+ show_usage
+ ui.fatal("You must specify a client name")
+ exit 1
+ end
+
+ client = Chef::ApiClientV1.reregister(@client_name)
+ Chef::Log.trace("Updated client data: #{client.inspect}")
+ key = client.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/client_show.rb b/knife/lib/chef/knife/client_show.rb
new file mode 100644
index 0000000000..102ff2c4cc
--- /dev/null
+++ b/knife/lib/chef/knife/client_show.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class ClientShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require "chef/api_client_v1" unless defined?(Chef::ApiClientV1)
+ end
+
+ banner "knife client show CLIENT (options)"
+
+ def run
+ @client_name = @name_args[0]
+
+ if @client_name.nil?
+ show_usage
+ ui.fatal("You must specify a client name")
+ exit 1
+ end
+
+ client = Chef::ApiClientV1.load(@client_name)
+ output(format_for_display(client))
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/config_get.rb b/knife/lib/chef/knife/config_get.rb
index 91e6b7affd..91e6b7affd 100644
--- a/lib/chef/knife/config_get.rb
+++ b/knife/lib/chef/knife/config_get.rb
diff --git a/lib/chef/knife/config_get_profile.rb b/knife/lib/chef/knife/config_get_profile.rb
index a355c531fe..a355c531fe 100644
--- a/lib/chef/knife/config_get_profile.rb
+++ b/knife/lib/chef/knife/config_get_profile.rb
diff --git a/knife/lib/chef/knife/config_list.rb b/knife/lib/chef/knife/config_list.rb
new file mode 100644
index 0000000000..be80ded3b2
--- /dev/null
+++ b/knife/lib/chef/knife/config_list.rb
@@ -0,0 +1,139 @@
+#
+# Copyright:: Copyright (c) 2018, Noah Kantrowitz
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class ConfigList < Knife
+ banner "knife config list (options)"
+
+ TABLE_HEADER ||= [" Profile", "Client", "Key", "Server"].freeze
+
+ deps do
+ require "chef/workstation_config_loader" unless defined?(Chef::WorkstationConfigLoader)
+ require "tty-screen" unless defined?(TTY::Screen)
+ require "tty-table" unless defined?(TTY::Table)
+ end
+
+ option :ignore_knife_rb,
+ short: "-i",
+ long: "--ignore-knife-rb",
+ description: "Ignore the current config.rb/knife.rb configuration.",
+ default: false
+
+ def configure_chef
+ apply_computed_config
+ end
+
+ def run
+ credentials_data = self.class.config_loader.parse_credentials_file
+ if credentials_data.nil? || credentials_data.empty?
+ # Should this just show the ambient knife.rb config as "default" instead?
+ ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty")
+ exit 1
+ end
+
+ current_profile = self.class.config_loader.credentials_profile(config[:profile])
+ profiles = credentials_data.keys.map do |profile|
+ if config[:ignore_knife_rb]
+ # Don't do any fancy loading nonsense, just the raw data.
+ profile_data = credentials_data[profile]
+ {
+ profile: profile,
+ active: profile == current_profile,
+ client_name: profile_data["client_name"] || profile_data["node_name"],
+ client_key: profile_data["client_key"],
+ server_url: profile_data["chef_server_url"],
+ }
+ else
+ # Fancy loading nonsense so we get what the actual config would be.
+ # Note that this modifies the global config, after this, all bets are
+ # off as to whats in the config.
+ Chef::Config.reset
+ wcl = Chef::WorkstationConfigLoader.new(nil, Chef::Log, profile: profile)
+ wcl.load
+ {
+ profile: profile,
+ active: profile == current_profile,
+ client_name: Chef::Config[:node_name],
+ client_key: Chef::Config[:client_key],
+ server_url: Chef::Config[:chef_server_url],
+ }
+ end
+ end
+
+ # Try to reset the config.
+ unless config[:ignore_knife_rb]
+ Chef::Config.reset
+ apply_computed_config
+ end
+
+ if ui.interchange?
+ # Machine-readable output.
+ ui.output(profiles)
+ else
+ # Table output.
+ ui.output(render_table(profiles))
+ end
+ end
+
+ private
+
+ def render_table(profiles, padding: 1)
+ rows = []
+ # Render the data to a 2D array that will be used for the table.
+ profiles.each do |profile|
+ # Replace the home dir in the client key path with ~.
+ profile[:client_key] = profile[:client_key].to_s.gsub(/^#{Regexp.escape(Dir.home)}/, "~") if profile[:client_key]
+ profile[:profile] = "#{profile[:active] ? "*" : " "}#{profile[:profile]}"
+ rows << profile.values_at(:profile, :client_name, :client_key, :server_url)
+ end
+
+ table = TTY::Table.new(header: TABLE_HEADER, rows: rows)
+
+ # Rotate the table to vertical if the screen width is less than table width.
+ if table.width > TTY::Screen.width
+ table.orientation = :vertical
+ table.rotate
+ # Add a new line after each profile record.
+ table.render do |renderer|
+ renderer.border do
+ separator ->(row) { (row + 1) % TABLE_HEADER.size == 0 }
+ end
+ # Remove the leading space added of the first column.
+ renderer.filter = Proc.new do |val, row_index, col_index|
+ if col_index == 1 || (row_index) % TABLE_HEADER.size == 0
+ val.strip
+ else
+ val
+ end
+ end
+ end
+ else
+ table.render do |renderer|
+ renderer.border do
+ mid "-"
+ end
+ renderer.padding = [0, padding, 0, 0] # pad right with 2 characters
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/config_list_profiles.rb b/knife/lib/chef/knife/config_list_profiles.rb
index c037b0de53..c037b0de53 100644
--- a/lib/chef/knife/config_list_profiles.rb
+++ b/knife/lib/chef/knife/config_list_profiles.rb
diff --git a/lib/chef/knife/config_show.rb b/knife/lib/chef/knife/config_show.rb
index 7f28891885..7f28891885 100644
--- a/lib/chef/knife/config_show.rb
+++ b/knife/lib/chef/knife/config_show.rb
diff --git a/lib/chef/knife/config_use.rb b/knife/lib/chef/knife/config_use.rb
index e944dc210b..e944dc210b 100644
--- a/lib/chef/knife/config_use.rb
+++ b/knife/lib/chef/knife/config_use.rb
diff --git a/lib/chef/knife/config_use_profile.rb b/knife/lib/chef/knife/config_use_profile.rb
index 169bdbef30..169bdbef30 100644
--- a/lib/chef/knife/config_use_profile.rb
+++ b/knife/lib/chef/knife/config_use_profile.rb
diff --git a/knife/lib/chef/knife/configure.rb b/knife/lib/chef/knife/configure.rb
new file mode 100644
index 0000000000..9c806b4af6
--- /dev/null
+++ b/knife/lib/chef/knife/configure.rb
@@ -0,0 +1,150 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class Configure < Knife
+ attr_reader :chef_server, :new_client_name, :admin_client_name, :admin_client_key
+ attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key
+
+ deps do
+ require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+ require_relative "client_create"
+ require_relative "user_create"
+ Chef::Knife::ClientCreate.load_deps
+ Chef::Knife::UserCreate.load_deps
+ end
+
+ banner "knife configure (options)"
+
+ option :repository,
+ short: "-r REPO",
+ long: "--repository REPO",
+ description: "The path to the chef-repo."
+
+ option :initial,
+ short: "-i",
+ long: "--initial",
+ boolean: true,
+ description: "Use to create a API client, typically an administrator client on a freshly-installed server."
+
+ option :admin_client_name,
+ long: "--admin-client-name NAME",
+ description: "The name of the client, typically the name of the admin client."
+
+ option :admin_client_key,
+ long: "--admin-client-key PATH",
+ description: "The path to the private key used by the client, typically a file named admin.pem."
+
+ option :validation_client_name,
+ long: "--validation-client-name NAME",
+ description: "The name of the validation client, typically a client named chef-validator."
+
+ option :validation_key,
+ long: "--validation-key PATH",
+ description: "The path to the validation key used by the client, typically a file named validation.pem."
+
+ def configure_chef
+ # We are just faking out the system so that you can do this without a key specified
+ Chef::Config[:node_name] = "woot"
+ super
+ Chef::Config[:node_name] = nil
+ end
+
+ def run
+ FileUtils.mkdir_p(chef_config_path)
+
+ ask_user_for_config
+
+ confirm("Overwrite #{config_file_path}") if ::File.exist?(config_file_path)
+
+ ::File.open(config_file_path, "w") do |f|
+ f.puts <<~EOH
+ [default]
+ client_name = '#{new_client_name}'
+ client_key = '#{new_client_key}'
+ chef_server_url = '#{chef_server}'
+ EOH
+ end
+
+ if config[:initial]
+ ui.msg("Creating initial API user...")
+ Chef::Config[:chef_server_url] = chef_server
+ Chef::Config[:node_name] = admin_client_name
+ Chef::Config[:client_key] = admin_client_key
+ user_create = Chef::Knife::UserCreate.new
+ user_create.name_args = [ new_client_name ]
+ user_create.config[:user_password] = config[:user_password] ||
+ ui.ask("Please enter a password for the new user: ", echo: false)
+ user_create.config[:admin] = true
+ user_create.config[:file] = new_client_key
+ user_create.config[:yes] = true
+ user_create.config[:disable_editing] = true
+ user_create.run
+ else
+ ui.msg("*****")
+ ui.msg("")
+ ui.msg("You must place your client key in:")
+ ui.msg(" #{new_client_key}")
+ ui.msg("Before running commands with Knife")
+ ui.msg("")
+ ui.msg("*****")
+ end
+
+ ui.msg("Knife configuration file written to #{config_file_path}")
+ end
+
+ def ask_user_for_config
+ server_name = guess_servername
+ @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", default: "https://#{server_name}/organizations/myorg")
+ if config[:initial]
+ @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", default: Etc.getlogin)
+ @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", default: "admin")
+ @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", default: "#{ChefUtils::Dist::Server::CONF_DIR}/admin.pem")
+ @admin_client_key = File.expand_path(@admin_client_key)
+ else
+ @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", default: Etc.getlogin)
+ end
+
+ @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem")
+ @new_client_key = File.expand_path(@new_client_key)
+ end
+
+ # @return [String] our best guess at what the servername should be using Ohai data and falling back to localhost
+ def guess_servername
+ require "ohai" unless defined?(Ohai::System)
+ o = Ohai::System.new
+ o.all_plugins(%w{ os hostname fqdn })
+ o[:fqdn] || o[:machinename] || o[:hostname] || "localhost"
+ end
+
+ # @return [String] the path to the user's .chef directory
+ def chef_config_path
+ @chef_config_path ||= ChefConfig::PathHelper.home(".chef")
+ end
+
+ # @return [String] the full path to the config file (credential file)
+ def config_file_path
+ @config_file_path ||= ::File.expand_path(::File.join(chef_config_path, "credentials"))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/configure_client.rb b/knife/lib/chef/knife/configure_client.rb
index c6f159ec8f..c6f159ec8f 100644
--- a/lib/chef/knife/configure_client.rb
+++ b/knife/lib/chef/knife/configure_client.rb
diff --git a/knife/lib/chef/knife/cookbook_bulk_delete.rb b/knife/lib/chef/knife/cookbook_bulk_delete.rb
new file mode 100644
index 0000000000..d294db842c
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_bulk_delete.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookBulkDelete < Knife
+
+ deps do
+ require_relative "cookbook_delete"
+ require "chef/cookbook_version" unless defined?(Chef::CookbookVersion)
+ end
+
+ option :purge, short: "-p", long: "--purge", boolean: true, description: "Permanently remove files from backing data store."
+
+ banner "knife cookbook bulk delete REGEX (options)"
+
+ def run
+ unless regex_str = @name_args.first
+ ui.fatal("You must supply a regular expression to match the results against")
+ exit 42
+ end
+
+ regex = Regexp.new(regex_str)
+
+ all_cookbooks = Chef::CookbookVersion.list
+ cookbooks_names = all_cookbooks.keys.grep(regex)
+ cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name]; hash }
+ ui.msg "All versions of the following cookbooks will be deleted:"
+ ui.msg ""
+ ui.msg ui.list(cookbooks_to_delete.keys.sort, :columns_down)
+ ui.msg ""
+
+ unless config[:yes]
+ ui.confirm("Do you really want to delete these cookbooks")
+
+ if config[:purge]
+ ui.msg("Files that are common to multiple cookbooks are shared, so purging the files may break other cookbooks.")
+ ui.confirm("Are you sure you want to purge files instead of just deleting the cookbooks")
+ end
+ ui.msg ""
+ end
+
+ cookbooks_names.each do |cookbook_name|
+ versions = rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map { |v| v["version"] }.flatten
+ versions.each do |version|
+ rest.delete("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}")
+ ui.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/cookbook_delete.rb b/knife/lib/chef/knife/cookbook_delete.rb
new file mode 100644
index 0000000000..fac23ae336
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_delete.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookDelete < Knife
+
+ attr_accessor :cookbook_name, :version
+
+ deps do
+ require "chef/cookbook_version" unless defined?(Chef::CookbookVersion)
+ end
+
+ option :all, short: "-a", long: "--all", boolean: true, description: "Delete all versions of the cookbook."
+
+ option :purge, short: "-p", long: "--purge", boolean: true, description: "Permanently remove files from backing data store."
+
+ banner "knife cookbook delete COOKBOOK VERSION (options)"
+
+ def run
+ confirm("Files that are common to multiple cookbooks are shared, so purging the files may disable other cookbooks. Are you sure you want to purge files instead of just deleting the cookbook") if config[:purge]
+ @cookbook_name, @version = name_args
+ if @cookbook_name && @version
+ delete_explicit_version
+ elsif @cookbook_name && config[:all]
+ delete_all_versions
+ elsif @cookbook_name && @version.nil?
+ delete_without_explicit_version
+ elsif @cookbook_name.nil?
+ show_usage
+ ui.fatal("You must provide the name of the cookbook to delete.")
+ exit(1)
+ end
+ end
+
+ def delete_explicit_version
+ delete_object(Chef::CookbookVersion, "#{@cookbook_name} version #{@version}", "cookbook") do
+ delete_request("cookbooks/#{@cookbook_name}/#{@version}")
+ end
+ end
+
+ def delete_all_versions
+ confirm("Do you really want to delete all versions of #{@cookbook_name}")
+ delete_all_without_confirmation
+ end
+
+ def delete_all_without_confirmation
+ # look up the available versions again just in case the user
+ # got to the list of versions to delete and selected 'all'
+ # and also a specific version
+ @available_versions = nil
+ Array(available_versions).each do |version|
+ delete_version_without_confirmation(version)
+ end
+ end
+
+ def delete_without_explicit_version
+ if available_versions.nil?
+ # we already logged an error or 2 about it, so just bail
+ exit(1)
+ elsif available_versions.size == 1
+ @version = available_versions.first
+ delete_explicit_version
+ else
+ versions_to_delete = ask_which_versions_to_delete
+ delete_versions_without_confirmation(versions_to_delete)
+ end
+ end
+
+ def available_versions
+ @available_versions ||= rest.get("cookbooks/#{@cookbook_name}").map do |name, url_and_version|
+ url_and_version["versions"].map { |url_by_version| url_by_version["version"] }
+ end.flatten
+ rescue Net::HTTPClientException => e
+ if /^404/.match?(e.to_s)
+ ui.error("Cannot find a cookbook named #{@cookbook_name} to delete.")
+ nil
+ else
+ raise
+ end
+ end
+
+ def ask_which_versions_to_delete
+ question = "Which version(s) do you want to delete?\n"
+ valid_responses = {}
+ available_versions.each_with_index do |version, index|
+ valid_responses[(index + 1).to_s] = version
+ question << "#{index + 1}. #{@cookbook_name} #{version}\n"
+ end
+ valid_responses[(available_versions.size + 1).to_s] = :all
+ question << "#{available_versions.size + 1}. All versions\n\n"
+ responses = ask_question(question).split(",").map(&:strip)
+
+ if responses.empty?
+ ui.error("No versions specified, exiting")
+ exit(1)
+ end
+ versions = responses.map do |response|
+ if version = valid_responses[response]
+ version
+ else
+ ui.error("#{response} is not a valid choice, skipping it")
+ end
+ end
+ versions.compact
+ end
+
+ def delete_version_without_confirmation(version)
+ object = delete_request("cookbooks/#{@cookbook_name}/#{version}")
+ output(format_for_display(object)) if config[:print_after]
+ ui.info("Deleted cookbook[#{@cookbook_name}][#{version}]")
+ end
+
+ def delete_versions_without_confirmation(versions)
+ versions.each do |version|
+ if version == :all
+ delete_all_without_confirmation
+ break
+ else
+ delete_version_without_confirmation(version)
+ end
+ end
+ end
+
+ private
+
+ def delete_request(path)
+ path += "?purge=true" if config[:purge]
+ rest.delete(path)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/cookbook_download.rb b/knife/lib/chef/knife/cookbook_download.rb
new file mode 100644
index 0000000000..dcf7299901
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_download.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Christopher Walters (<cw@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookDownload < Knife
+
+ attr_reader :version
+ attr_accessor :cookbook_name
+
+ deps do
+ require "chef/cookbook_version" unless defined?(Chef::CookbookVersion)
+ end
+
+ banner "knife cookbook download COOKBOOK [VERSION] (options)"
+
+ option :latest,
+ short: "-N",
+ long: "--latest",
+ description: "The version of the cookbook to download.",
+ boolean: true
+
+ option :download_directory,
+ short: "-d DOWNLOAD_DIRECTORY",
+ long: "--dir DOWNLOAD_DIRECTORY",
+ description: "The directory to download the cookbook into.",
+ default: Dir.pwd
+
+ option :force,
+ short: "-f",
+ long: "--force",
+ description: "Force download over the download directory if it exists."
+
+ # TODO: tim/cw: 5-23-2010: need to implement knife-side
+ # specificity for downloads - need to implement --platform and
+ # --fqdn here
+ def run
+ @cookbook_name, @version = @name_args
+
+ if @cookbook_name.nil?
+ show_usage
+ ui.fatal("You must specify a cookbook name")
+ exit 1
+ elsif @version.nil?
+ @version = determine_version
+ if @version.nil?
+ ui.fatal("No such cookbook found")
+ exit 1
+ end
+ end
+
+ ui.info("Downloading #{@cookbook_name} cookbook version #{@version}")
+
+ cookbook = Chef::CookbookVersion.load(@cookbook_name, @version)
+ manifest = cookbook.cookbook_manifest
+
+ basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}")
+ if File.exist?(basedir)
+ if config[:force]
+ Chef::Log.trace("Deleting #{basedir}")
+ FileUtils.rm_rf(basedir)
+ else
+ ui.fatal("Directory #{basedir} exists, use --force to overwrite")
+ exit
+ end
+ end
+
+ manifest.by_parent_directory.each do |segment, files|
+ ui.info("Downloading #{segment}")
+ files.each do |segment_file|
+ dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR))
+ Chef::Log.trace("Downloading #{segment_file["path"]} to #{dest}")
+ FileUtils.mkdir_p(File.dirname(dest))
+ tempfile = rest.streaming_request(segment_file["url"])
+ FileUtils.mv(tempfile.path, dest)
+ end
+ end
+ ui.info("Cookbook downloaded to #{basedir}")
+ end
+
+ def determine_version
+ if available_versions.nil?
+ nil
+ elsif available_versions.size == 1
+ @version = available_versions.first
+ elsif config[:latest]
+ @version = available_versions.last
+ else
+ ask_which_version
+ end
+ end
+
+ def available_versions
+ @available_versions ||= begin
+ versions = Chef::CookbookVersion.available_versions(@cookbook_name)
+ unless versions.nil?
+ versions.map! { |version| Chef::Version.new(version) }
+ versions.sort!
+ end
+ versions
+ end
+ @available_versions
+ end
+
+ def ask_which_version
+ question = "Which version do you want to download?\n"
+ valid_responses = {}
+ available_versions.each_with_index do |version, index|
+ valid_responses[(index + 1).to_s] = version
+ question << "#{index + 1}. #{@cookbook_name} #{version}\n"
+ end
+ question += "\n"
+ response = ask_question(question).strip
+
+ unless @version = valid_responses[response]
+ ui.error("'#{response}' is not a valid value.")
+ exit(1)
+ end
+ @version
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/cookbook_list.rb b/knife/lib/chef/knife/cookbook_list.rb
index 719c10f893..719c10f893 100644
--- a/lib/chef/knife/cookbook_list.rb
+++ b/knife/lib/chef/knife/cookbook_list.rb
diff --git a/knife/lib/chef/knife/cookbook_metadata.rb b/knife/lib/chef/knife/cookbook_metadata.rb
new file mode 100644
index 0000000000..854e7a6609
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_metadata.rb
@@ -0,0 +1,106 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookMetadata < Knife
+
+ deps do
+ require "chef/cookbook_loader" unless defined?(Chef::CookbookLoader)
+ require "chef/cookbook/metadata" unless defined?(Chef::Cookbook::Metadata)
+ end
+
+ banner "knife cookbook metadata COOKBOOK (options)"
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| o.split(":") }
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Generate metadata for all cookbooks, rather than just a single cookbook."
+
+ def run
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
+ if config[:all]
+ cl = Chef::CookbookLoader.new(config[:cookbook_path])
+ cl.load_cookbooks
+ cl.each_key do |cname|
+ generate_metadata(cname.to_s)
+ end
+ else
+ cookbook_name = @name_args[0]
+ if cookbook_name.nil? || cookbook_name.empty?
+ ui.error "You must specify the cookbook to generate metadata for, or use the --all option."
+ exit 1
+ end
+ generate_metadata(cookbook_name)
+ end
+ end
+
+ def generate_metadata(cookbook)
+ Array(config[:cookbook_path]).reverse_each do |path|
+ file = File.expand_path(File.join(path, cookbook, "metadata.rb"))
+ if File.exist?(file)
+ generate_metadata_from_file(cookbook, file)
+ else
+ validate_metadata_json(path, cookbook)
+ end
+ end
+ end
+
+ def generate_metadata_from_file(cookbook, file)
+ ui.info("Generating metadata for #{cookbook} from #{file}")
+ md = Chef::Cookbook::Metadata.new
+ md.name(cookbook)
+ md.from_file(file)
+ json_file = File.join(File.dirname(file), "metadata.json")
+ File.open(json_file, "w") do |f|
+ f.write(Chef::JSONCompat.to_json_pretty(md))
+ end
+ Chef::Log.trace("Generated #{json_file}")
+ rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
+ ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
+ ui.stderr.puts "in #{file}:"
+ ui.stderr.puts
+ ui.stderr.puts e.message
+ exit 1
+ end
+
+ def validate_metadata_json(path, cookbook)
+ json_file = File.join(path, cookbook, "metadata.json")
+ if File.exist?(json_file)
+ Chef::Cookbook::Metadata.validate_json(IO.read(json_file))
+ end
+ rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
+ ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
+ ui.stderr.puts "in #{json_file}:"
+ ui.stderr.puts
+ ui.stderr.puts e.message
+ exit 1
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/cookbook_metadata_from_file.rb b/knife/lib/chef/knife/cookbook_metadata_from_file.rb
new file mode 100644
index 0000000000..77a141d426
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_metadata_from_file.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright 2010-2016, Matthew Kent
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookMetadataFromFile < Knife
+
+ deps do
+ require "chef/cookbook/metadata" unless defined?(Chef::Cookbook::Metadata)
+ end
+
+ banner "knife cookbook metadata from file FILE (options)"
+
+ def run
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the FILE.")
+ exit(1)
+ end
+
+ file = @name_args[0]
+ cookbook = File.basename(File.dirname(file))
+
+ @metadata = Chef::Knife::CookbookMetadata.new
+ @metadata.generate_metadata_from_file(cookbook, file)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/cookbook_show.rb b/knife/lib/chef/knife/cookbook_show.rb
new file mode 100644
index 0000000000..aac26447b9
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_show.rb
@@ -0,0 +1,98 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookShow < Knife
+
+ deps do
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ require "uri" unless defined?(URI)
+ require "chef/cookbook_version" unless defined?(Chef::CookbookVersion)
+ end
+
+ banner "knife cookbook show COOKBOOK [VERSION] [PART] [FILENAME] (options)"
+
+ option :fqdn,
+ short: "-f FQDN",
+ long: "--fqdn FQDN",
+ description: "The FQDN of the host to see the file for."
+
+ option :platform,
+ short: "-p PLATFORM",
+ long: "--platform PLATFORM",
+ description: "The platform to see the file for."
+
+ option :platform_version,
+ short: "-V VERSION",
+ long: "--platform-version VERSION",
+ description: "The platform version to see the file for."
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ cookbook_name, cookbook_version, segment, filename = @name_args
+
+ cookbook = Chef::CookbookVersion.load(cookbook_name, cookbook_version) unless cookbook_version.nil?
+
+ case @name_args.length
+ when 4 # We are showing a specific file
+ node = {}
+ node[:fqdn] = config[:fqdn] if config.key?(:fqdn)
+ node[:platform] = config[:platform] if config.key?(:platform)
+ node[:platform_version] = config[:platform_version] if config.key?(:platform_version)
+
+ class << node
+ def attribute?(name) # rubocop:disable Lint/NestedMethodDefinition
+ key?(name)
+ end
+ end
+
+ manifest_entry = cookbook.preferred_manifest_record(node, segment, filename)
+ temp_file = rest.streaming_request(manifest_entry[:url])
+
+ # the temp file is cleaned up elsewhere
+ temp_file.open if temp_file.closed?
+ pretty_print(temp_file.read)
+
+ when 3 # We are showing a specific part of the cookbook
+ if segment == "metadata"
+ output(cookbook.metadata)
+ else
+ output(cookbook.files_for(segment))
+ end
+ when 2 # We are showing the whole cookbook
+ output(cookbook.display)
+ when 1 # We are showing the cookbook versions (all of them)
+ env = config[:environment]
+ api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}"
+ output(format_cookbook_list_for_display(rest.get(api_endpoint)))
+ when 0
+ show_usage
+ ui.fatal("You must specify a cookbook name")
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/cookbook_upload.rb b/knife/lib/chef/knife/cookbook_upload.rb
new file mode 100644
index 0000000000..d9582a3ccc
--- /dev/null
+++ b/knife/lib/chef/knife/cookbook_upload.rb
@@ -0,0 +1,292 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Christopher Walters (<cw@chef.io>)
+# Author:: Nuo Yan (<yan.nuo@gmail.com>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class CookbookUpload < Knife
+ deps do
+ require "chef/mixin/file_class" unless defined?(Chef::Mixin::FileClass)
+ include Chef::Mixin::FileClass
+ require "chef/exceptions" unless defined?(Chef::Exceptions)
+ require "chef/cookbook_loader" unless defined?(Chef::CookbookLoader)
+ require "chef/cookbook_uploader" unless defined?(Chef::CookbookUploader)
+ end
+
+ banner "knife cookbook upload [COOKBOOKS...] (options)"
+
+ option :cookbook_path,
+ short: "-o 'PATH:PATH'",
+ long: "--cookbook-path 'PATH:PATH'",
+ description: "A delimited path to search for cookbooks. On Unix the delimiter is ':', on Windows it is ';'.",
+ proc: lambda { |o| o.split(File::PATH_SEPARATOR) }
+
+ option :freeze,
+ long: "--freeze",
+ description: "Freeze this version of the cookbook so that it cannot be overwritten.",
+ boolean: true
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Upload all cookbooks, rather than just a single cookbook."
+
+ option :force,
+ long: "--force",
+ boolean: true,
+ description: "Update cookbook versions even if they have been frozen."
+
+ option :concurrency,
+ long: "--concurrency NUMBER_OF_THREADS",
+ description: "How many concurrent threads will be used.",
+ default: 10,
+ proc: lambda { |o| o.to_i }
+
+ option :environment,
+ short: "-E",
+ long: "--environment ENVIRONMENT",
+ description: "Set ENVIRONMENT's version dependency match the version you're uploading.",
+ default: nil
+
+ option :depends,
+ short: "-d",
+ long: "--include-dependencies",
+ description: "Also upload cookbook dependencies."
+
+ def run
+ # Sanity check before we load anything from the server
+ if ! config[:all] && @name_args.empty?
+ show_usage
+ ui.fatal("You must specify the --all flag or at least one cookbook name")
+ exit 1
+ end
+
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
+ assert_environment_valid!
+ version_constraints_to_update = {}
+ upload_failures = 0
+ upload_ok = 0
+
+ # Get a list of cookbooks and their versions from the server
+ # to check for the existence of a cookbook's dependencies.
+ @server_side_cookbooks = Chef::CookbookVersion.list_all_versions
+ justify_width = @server_side_cookbooks.map(&:size).max.to_i + 2
+
+ cookbooks = []
+ cookbooks_to_upload.each do |cookbook_name, cookbook|
+ raise Chef::Exceptions::MetadataNotFound.new(cookbook.root_paths[0], cookbook_name) unless cookbook.has_metadata_file?
+
+ if cookbook.metadata.name.nil?
+ message = "Cookbook loaded at path [#{cookbook.root_paths[0]}] has invalid metadata: #{cookbook.metadata.errors.join("; ")}"
+ raise Chef::Exceptions::MetadataNotValid, message
+ end
+
+ cookbooks << cookbook
+ end
+
+ if cookbooks.empty?
+ cookbook_path = config[:cookbook_path].respond_to?(:join) ? config[:cookbook_path].join(", ") : config[:cookbook_path]
+ ui.warn("Could not find any cookbooks in your cookbook path: '#{File.expand_path(cookbook_path)}'. Use --cookbook-path to specify the desired path.")
+ else
+ Chef::CookbookLoader.copy_to_tmp_dir_from_array(cookbooks) do |tmp_cl|
+ tmp_cl.load_cookbooks
+ tmp_cl.compile_metadata
+ tmp_cl.freeze_versions if config[:freeze]
+
+ cookbooks_for_upload = []
+ tmp_cl.each do |cookbook_name, cookbook|
+ cookbooks_for_upload << cookbook
+ version_constraints_to_update[cookbook_name] = cookbook.version
+ end
+ if config[:all]
+ if cookbooks_for_upload.any?
+ begin
+ upload(cookbooks_for_upload, justify_width)
+ rescue Chef::Exceptions::CookbookFrozen
+ ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
+ ui.error("Uploading of some of the cookbooks must be failed. Remove cookbook whose version is frozen from your cookbooks repo OR use --force option.")
+ upload_failures += 1
+ rescue SystemExit => e
+ raise exit e.status
+ end
+ ui.info("Uploaded all cookbooks.") if upload_failures == 0
+ end
+ else
+ tmp_cl.each do |cookbook_name, cookbook|
+
+ upload([cookbook], justify_width)
+ upload_ok += 1
+ rescue Exceptions::CookbookNotFoundInRepo => e
+ upload_failures += 1
+ ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
+ Log.debug(e)
+ upload_failures += 1
+ rescue Exceptions::CookbookFrozen
+ ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.")
+ upload_failures += 1
+ rescue SystemExit => e
+ raise exit e.status
+
+ end
+
+ if upload_failures == 0
+ ui.info "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"}."
+ elsif upload_failures > 0 && upload_ok > 0
+ ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"} ok but #{upload_failures} " +
+ "cookbook#{upload_failures == 1 ? "" : "s"} upload failed."
+ elsif upload_failures > 0 && upload_ok == 0
+ ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures == 1 ? "" : "s"}."
+ exit 1
+ end
+ end
+ unless version_constraints_to_update.empty?
+ update_version_constraints(version_constraints_to_update) if config[:environment]
+ end
+ end
+ end
+ end
+
+ def cookbooks_to_upload
+ @cookbooks_to_upload ||=
+ if config[:all]
+ cookbook_repo.load_cookbooks
+ else
+ upload_set = {}
+ @name_args.each do |cookbook_name|
+
+ unless upload_set.key?(cookbook_name)
+ upload_set[cookbook_name] = cookbook_repo[cookbook_name]
+ if config[:depends]
+ upload_set[cookbook_name].metadata.dependencies.each_key { |dep| @name_args << dep }
+ end
+ end
+ rescue Exceptions::CookbookNotFoundInRepo => e
+ ui.error(e.message)
+ Log.debug(e)
+
+ end
+ upload_set
+ end
+ end
+
+ def cookbook_repo
+ @cookbook_loader ||= begin
+ Chef::Cookbook::FileVendor.fetch_from_disk(config[:cookbook_path])
+ Chef::CookbookLoader.new(config[:cookbook_path])
+ end
+ end
+
+ def update_version_constraints(new_version_constraints)
+ new_version_constraints.each do |cookbook_name, version|
+ environment.cookbook_versions[cookbook_name] = "= #{version}"
+ end
+ environment.save
+ end
+
+ def environment
+ @environment ||= config[:environment] ? Environment.load(config[:environment]) : nil
+ end
+
+ private
+
+ def assert_environment_valid!
+ environment
+ rescue Net::HTTPClientException => e
+ if e.response.code.to_s == "404"
+ ui.error "The environment #{config[:environment]} does not exist on the server, aborting."
+ Log.debug(e)
+ exit 1
+ else
+ raise
+ end
+ end
+
+ def upload(cookbooks, justify_width)
+ cookbooks.each do |cb|
+ ui.info("Uploading #{cb.name.to_s.ljust(justify_width + 10)} [#{cb.version}]")
+ check_for_broken_links!(cb)
+ check_for_dependencies!(cb)
+ end
+ Chef::CookbookUploader.new(cookbooks, force: config[:force], concurrency: config[:concurrency]).upload_cookbooks
+ rescue Chef::Exceptions::CookbookFrozen => e
+ ui.error e
+ raise
+ end
+
+ def check_for_broken_links!(cookbook)
+ # MUST!! dup the cookbook version object--it memoizes its
+ # manifest object, but the manifest becomes invalid when you
+ # regenerate the metadata
+ broken_files = cookbook.dup.manifest_records_by_path.select do |path, info|
+ !/[0-9a-f]{32,}/.match?(info["checksum"])
+ end
+ unless broken_files.empty?
+ broken_filenames = Array(broken_files).map { |path, info| path }
+ ui.error "The cookbook #{cookbook.name} has one or more broken files"
+ ui.error "This is probably caused by broken symlinks in the cookbook directory"
+ ui.error "The broken file(s) are: #{broken_filenames.join(" ")}"
+ exit 1
+ end
+ end
+
+ def check_for_dependencies!(cookbook)
+ # for all dependencies, check if the version is on the server, or
+ # the version is in the cookbooks being uploaded. If not, exit and warn the user.
+ missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version|
+ check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
+ end
+
+ unless missing_dependencies.empty?
+ missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'" }
+ ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently"
+ ui.error "being uploaded and cannot be found on the server."
+ ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(", ")}"
+ exit 1
+ end
+ end
+
+ def check_server_side_cookbooks(cookbook_name, version)
+ if @server_side_cookbooks[cookbook_name].nil?
+ false
+ else
+ versions = @server_side_cookbooks[cookbook_name]["versions"].collect { |versions| versions["version"] }
+ Log.debug "Versions of cookbook '#{cookbook_name}' returned by the server: #{versions.join(", ")}"
+ @server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash|
+ if Chef::VersionConstraint.new(version).include?(versions_hash["version"])
+ Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash["version"]}' on the server"
+ return true
+ end
+ end
+ false
+ end
+ end
+
+ def check_uploading_cookbooks(cookbook_name, version)
+ if (! cookbooks_to_upload[cookbook_name].nil?) && Chef::VersionConstraint.new(version).include?(cookbooks_to_upload[cookbook_name].version)
+ Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to a local cookbook."
+ return true
+ end
+ false
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/bootstrap_context.rb b/knife/lib/chef/knife/core/bootstrap_context.rb
new file mode 100644
index 0000000000..0d71aa8dc3
--- /dev/null
+++ b/knife/lib/chef/knife/core/bootstrap_context.rb
@@ -0,0 +1,264 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "run_list" unless defined?(Chef::RunList)
+require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+require "pathname" unless defined?(Pathname)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ module Core
+ # Instances of BootstrapContext are the context objects (i.e., +self+) for
+ # bootstrap templates. For backwards compatibility, they +must+ set the
+ # following instance variables:
+ # * @config - a hash of knife's config values
+ # * @run_list - the run list for the node to bootstrap
+ #
+ class BootstrapContext
+
+ attr_accessor :client_pem
+ attr_accessor :config
+ attr_accessor :chef_config
+
+ def initialize(config, run_list, chef_config, secret = nil)
+ @config = config
+ @run_list = run_list
+ @chef_config = chef_config
+ @secret = secret
+ end
+
+ def bootstrap_environment
+ config[:environment]
+ end
+
+ def validation_key
+ if chef_config[:validation_key] &&
+ File.exist?(File.expand_path(chef_config[:validation_key]))
+ IO.read(File.expand_path(chef_config[:validation_key]))
+ else
+ false
+ end
+ end
+
+ def client_d
+ @client_d ||= client_d_content
+ end
+
+ def encrypted_data_bag_secret
+ @secret
+ end
+
+ # Contains commands and content, see trusted_certs_content
+ # @todo Rename to trusted_certs_script
+ def trusted_certs
+ @trusted_certs ||= trusted_certs_content
+ end
+
+ def get_log_location
+ if !(chef_config[:config_log_location].class == IO ) && (chef_config[:config_log_location].nil? || chef_config[:config_log_location].to_s.empty?)
+ "STDOUT"
+ elsif chef_config[:config_log_location].equal?(:win_evt)
+ raise "The value :win_evt is not supported for config_log_location on Linux Platforms \n"
+ elsif chef_config[:config_log_location].equal?(:syslog)
+ ":syslog"
+ elsif chef_config[:config_log_location].equal?(STDOUT)
+ "STDOUT"
+ elsif chef_config[:config_log_location].equal?(STDERR)
+ "STDERR"
+ elsif chef_config[:config_log_location]
+ %Q{"#{chef_config[:config_log_location]}"}
+ else
+ "STDOUT"
+ end
+ end
+
+ def config_content
+ client_rb = <<~CONFIG
+ chef_server_url "#{chef_config[:chef_server_url]}"
+ validation_client_name "#{chef_config[:validation_client_name]}"
+ CONFIG
+
+ unless chef_config[:chef_license].nil?
+ client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
+ end
+
+ unless chef_config[:config_log_level].nil? || chef_config[:config_log_level].empty?
+ client_rb << %Q{log_level :#{chef_config[:config_log_level]}\n}
+ end
+
+ client_rb << "log_location #{get_log_location}\n"
+
+ if config[:chef_node_name]
+ client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
+ else
+ client_rb << "# Using default node name (fqdn)\n"
+ end
+
+ # We configure :verify_api_cert only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
+ value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
+ client_rb << %Q{verify_api_cert #{value}\n}
+ end
+
+ # We configure :ssl_verify_mode only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if config[:node_ssl_verify_mode] || config.key?(:ssl_verify_mode)
+ value = case config[:node_ssl_verify_mode]
+ when "peer"
+ :verify_peer
+ when "none"
+ :verify_none
+ when nil
+ config[:ssl_verify_mode]
+ else
+ nil
+ end
+
+ if value
+ client_rb << %Q{ssl_verify_mode :#{value}\n}
+ end
+ end
+
+ if config[:ssl_verify_mode]
+ client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
+ end
+
+ if config[:bootstrap_proxy]
+ client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
+ end
+
+ if config[:bootstrap_proxy_user]
+ client_rb << %Q{http_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
+ client_rb << %Q{https_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
+ end
+
+ if config[:bootstrap_proxy_pass]
+ client_rb << %Q{http_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
+ client_rb << %Q{https_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
+ end
+
+ if config[:bootstrap_no_proxy]
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
+ end
+
+ if encrypted_data_bag_secret
+ client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n}
+ end
+
+ unless trusted_certs.empty?
+ client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n}
+ end
+
+ if chef_config[:fips]
+ client_rb << "fips true\n"
+ end
+
+ unless chef_config[:file_cache_path].nil?
+ client_rb << "file_cache_path \"#{chef_config[:file_cache_path]}\"\n"
+ end
+
+ unless chef_config[:file_backup_path].nil?
+ client_rb << "file_backup_path \"#{chef_config[:file_backup_path]}\"\n"
+ end
+
+ client_rb
+ end
+
+ def start_chef
+ # If the user doesn't have a client path configure, let bash use the PATH for what it was designed for
+ client_path = chef_config[:chef_client_path] || ChefUtils::Dist::Infra::CLIENT
+ s = "#{client_path} -j /etc/chef/first-boot.json"
+ if config[:verbosity] && config[:verbosity] >= 3
+ s << " -l trace"
+ elsif config[:verbosity] && config[:verbosity] >= 2
+ s << " -l debug"
+ end
+ s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
+ s << " --no-color" unless config[:color]
+ s
+ end
+
+ #
+ # Returns the version of Chef to install (as recognized by the Omnitruck API)
+ #
+ # @return [String] download version string
+ def version_to_install
+ return config[:bootstrap_version] if config[:bootstrap_version]
+
+ if config[:channel] == "stable"
+ Chef::VERSION.split(".").first
+ else
+ "latest"
+ end
+ end
+
+ def first_boot
+ (config[:first_boot_attributes] = Mash.new(config[:first_boot_attributes]) || Mash.new).tap do |attributes|
+ if config[:policy_name] && config[:policy_group]
+ attributes[:policy_name] = config[:policy_name]
+ attributes[:policy_group] = config[:policy_group]
+ else
+ attributes[:run_list] = @run_list
+ end
+ attributes.delete(:run_list) if attributes[:policy_name] && !attributes[:policy_name].empty?
+ attributes.merge!(tags: config[:tags]) if config[:tags] && !config[:tags].empty?
+ end
+ end
+
+ private
+
+ # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
+ # This string should contain both the commands necessary to both create the files, as well as their content
+ def trusted_certs_content
+ content = ""
+ if chef_config[:trusted_certs_dir]
+ Dir.glob(File.join(ChefConfig::PathHelper.escape_glob_dir(chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
+ content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" +
+ IO.read(File.expand_path(cert)) + "\nEOP\n"
+ end
+ end
+ content
+ end
+
+ def client_d_content
+ content = ""
+ if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
+ root = Pathname(chef_config[:client_d_dir])
+ root.find do |f|
+ relative = f.relative_path_from(root)
+ if f != root
+ file_on_node = "/etc/chef/client.d/#{relative}"
+ if f.directory?
+ content << "mkdir #{file_on_node}\n"
+ else
+ content << "cat > #{file_on_node} <<'EOP'\n" +
+ f.read.gsub("'", "'\\\\''") + "\nEOP\n"
+ end
+ end
+ end
+ end
+ content
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/cookbook_scm_repo.rb b/knife/lib/chef/knife/core/cookbook_scm_repo.rb
new file mode 100644
index 0000000000..921dadad8b
--- /dev/null
+++ b/knife/lib/chef/knife/core/cookbook_scm_repo.rb
@@ -0,0 +1,159 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/mixin/shell_out" unless defined?(Chef::Mixin::ShellOut)
+
+class Chef
+ class Knife
+ class CookbookSCMRepo
+
+ DIRTY_REPO = /^\s+M/.freeze
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :repo_path
+ attr_reader :default_branch
+ attr_reader :use_current_branch
+ attr_reader :ui
+
+ def initialize(repo_path, ui, opts = {})
+ @repo_path = repo_path
+ @ui = ui
+ @default_branch = "master"
+ @use_current_branch = false
+ apply_opts(opts)
+ end
+
+ def sanity_check
+ unless ::File.directory?(repo_path)
+ ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory")
+ exit 1
+ end
+ unless git_repo?(repo_path)
+ ui.error "The cookbook repo #{repo_path} is not a git repository."
+ ui.info("Use `git init` to initialize a git repo")
+ exit 1
+ end
+ if use_current_branch
+ @default_branch = get_current_branch
+ end
+ unless branch_exists?(default_branch)
+ ui.error "The default branch '#{default_branch}' does not exist"
+ ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
+ exit 1
+ end
+ cmd = git("status --porcelain")
+ if DIRTY_REPO.match?(cmd.stdout)
+ ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
+ ui.msg cmd.stdout
+ ui.info "Commit or stash your changes before importing cookbooks"
+ exit 1
+ end
+ # TODO: any untracked files in the cookbook directory will get nuked later
+ # make this an error condition also.
+ true
+ end
+
+ def reset_to_default_state
+ ui.info("Checking out the #{default_branch} branch.")
+ git("checkout #{default_branch}")
+ end
+
+ def prepare_to_import(cookbook_name)
+ branch = "chef-vendor-#{cookbook_name}"
+ if branch_exists?(branch)
+ ui.info("Pristine copy branch (#{branch}) exists, switching to it.")
+ git("checkout #{branch}")
+ else
+ ui.info("Creating pristine copy branch #{branch}")
+ git("checkout -b #{branch}")
+ end
+ end
+
+ def finalize_updates_to(cookbook_name, version)
+ if update_count = updated?(cookbook_name)
+ ui.info "#{update_count} files updated, committing changes"
+ git("add #{cookbook_name}")
+ git("commit -m \"Import #{cookbook_name} version #{version}\" -- #{cookbook_name}")
+ ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}")
+ git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}")
+ true
+ else
+ ui.info("No changes made to #{cookbook_name}")
+ false
+ end
+ end
+
+ def merge_updates_from(cookbook_name, version)
+ branch = "chef-vendor-#{cookbook_name}"
+ Dir.chdir(repo_path) do
+ if system("git merge #{branch}")
+ ui.info("Cookbook #{cookbook_name} version #{version} successfully installed")
+ else
+ ui.error("You have merge conflicts - please resolve manually")
+ ui.info("Merge status (cd #{repo_path}; git status):")
+ system("git status")
+ exit 3
+ end
+ end
+ end
+
+ def updated?(cookbook_name)
+ update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count
+ update_count == 0 ? nil : update_count
+ end
+
+ def branch_exists?(branch_name)
+ git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ }
+ end
+
+ def get_current_branch
+ ref = git("symbolic-ref HEAD").stdout
+ ref.chomp.split("/")[2]
+ end
+
+ private
+
+ def git_repo?(directory)
+ if File.directory?(File.join(directory, ".git"))
+ true
+ elsif File.dirname(directory) == directory
+ false
+ else
+ git_repo?(File.dirname(directory))
+ end
+ end
+
+ def apply_opts(opts)
+ opts.each do |option, value|
+ case option.to_s
+ when "default_branch"
+ @default_branch = value
+ when "use_current_branch"
+ @use_current_branch = value
+ end
+ end
+ end
+
+ def git(command)
+ shell_out!("git #{command}", cwd: repo_path)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/cookbook_site_streaming_uploader.rb b/knife/lib/chef/knife/core/cookbook_site_streaming_uploader.rb
new file mode 100644
index 0000000000..85e83af5da
--- /dev/null
+++ b/knife/lib/chef/knife/core/cookbook_site_streaming_uploader.rb
@@ -0,0 +1,249 @@
+#
+# Author:: Stanislav Vitvitskiy
+# Author:: Nuo Yan (nuo@chef.io)
+# Author:: Christopher Walters (<cw@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+autoload :URI, "uri"
+module Net
+ autoload :HTTP, "net/http"
+end
+autoload :OpenSSL, "openssl"
+module Mixlib
+ module Authentication
+ autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
+ end
+end
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require_relative "../cookbook_metadata"
+class Chef
+ class Knife
+ module Core
+ # == Chef::Knife::Core::CookbookSiteStreamingUploader
+ # A streaming multipart HTTP upload implementation. Used to upload cookbooks
+ # (in tarball form) to https://supermarket.chef.io
+ #
+ # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
+ class CookbookSiteStreamingUploader
+
+ DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }.freeze # rubocop:disable Naming/ConstantName
+
+ class << self
+
+ def create_build_dir(cookbook)
+ tmp_cookbook_path = Tempfile.new("#{ChefUtils::Dist::Infra::SHORT}-#{cookbook.name}-build")
+ tmp_cookbook_path.close
+ tmp_cookbook_dir = tmp_cookbook_path.path
+ File.unlink(tmp_cookbook_dir)
+ FileUtils.mkdir_p(tmp_cookbook_dir)
+ Chef::Log.trace("Staging at #{tmp_cookbook_dir}")
+ checksums_to_on_disk_paths = cookbook.checksums
+ cookbook.each_file do |manifest_record|
+ path_in_cookbook = manifest_record[:path]
+ on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
+ dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
+ FileUtils.mkdir_p(File.dirname(dest))
+ Chef::Log.trace("Staging #{on_disk_path} to #{dest}")
+ FileUtils.cp(on_disk_path, dest)
+ end
+
+ # First, generate metadata
+ Chef::Log.trace("Generating metadata")
+ kcm = Chef::Knife::CookbookMetadata.new
+ kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
+ kcm.name_args = [ cookbook.name.to_s ]
+ kcm.run
+
+ tmp_cookbook_dir
+ end
+
+ def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
+ make_request(:post, to_url, user_id, secret_key_filename, params, headers)
+ end
+
+ def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
+ make_request(:put, to_url, user_id, secret_key_filename, params, headers)
+ end
+
+ def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
+ boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ"
+ parts = []
+ content_file = nil
+
+ secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
+
+ unless params.nil? || params.empty?
+ params.each do |key, value|
+ if value.is_a?(File)
+ content_file = value
+ filepath = value.path
+ filename = File.basename(filepath)
+ parts << StringPart.new( "--" + boundary + "\r\n" +
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
+ "Content-Type: application/octet-stream\r\n\r\n")
+ parts << StreamPart.new(value, File.size(filepath))
+ parts << StringPart.new("\r\n")
+ else
+ parts << StringPart.new( "--" + boundary + "\r\n" +
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
+ parts << StringPart.new(value.to_s + "\r\n")
+ end
+ end
+ parts << StringPart.new("--" + boundary + "--\r\n")
+ end
+
+ body_stream = MultipartStream.new(parts)
+
+ timestamp = Time.now.utc.iso8601
+
+ url = URI.parse(to_url)
+
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
+
+ # We use the body for signing the request if the file parameter
+ # wasn't a valid file or wasn't included. Extract the body (with
+ # multi-part delimiters intact) to sign the request.
+ # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
+ # always hash the entire request body. In the file case it would just be
+ # expanded multipart text - the entire body of the POST.
+ content_body = parts.inject("") { |result, part| result + part.read(0, part.size) }
+ content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
+
+ signing_options = {
+ http_method: http_verb,
+ path: url.path,
+ user_id: user_id,
+ timestamp: timestamp }
+ (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
+
+ headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
+
+ content_file.rewind if content_file
+
+ # net/http doesn't like symbols for header keys, so we'll to_s each one just in case
+ headers = DefaultHeaders.merge(Hash[*headers.map { |k, v| [k.to_s, v] }.flatten])
+
+ req = case http_verb
+ when :put
+ Net::HTTP::Put.new(url.path, headers)
+ when :post
+ Net::HTTP::Post.new(url.path, headers)
+ end
+ req.content_length = body_stream.size
+ req.content_type = "multipart/form-data; boundary=" + boundary unless parts.empty?
+ req.body_stream = body_stream
+
+ http = Chef::HTTP::BasicClient.new(url).http_client
+ res = http.request(req)
+
+ # alias status to code and to_s to body for test purposes
+ # TODO: stop the following madness!
+ class << res
+ alias :to_s :body
+
+ # BUG this makes the response compatible with what response_steps expects to test headers (response.headers[] -> response[])
+ def headers # rubocop:disable Lint/NestedMethodDefinition
+ self
+ end
+
+ def status # rubocop:disable Lint/NestedMethodDefinition
+ code.to_i
+ end
+ end
+ res
+ end
+
+ end
+
+ class StreamPart
+ def initialize(stream, size)
+ @stream, @size = stream, size
+ end
+
+ def size
+ @size
+ end
+
+ # read the specified amount from the stream
+ def read(offset, how_much)
+ @stream.read(how_much)
+ end
+ end
+
+ class StringPart
+ def initialize(str)
+ @str = str
+ end
+
+ def size
+ @str.length
+ end
+
+ # read the specified amount from the string starting at the offset
+ def read(offset, how_much)
+ @str[offset, how_much]
+ end
+ end
+
+ class MultipartStream
+ def initialize(parts)
+ @parts = parts
+ @part_no = 0
+ @part_offset = 0
+ end
+
+ def size
+ @parts.inject(0) { |size, part| size + part.size }
+ end
+
+ def read(how_much, dst_buf = nil)
+ if @part_no >= @parts.size
+ dst_buf.replace("") if dst_buf
+ return dst_buf
+ end
+
+ how_much_current_part = @parts[@part_no].size - @part_offset
+
+ how_much_current_part = if how_much_current_part > how_much
+ how_much
+ else
+ how_much_current_part
+ end
+
+ how_much_next_part = how_much - how_much_current_part
+
+ current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
+
+ # recurse into the next part if the current one was not large enough
+ if how_much_next_part > 0
+ @part_no += 1
+ @part_offset = 0
+ next_part = read(how_much_next_part)
+ result = current_part + (next_part || "")
+ else
+ @part_offset += how_much_current_part
+ result = current_part
+ end
+ dst_buf ? dst_buf.replace(result || "") : result
+ end
+ end
+
+ end
+ end
+ end
+end
+
diff --git a/knife/lib/chef/knife/core/formatting_options.rb b/knife/lib/chef/knife/core/formatting_options.rb
new file mode 100644
index 0000000000..cdee2c5989
--- /dev/null
+++ b/knife/lib/chef/knife/core/formatting_options.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ module Core
+
+ # This module may be included into a knife subcommand class to automatically
+ # add configuration options used by the StatusPresenter and NodePresenter.
+ module FormattingOptions
+ # @private
+ # Would prefer to do this in a rational way, but can't be done b/c of
+ # Mixlib::CLI's design :(
+ def self.included(includer)
+ includer.class_eval do
+ option :medium_output,
+ short: "-m",
+ long: "--medium",
+ boolean: true,
+ default: false,
+ description: "Include normal attributes in the output"
+
+ option :long_output,
+ short: "-l",
+ long: "--long",
+ boolean: true,
+ default: false,
+ description: "Include all attributes in the output"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/gem_glob_loader.rb b/knife/lib/chef/knife/core/gem_glob_loader.rb
new file mode 100644
index 0000000000..d365602cb4
--- /dev/null
+++ b/knife/lib/chef/knife/core/gem_glob_loader.rb
@@ -0,0 +1,134 @@
+# Author:: Christopher Brown (<cb@chef.io>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "../version"
+require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+class Chef
+ class Knife
+ class SubcommandLoader
+ class GemGlobLoader < Chef::Knife::SubcommandLoader
+ MATCHES_CHEF_GEM ||= %r{/chef-\d+\.\d+\.\d+}.freeze
+ MATCHES_THIS_CHEF_GEM ||= %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
+
+ def subcommand_files
+ @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ end
+
+ # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
+ # If rubygems is not installed, falls back to globbing the knife directory.
+ # The Hash is of the form {"relative/path" => "/absolute/path"}
+ #--
+ # Note: the "right" way to load the plugins is to require the relative path, i.e.,
+ # require 'chef/knife/command'
+ # but we're getting frustrated by bugs at every turn, and it's slow besides. So
+ # subcommand loader has been modified to load the plugins by using Kernel.load
+ # with the absolute path.
+ def gem_and_builtin_subcommands
+ require "rubygems" unless defined?(Gem)
+ find_subcommands_via_rubygems
+ rescue LoadError
+ find_subcommands_via_dirglob
+ end
+
+ def find_subcommands_via_rubygems
+ files = find_files_latest_gems "chef/knife/*.rb"
+ version_file_match = /#{Regexp.escape(File.join('chef', 'knife', 'version'))}$/
+ subcommand_files = {}
+ files.each do |file|
+
+ rel_path = file[/(.*)(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 2]
+
+ # When not installed as a gem (ChefDK/appbundler in particular), AND
+ # a different version of Chef is installed via gems, `files` will
+ # include some files from the 'other' Chef install. If this contains
+ # a knife command that doesn't exist in this version of Chef, we will
+ # get a LoadError later when we try to require it.
+ next if from_different_chef_version?(file)
+
+ # Exclude knife/chef/version. It's not a knife command, and force-loading
+ # when we load all of these files will emit constant-already-defined warnings
+ next if rel_path =~ version_file_match
+
+ subcommand_files[rel_path] = file
+ end
+
+ subcommand_files.merge(find_subcommands_via_dirglob)
+ end
+
+ private
+
+ def find_files_latest_gems(glob, check_load_path = true)
+ files = []
+
+ if check_load_path
+ files = $LOAD_PATH.map do |load_path|
+ Dir["#{File.expand_path glob, ChefConfig::PathHelper.escape_glob_dir(load_path)}#{Gem.suffix_pattern}"]
+ end.flatten.select { |file| File.file? file.untaint }
+
+ end
+
+ gem_files = latest_gem_specs.map do |spec|
+ # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
+ if spec.respond_to? :matches_for_glob
+ spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ else
+ check_spec_for_glob(spec, glob)
+ end
+ end.flatten
+
+ files.concat gem_files
+ files.uniq! if check_load_path
+
+ files
+ end
+
+ def latest_gem_specs
+ @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
+ Gem::Specification.latest_specs(true) # find prerelease gems
+ else
+ Gem.source_index.latest_specs(true)
+ end
+ end
+
+ def check_spec_for_glob(spec, glob)
+ dirs = if spec.require_paths.size > 1
+ "{#{spec.require_paths.join(",")}}"
+ else
+ spec.require_paths.first
+ end
+
+ glob = File.join(ChefConfig::PathHelper.escape_glob_dir(spec.full_gem_path, dirs), glob)
+
+ Dir[glob].map(&:untaint)
+ end
+
+ def from_different_chef_version?(path)
+ matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
+ end
+
+ def matches_any_chef_gem?(path)
+ path =~ MATCHES_CHEF_GEM
+ end
+
+ def matches_this_chef_gem?(path)
+ path =~ MATCHES_THIS_CHEF_GEM
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/generic_presenter.rb b/knife/lib/chef/knife/core/generic_presenter.rb
index 850bfa8b3d..850bfa8b3d 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/knife/lib/chef/knife/core/generic_presenter.rb
diff --git a/knife/lib/chef/knife/core/hashed_command_loader.rb b/knife/lib/chef/knife/core/hashed_command_loader.rb
new file mode 100644
index 0000000000..e419037b67
--- /dev/null
+++ b/knife/lib/chef/knife/core/hashed_command_loader.rb
@@ -0,0 +1,100 @@
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../version"
+class Chef
+ class Knife
+ class SubcommandLoader
+ #
+ # Load a subcommand from a pre-computed path
+ # for the given command.
+ #
+ class HashedCommandLoader < Chef::Knife::SubcommandLoader
+ KEY = "_autogenerated_command_paths".freeze
+
+ attr_accessor :manifest
+
+ def initialize(chef_config_dir, plugin_manifest)
+ super(chef_config_dir)
+ @manifest = plugin_manifest
+ end
+
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split("-") }.flatten!
+ find_longest_key(manifest[KEY]["plugins_by_category"], category_words, " ")
+ end
+
+ def list_commands(pref_category = nil)
+ if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category)
+ commands = { pref_category => manifest[KEY]["plugins_by_category"][pref_category] }
+ else
+ commands = manifest[KEY]["plugins_by_category"]
+ end
+ # If any of the specified plugins in the manifest don't have a valid path we will
+ # eventually get an error and the user will need to rehash - instead, lets just
+ # print out 1 error here telling them to rehash
+ errors = {}
+ commands.collect { |k, v| v }.flatten.each do |command|
+ paths = manifest[KEY]["plugins_paths"][command]
+ if paths && paths.is_a?(Array)
+ # It is only an error if all the paths don't exist
+ if paths.all? { |sc| !File.exist?(sc) }
+ errors[command] = paths
+ end
+ end
+ end
+ if errors.empty?
+ commands
+ else
+ Chef::Log.error "There are plugin files specified in the knife cache that cannot be found. Please run knife rehash to update the subcommands cache. If you see this error after rehashing delete the cache at #{Chef::Knife::SubcommandLoader.plugin_manifest_path}"
+ Chef::Log.error "Missing files:\n\t#{errors.values.flatten.join("\n\t")}"
+ {}
+ end
+ end
+
+ def subcommand_files
+ manifest[KEY]["plugins_paths"].values.flatten
+ end
+
+ def load_command(args)
+ paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)]
+ if paths.nil? || paths.empty? || (! paths.is_a? Array)
+ false
+ else
+ paths.each do |sc|
+ if File.exist?(sc)
+ Kernel.load sc
+ else
+ return false
+ end
+ end
+ true
+ end
+ end
+
+ def subcommand_for_args(args)
+ if manifest[KEY]["plugins_paths"].key?(args)
+ args
+ else
+ find_longest_key(manifest[KEY]["plugins_paths"], args, "_")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/node_editor.rb b/knife/lib/chef/knife/core/node_editor.rb
new file mode 100644
index 0000000000..5980cd888e
--- /dev/null
+++ b/knife/lib/chef/knife/core/node_editor.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Jordan Running (<jr@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/json_compat" unless defined?(Chef::JSONCompat)
+require "chef/node" unless defined?(Chef::Node)
+
+class Chef
+ class Knife
+ class NodeEditor
+ attr_reader :node, :ui, :config
+ private :node, :ui, :config
+
+ # @param node [Chef::Node]
+ # @param ui [Chef::Knife::UI]
+ # @param config [Hash]
+ def initialize(node, ui, config)
+ @node, @ui, @config = node, ui, config
+ end
+
+ # Opens the node data (as JSON) in the user's editor and returns a new
+ # {Chef::Node} reflecting the user's changes.
+ #
+ # @return [Chef::Node]
+ def edit_node
+ abort "You specified the --disable_editing option, nothing to edit" if config[:disable_editing]
+ assert_editor_set!
+
+ updated_node_data = ui.edit_hash(view)
+ apply_updates(updated_node_data)
+ @updated_node
+ end
+
+ # Returns an array of the names of properties that have been changed or
+ # +false+ if none were changed.
+ #
+ # @return [Array<String>] if any properties have been changed.
+ # @return [false] if no properties have been changed.
+ def updated?
+ return false if @updated_node.nil?
+
+ pristine_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(node))
+ updated_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@updated_node))
+
+ updated_properties = %w{
+ name
+ chef_environment
+ automatic
+ default
+ normal
+ override
+ policy_name
+ policy_group
+ run_list
+ }.reject do |key|
+ pristine_copy[key] == updated_copy[key]
+ end
+
+ updated_properties.any? && updated_properties
+ end
+
+ # @api private
+ def view
+ result = {
+ "name" => node.name,
+ "chef_environment" => node.chef_environment,
+ "normal" => node.normal_attrs,
+ "policy_name" => node.policy_name,
+ "policy_group" => node.policy_group,
+ "run_list" => node.run_list,
+ }
+
+ if config[:all_attributes]
+ result["default"] = node.default_attrs
+ result["override"] = node.override_attrs
+ result["automatic"] = node.automatic_attrs
+ end
+
+ result
+ end
+
+ # @api private
+ def apply_updates(updated_data)
+ if node.name && node.name != updated_data["name"]
+ ui.warn "Changing the name of a node results in a new node being created, #{node.name} will not be modified or removed."
+ ui.confirm "Proceed with creation of new node"
+ end
+
+ data = updated_data.dup
+
+ unless config[:all_attributes]
+ data["automatic"] = node.automatic_attrs
+ data["default"] = node.default_attrs
+ data["override"] = node.override_attrs
+ end
+
+ @updated_node = Node.from_hash(data)
+ end
+
+ private
+
+ def abort(message)
+ ui.error(message)
+ exit 1
+ end
+
+ def assert_editor_set!
+ unless config[:editor]
+ abort "You must set your EDITOR environment variable or configure your editor via knife.rb"
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/node_presenter.rb b/knife/lib/chef/knife/core/node_presenter.rb
new file mode 100644
index 0000000000..8c948cf76c
--- /dev/null
+++ b/knife/lib/chef/knife/core/node_presenter.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "text_formatter"
+require_relative "generic_presenter"
+
+class Chef
+ class Knife
+ module Core
+
+ # A customized presenter for Chef::Node objects. Supports variable-length
+ # output formats for displaying node data
+ class NodePresenter < GenericPresenter
+
+ def format(data)
+ if parse_format_option == :json
+ summarize_json(data)
+ else
+ super
+ end
+ end
+
+ def summarize_json(data)
+ if data.is_a?(Chef::Node)
+ node = data
+ result = {}
+
+ result["name"] = node.name
+ if node.policy_name.nil? && node.policy_group.nil?
+ result["chef_environment"] = node.chef_environment
+ else
+ result["policy_name"] = node.policy_name
+ result["policy_group"] = node.policy_group
+ end
+ result["run_list"] = node.run_list
+ result["normal"] = node.normal_attrs
+
+ if config[:long_output]
+ result["default"] = node.default_attrs
+ result["override"] = node.override_attrs
+ result["automatic"] = node.automatic_attrs
+ end
+
+ Chef::JSONCompat.to_json_pretty(result)
+ else
+ Chef::JSONCompat.to_json_pretty(data)
+ end
+ end
+
+ # Converts a Chef::Node object to a string suitable for output to a
+ # terminal. If config[:medium_output] or config[:long_output] are set
+ # the volume of output is adjusted accordingly. Uses colors if enabled
+ # in the ui object.
+ def summarize(data)
+ if data.is_a?(Chef::Node)
+ node = data
+ # special case clouds with their split horizon thing.
+ ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
+
+ summarized = <<~SUMMARY
+ #{ui.color("Node Name:", :bold)} #{ui.color(node.name, :bold)}
+ SUMMARY
+ show_policy = !(node.policy_name.nil? && node.policy_group.nil?)
+ if show_policy
+ summarized << <<~POLICY
+ #{key("Policy Name:")} #{node.policy_name}
+ #{key("Policy Group:")} #{node.policy_group}
+ POLICY
+ else
+ summarized << <<~ENV
+ #{key("Environment:")} #{node.chef_environment}
+ ENV
+ end
+ summarized << <<~SUMMARY
+ #{key("FQDN:")} #{node[:fqdn]}
+ #{key("IP:")} #{ip}
+ #{key("Run List:")} #{node.run_list}
+ SUMMARY
+ unless show_policy
+ summarized << <<~ROLES
+ #{key("Roles:")} #{Array(node[:roles]).join(", ")}
+ ROLES
+ end
+ summarized << <<~SUMMARY
+ #{key("Recipes:")} #{Array(node[:recipes]).join(", ")}
+ #{key("Platform:")} #{node[:platform]} #{node[:platform_version]}
+ #{key("Tags:")} #{node.tags.join(", ")}
+ SUMMARY
+ if config[:medium_output] || config[:long_output]
+ summarized += <<~MORE
+ #{key("Attributes:")}
+ #{text_format(node.normal_attrs)}
+ MORE
+ end
+ if config[:long_output]
+ summarized += <<~MOST
+ #{key("Default Attributes:")}
+ #{text_format(node.default_attrs)}
+ #{key("Override Attributes:")}
+ #{text_format(node.override_attrs)}
+ #{key("Automatic Attributes (Ohai Data):")}
+ #{text_format(node.automatic_attrs)}
+ MOST
+ end
+ summarized
+ else
+ super
+ end
+ end
+
+ def key(key_text)
+ ui.color(key_text, :cyan)
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/object_loader.rb b/knife/lib/chef/knife/core/object_loader.rb
new file mode 100644
index 0000000000..abfcb7b0cc
--- /dev/null
+++ b/knife/lib/chef/knife/core/object_loader.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+autoload :FFI_Yajl, "ffi_yajl"
+require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+require "chef/data_bag_item" unless defined?(Chef::DataBagItem)
+
+class Chef
+ class Knife
+ module Core
+ class ObjectLoader
+
+ attr_reader :ui
+ attr_reader :klass
+
+ class ObjectType
+ FILE = 1
+ FOLDER = 2
+ end
+
+ def initialize(klass, ui)
+ @klass = klass
+ @ui = ui
+ end
+
+ def load_from(repo_location, *components)
+ unless object_file = find_file(repo_location, *components)
+ ui.error "Could not find or open file '#{components.last}' in current directory or in '#{repo_location}/#{components.join("/")}'"
+ exit 1
+ end
+ object_from_file(object_file)
+ end
+
+ # When someone makes this awesome, please update the above error message.
+ def find_file(repo_location, *components)
+ if file_exists_and_is_readable?(File.expand_path( components.last ))
+ File.expand_path( components.last )
+ else
+ relative_path = File.join(Dir.pwd, repo_location, *components)
+ if file_exists_and_is_readable?(relative_path)
+ relative_path
+ else
+ nil
+ end
+ end
+ end
+
+ # Find all objects in the given location
+ # If the object type is File it will look for all *.{json,rb}
+ # files, otherwise it will lookup for folders only (useful for
+ # data_bags)
+ #
+ # @param [String] path - base look up location
+ #
+ # @return [Array<String>] basenames of the found objects
+ #
+ # @api public
+ def find_all_objects(path)
+ path = File.join(ChefConfig::PathHelper.escape_glob_dir(File.expand_path(path)), "*")
+ path << ".{json,rb}"
+ objects = Dir.glob(path)
+ objects.map { |o| File.basename(o) }
+ end
+
+ def find_all_object_dirs(path)
+ path = File.join(ChefConfig::PathHelper.escape_glob_dir(File.expand_path(path)), "*")
+ objects = Dir.glob(path)
+ objects.delete_if { |o| !File.directory?(o) }
+ objects.map { |o| File.basename(o) }
+ end
+
+ def object_from_file(filename)
+ case filename
+ when /\.(js|json)$/
+ r = FFI_Yajl::Parser.parse(IO.read(filename))
+
+ # Chef::DataBagItem doesn't work well with the json_create method
+ if @klass == Chef::DataBagItem
+ r
+ else
+ @klass.from_hash(r)
+ end
+ when /\.rb$/
+ r = klass.new
+ r.from_file(filename)
+ r
+ else
+ ui.fatal("File must end in .js, .json, or .rb")
+ exit 30
+ end
+ end
+
+ def file_exists_and_is_readable?(file)
+ File.exist?(file) && File.readable?(file)
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/status_presenter.rb b/knife/lib/chef/knife/core/status_presenter.rb
new file mode 100644
index 0000000000..271c71d618
--- /dev/null
+++ b/knife/lib/chef/knife/core/status_presenter.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
+# Copyright:: Copyright (c) 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 "text_formatter"
+require_relative "generic_presenter"
+
+class Chef
+ class Knife
+ module Core
+
+ # A customized presenter for Chef::Node objects. Supports variable-length
+ # output formats for displaying node data
+ class StatusPresenter < GenericPresenter
+
+ def format(data)
+ if parse_format_option == :json
+ summarize_json(data)
+ else
+ super
+ end
+ end
+
+ def summarize_json(list)
+ result_list = []
+ list.each do |node|
+ result = {}
+
+ result["name"] = node["name"] || node.name
+ result["chef_environment"] = node["chef_environment"]
+ ip = (node["cloud"] && node["cloud"]["public_ipv4_addrs"]&.first) || node["ipaddress"]
+ fqdn = (node["cloud"] && node["cloud"]["public_hostname"]) || node["fqdn"]
+ result["ip"] = ip if ip
+ result["fqdn"] = fqdn if fqdn
+ result["run_list"] = node.run_list if config["run_list"]
+ result["ohai_time"] = node["ohai_time"]
+ result["platform"] = node["platform"] if node["platform"]
+ result["platform_version"] = node["platform_version"] if node["platform_version"]
+
+ if config[:long_output]
+ result["default"] = node.default_attrs
+ result["override"] = node.override_attrs
+ result["automatic"] = node.automatic_attrs
+ end
+ result_list << result
+ end
+
+ Chef::JSONCompat.to_json_pretty(result_list)
+ end
+
+ # Converts a Chef::Node object to a string suitable for output to a
+ # terminal. If config[:medium_output] or config[:long_output] are set
+ # the volume of output is adjusted accordingly. Uses colors if enabled
+ # in the ui object.
+ def summarize(list)
+ summarized = ""
+ list.each do |data|
+ node = data
+ # special case clouds with their split horizon thing.
+ ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
+ fqdn = (node[:cloud] && node[:cloud][:public_hostname]) || node[:fqdn]
+ name = node["name"] || node.name
+
+ if config[:run_list]
+ if config[:long_output]
+ run_list = node.run_list.map { |rl| "#{rl.type}[#{rl.name}]" }
+ else
+ run_list = node["run_list"]
+ end
+ end
+
+ line_parts = []
+
+ if node["ohai_time"]
+ hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
+ hours_text = "#{hours} hour#{hours == 1 ? " " : "s"}"
+ minutes_text = "#{minutes} minute#{minutes == 1 ? " " : "s"}"
+ seconds_text = "#{seconds} second#{seconds == 1 ? " " : "s"}"
+ if hours > 24
+ color = :red
+ text = hours_text
+ elsif hours >= 1
+ color = :yellow
+ text = hours_text
+ elsif minutes >= 1
+ color = :green
+ text = minutes_text
+ else
+ color = :green
+ text = seconds_text
+ end
+ line_parts << @ui.color(text, color) + " ago" << name
+ else
+ line_parts << "Node #{name} has not yet converged"
+ end
+
+ line_parts << fqdn if fqdn
+ line_parts << ip if ip
+ line_parts << run_list.to_s if run_list
+
+ if node["platform"]
+ platform = node["platform"].dup
+ if node["platform_version"]
+ platform << " #{node["platform_version"]}"
+ end
+ line_parts << platform
+ end
+
+ summarized = summarized + line_parts.join(", ") + ".\n"
+ end
+ summarized
+ end
+
+ def key(key_text)
+ ui.color(key_text, :cyan)
+ end
+
+ # @private
+ # @todo this is duplicated from StatusHelper in the Webui. dedup.
+ def time_difference_in_hms(unix_time)
+ now = Time.now.to_i
+ difference = now - unix_time.to_i
+ hours = (difference / 3600).to_i
+ difference = difference % 3600
+ minutes = (difference / 60).to_i
+ seconds = (difference % 60)
+ [hours, minutes, seconds]
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/subcommand_loader.rb b/knife/lib/chef/knife/core/subcommand_loader.rb
new file mode 100644
index 0000000000..ca7bfcd008
--- /dev/null
+++ b/knife/lib/chef/knife/core/subcommand_loader.rb
@@ -0,0 +1,208 @@
+# Author:: Christopher Brown (<cb@chef.io>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "../version"
+require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+require "chef/run_list" unless defined?(Chef::RunList)
+require_relative "gem_glob_loader"
+require_relative "hashed_command_loader"
+
+class Chef
+ class Knife
+ #
+ # Public Methods of a Subcommand Loader
+ #
+ # load_commands - loads all available subcommands
+ # load_command(args) - loads subcommands for the given args
+ # list_commands(args) - lists all available subcommands,
+ # optionally filtering by category
+ # subcommand_files - returns an array of all subcommand files
+ # that could be loaded
+ # command_class_from(args) - returns the subcommand class for the
+ # user-requested command
+ #
+ class SubcommandLoader
+ attr_reader :chef_config_dir
+
+ # A small factory method. Eventually, this is the only place
+ # where SubcommandLoader should know about its subclasses, but
+ # to maintain backwards compatibility many of the instance
+ # methods in this base class contain default implementations
+ # of the functions sub classes should otherwise provide
+ # or directly instantiate the appropriate subclass
+ def self.for_config(chef_config_dir)
+ if autogenerated_manifest?
+ Chef::Log.trace("Using autogenerated hashed command manifest #{plugin_manifest_path}")
+ Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
+ else
+ Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+ end
+ end
+
+ # There are certain situations where we want to shortcut the loader selection
+ # in self.for_config and force using the GemGlobLoader
+ def self.gem_glob_loader(chef_config_dir)
+ Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+ end
+
+ def self.plugin_manifest?
+ plugin_manifest_path && File.exist?(plugin_manifest_path)
+ end
+
+ def self.autogenerated_manifest?
+ plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+ end
+
+ def self.plugin_manifest
+ Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
+ end
+
+ def self.plugin_manifest_path
+ ChefConfig::PathHelper.home(".chef", "plugin_manifest.json")
+ end
+
+ def self.generate_hash
+ output = if plugin_manifest?
+ plugin_manifest
+ else
+ { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {} }
+ end
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_paths"] = Chef::Knife.subcommand_files
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_by_category"] = Chef::Knife.subcommands_by_category
+ output
+ end
+
+ def self.write_hash(data)
+ plugin_manifest_dir = File.expand_path("..", plugin_manifest_path)
+ FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
+ File.open(plugin_manifest_path, "w") do |f|
+ f.write(Chef::JSONCompat.to_json_pretty(data))
+ end
+ end
+
+ def initialize(chef_config_dir)
+ @chef_config_dir = chef_config_dir
+ end
+
+ # Load all the sub-commands
+ def load_commands
+ return true if @loaded
+
+ subcommand_files.each { |subcommand| Kernel.load subcommand }
+ @loaded = true
+ end
+
+ def force_load
+ @loaded = false
+ load_commands
+ end
+
+ def load_command(_command_args)
+ load_commands
+ end
+
+ def list_commands(pref_cat = nil)
+ load_commands
+ if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
+ { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
+ else
+ Chef::Knife.subcommands_by_category
+ end
+ end
+
+ def command_class_from(args)
+ cmd_words = positional_arguments(args)
+ load_command(cmd_words)
+ result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
+ cmd_words, "_")]
+ result || Chef::Knife.subcommands[args.first.tr("-", "_")]
+ end
+
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split("-") }.flatten!
+ find_longest_key(Chef::Knife.subcommands_by_category,
+ category_words, " ")
+ end
+
+ #
+ # This is shared between the custom_manifest_loader and the gem_glob_loader
+ def find_subcommands_via_dirglob
+ # The "require paths" of the core knife subcommands bundled with chef
+ files = Dir[File.join(ChefConfig::PathHelper.escape_glob_dir(File.expand_path("../../knife", __dir__)), "*.rb")]
+ version_file_match = /#{Regexp.escape(File.join('chef', 'knife', 'version.rb'))}/
+ subcommand_files = {}
+ files.each do |knife_file|
+ rel_path = knife_file[/#{KNIFE_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1]
+ # Exclude version.rb file for the gem. It's not a knife command, and force-loading it later
+ # because loaded via in subcommand files generates CLI warnings about its consts already having been defined
+ next if knife_file =~ version_file_match
+
+ subcommand_files[rel_path] = knife_file
+ end
+ subcommand_files
+ end
+
+ #
+ # Utility function for finding an element in a hash given an array
+ # of words and a separator. We find the the longest key in the
+ # hash composed of the given words joined by the separator.
+ #
+ def find_longest_key(hash, words, sep = "_")
+ words = words.dup
+ match = nil
+ until match || words.empty?
+ candidate = words.join(sep).tr("-", "_")
+ if hash.key?(candidate)
+ match = candidate
+ else
+ words.pop
+ end
+ end
+ match
+ end
+
+ #
+ # The positional arguments from the argument list provided by the
+ # users. Used to search for subcommands and categories.
+ #
+ # @return [Array<String>]
+ #
+ def positional_arguments(args)
+ args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
+ end
+
+ # Returns an Array of paths to knife commands located in
+ # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
+ def site_subcommands
+ user_specific_files = []
+
+ if chef_config_dir
+ user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", ChefConfig::PathHelper.escape_glob_dir(chef_config_dir)))
+ end
+
+ # finally search ~/.chef/plugins/knife/*.rb
+ ChefConfig::PathHelper.home(".chef", "plugins", "knife") do |p|
+ user_specific_files.concat Dir.glob(File.join(ChefConfig::PathHelper.escape_glob_dir(p), "*.rb"))
+ end
+
+ user_specific_files
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/text_formatter.rb b/knife/lib/chef/knife/core/text_formatter.rb
index ec97748afb..ec97748afb 100644
--- a/lib/chef/knife/core/text_formatter.rb
+++ b/knife/lib/chef/knife/core/text_formatter.rb
diff --git a/knife/lib/chef/knife/core/ui.rb b/knife/lib/chef/knife/core/ui.rb
new file mode 100644
index 0000000000..782df1ca10
--- /dev/null
+++ b/knife/lib/chef/knife/core/ui.rb
@@ -0,0 +1,338 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Christopher Brown (<cb@chef.io>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "forwardable" unless defined?(Forwardable)
+require "chef/platform/query_helpers" # NOTE - this require doesn't defined any const we can check.
+require_relative "generic_presenter"
+require "tempfile" unless defined?(Tempfile)
+
+class Chef
+ class Knife
+
+ # The User Interaction class used by knife.
+ class UI
+
+ extend Forwardable
+
+ attr_reader :stdout
+ attr_reader :stderr
+ attr_reader :stdin
+ attr_reader :config
+
+ attr_reader :presenter
+
+ def_delegator :@presenter, :format_list_for_display
+ def_delegator :@presenter, :format_for_display
+ def_delegator :@presenter, :format_cookbook_list_for_display
+
+ def initialize(stdout, stderr, stdin, config)
+ @stdout, @stderr, @stdin, @config = stdout, stderr, stdin, config
+ @presenter = Chef::Knife::Core::GenericPresenter.new(self, config)
+ end
+
+ # Creates a new +presenter_class+ object and uses it to format structured
+ # data for display. By default, a Chef::Knife::Core::GenericPresenter
+ # object is used.
+ def use_presenter(presenter_class)
+ @presenter = presenter_class.new(self, config)
+ end
+
+ def highline
+ @highline ||= begin
+ require "highline"
+ HighLine.new
+ end
+ end
+
+ # Creates a new object of class TTY::Prompt
+ # with interrupt as exit so that it can be terminated with status code.
+ def prompt
+ @prompt ||= begin
+ require "tty-prompt"
+ TTY::Prompt.new(interrupt: :exit)
+ end
+ end
+
+ # pastel.decorate is a lightweight replacement for highline.color
+ def pastel
+ @pastel ||= begin
+ require "pastel" unless defined?(Pastel)
+ Pastel.new
+ end
+ end
+
+ # Prints a message to stdout. Aliased as +info+ for compatibility with
+ # the logger API.
+ #
+ # @param message [String] the text string
+ def msg(message)
+ stdout.puts message
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
+ end
+
+ # Prints a msg to stderr. Used for info, warn, error, and fatal.
+ #
+ # @param message [String] the text string
+ def log(message)
+ lines = message.split("\n")
+ first_line = lines.shift
+ stderr.puts first_line
+ # If the message is multiple lines,
+ # indent subsequent lines to align with the
+ # log type prefix ("ERROR: ", etc)
+ unless lines.empty?
+ prefix, = first_line.split(":", 2)
+ return if prefix.nil?
+
+ prefix_len = prefix.length
+ prefix_len -= 9 if color? # prefix includes 9 bytes of color escape sequences
+ prefix_len += 2 # include room to align to the ": " following PREFIX
+ padding = " " * prefix_len
+ lines.each do |line|
+ stderr.puts "#{padding}#{line}"
+ end
+ end
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
+ end
+
+ alias :info :log
+ alias :err :log
+
+ # Print a Debug
+ #
+ # @param message [String] the text string
+ def debug(message)
+ log("#{color("DEBUG:", :blue, :bold)} #{message}")
+ end
+
+ # Print a warning message
+ #
+ # @param message [String] the text string
+ def warn(message)
+ log("#{color("WARNING:", :yellow, :bold)} #{message}")
+ end
+
+ # Print an error message
+ #
+ # @param message [String] the text string
+ def error(message)
+ log("#{color("ERROR:", :red, :bold)} #{message}")
+ end
+
+ # Print a message describing a fatal error.
+ #
+ # @param message [String] the text string
+ def fatal(message)
+ log("#{color("FATAL:", :red, :bold)} #{message}")
+ end
+
+ # Print a message describing a fatal error and exit 1
+ #
+ # @param message [String] the text string
+ def fatal!(message)
+ fatal(message)
+ exit 1
+ end
+
+ def color(string, *colors)
+ if color?
+ pastel.decorate(string, *colors)
+ else
+ string
+ end
+ end
+
+ # Should colored output be used? For output to a terminal, this is
+ # determined by the value of `config[:color]`. When output is not to a
+ # terminal, colored output is never used
+ def color?
+ Chef::Config[:color] && stdout.tty?
+ end
+
+ def ask(*args, **options, &block)
+ prompt.ask(*args, **options, &block)
+ end
+
+ def list(*args)
+ highline.list(*args)
+ end
+
+ # Formats +data+ using the configured presenter and outputs the result
+ # via +msg+. Formatting can be customized by configuring a different
+ # presenter. See +use_presenter+
+ def output(data)
+ msg @presenter.format(data)
+ end
+
+ # Determines if the output format is a data interchange format, i.e.,
+ # JSON or YAML
+ def interchange?
+ @presenter.interchange?
+ end
+
+ def ask_question(question, opts = {})
+ question += "[#{opts[:default]}] " if opts[:default]
+
+ if opts[:default] && config[:defaults]
+ opts[:default]
+ else
+ stdout.print question
+ a = stdin.readline.strip
+
+ if opts[:default]
+ a.empty? ? opts[:default] : a
+ else
+ a
+ end
+ end
+ end
+
+ def pretty_print(data)
+ stdout.puts data
+ rescue Errno::EPIPE => e
+ raise e if @config[:verbosity] >= 2
+
+ exit 0
+ end
+
+ # Hash -> Hash
+ # Works the same as edit_data but
+ # returns a hash rather than a JSON string/Fully inflated object
+ def edit_hash(hash)
+ raw = edit_data(hash, false)
+ Chef::JSONCompat.parse(raw)
+ end
+
+ def edit_data(data, parse_output = true, object_class: nil)
+ output = Chef::JSONCompat.to_json_pretty(data)
+ unless config[:disable_editing]
+ Tempfile.open([ "knife-edit-", ".json" ]) do |tf|
+ tf.sync = true
+ tf.puts output
+ tf.close
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details." unless system("#{config[:editor]} #{tf.path}")
+
+ output = IO.read(tf.path)
+ end
+ end
+
+ if parse_output
+ if object_class.nil?
+ raise ArgumentError, "Please pass in the object class to hydrate or use #edit_hash"
+ else
+ object_class.from_hash(Chef::JSONCompat.parse(output))
+ end
+ else
+ output
+ end
+ end
+
+ def edit_object(klass, name)
+ object = klass.load(name)
+
+ output = edit_data(object, object_class: klass)
+
+ # Only make the save if the user changed the object.
+ #
+ # Output JSON for the original (object) and edited (output), then parse
+ # them without reconstituting the objects into real classes
+ # (create_additions=false). Then, compare the resulting simple objects,
+ # which will be Array/Hash/String/etc.
+ #
+ # We wouldn't have to do these shenanigans if all the editable objects
+ # implemented to_hash, or if to_json against a hash returned a string
+ # with stable key order.
+ object_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(object))
+ output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output))
+ if object_parsed_again != output_parsed_again
+ output.save
+ msg("Saved #{output}")
+ else
+ msg("Object unchanged, not saving")
+ end
+ output(format_for_display(object)) if config[:print_after]
+ end
+
+ def confirmation_instructions(default_choice)
+ case default_choice
+ when true
+ "? (Y/n) "
+ when false
+ "? (y/N) "
+ else
+ "? (Y/N) "
+ end
+ end
+
+ # See confirm method for argument information
+ def confirm_without_exit(question, append_instructions = true, default_choice = nil)
+ return true if config[:yes]
+
+ stdout.print question
+ stdout.print confirmation_instructions(default_choice) if append_instructions
+
+ answer = stdin.readline
+ answer.chomp!
+
+ case answer
+ when "Y", "y"
+ true
+ when "N", "n"
+ msg("You said no, so I'm done here.")
+ false
+ when ""
+ unless default_choice.nil?
+ default_choice
+ else
+ msg("I have no idea what to do with '#{answer}'")
+ msg("Just say Y or N, please.")
+ confirm_without_exit(question, append_instructions, default_choice)
+ end
+ else
+ msg("I have no idea what to do with '#{answer}'")
+ msg("Just say Y or N, please.")
+ confirm_without_exit(question, append_instructions, default_choice)
+ end
+ end
+
+ #
+ # Not the ideal signature for a function but we need to stick with this
+ # for now until we get a chance to break our API in Chef 12.
+ #
+ # question => Question to print before asking for confirmation
+ # append_instructions => Should print '? (Y/N)' as instructions
+ # default_choice => Set to true for 'Y', and false for 'N' as default answer
+ #
+ def confirm(question, append_instructions = true, default_choice = nil)
+ unless confirm_without_exit(question, append_instructions, default_choice)
+ exit 3
+ end
+ true
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/core/windows_bootstrap_context.rb b/knife/lib/chef/knife/core/windows_bootstrap_context.rb
new file mode 100644
index 0000000000..2904a231a5
--- /dev/null
+++ b/knife/lib/chef/knife/core/windows_bootstrap_context.rb
@@ -0,0 +1,405 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Copyright:: Copyright (c) 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 "bootstrap_context"
+require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ module Core
+ # Instances of BootstrapContext are the context objects (i.e., +self+) for
+ # bootstrap templates. For backwards compatibility, they +must+ set the
+ # following instance variables:
+ # * @config - a hash of knife's config values
+ # * @run_list - the run list for the node to bootstrap
+ #
+ class WindowsBootstrapContext < BootstrapContext
+ attr_accessor :config
+ attr_accessor :chef_config
+ attr_accessor :secret
+
+ def initialize(config, run_list, chef_config, secret = nil)
+ @config = config
+ @run_list = run_list
+ @chef_config = chef_config
+ @secret = secret
+ super(config, run_list, chef_config, secret)
+ end
+
+ def validation_key
+ if File.exist?(File.expand_path(chef_config[:validation_key]))
+ IO.read(File.expand_path(chef_config[:validation_key]))
+ else
+ false
+ end
+ end
+
+ def encrypted_data_bag_secret
+ escape_and_echo(@secret)
+ end
+
+ def trusted_certs_script
+ @trusted_certs_script ||= trusted_certs_content
+ end
+
+ def config_content
+ # The windows: true / windows: false in the block that follows is more than a bit weird. The way to read this is that we need
+ # the e.g. var_chef_dir to be rendered for the windows value ("C:\chef"), but then we are rendering into a file to be read by
+ # ruby, so we don't actually care about forward-vs-backslashes and by rendering into unix we avoid having to deal with the
+ # double-backwhacking of everything. So we expect to see:
+ #
+ # file_cache_path "C:/chef"
+ #
+ # Which is mildly odd, but should be entirely correct as far as ruby cares.
+ #
+ client_rb = <<~CONFIG
+ chef_server_url "#{chef_config[:chef_server_url]}"
+ validation_client_name "#{chef_config[:validation_client_name]}"
+ file_cache_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\cache"
+ file_backup_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\backup"
+ cache_options ({:path => "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\cache\\\\checksums", :skip_expires => true})
+ CONFIG
+
+ unless chef_config[:chef_license].nil?
+ client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
+ end
+
+ if config[:chef_node_name]
+ client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
+ else
+ client_rb << "# Using default node name (fqdn)\n"
+ end
+
+ if config[:config_log_level]
+ client_rb << %Q{log_level :#{config[:config_log_level]}\n}
+ else
+ client_rb << "log_level :auto\n"
+ end
+
+ client_rb << "log_location #{get_log_location}"
+
+ # We configure :verify_api_cert only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
+ value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
+ client_rb << %Q{verify_api_cert #{value}\n}
+ end
+
+ # We configure :ssl_verify_mode only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if config[:node_ssl_verify_mode] || config.key?(:ssl_verify_mode)
+ value = case config[:node_ssl_verify_mode]
+ when "peer"
+ :verify_peer
+ when "none"
+ :verify_none
+ when nil
+ config[:ssl_verify_mode]
+ else
+ nil
+ end
+
+ if value
+ client_rb << %Q{ssl_verify_mode :#{value}\n}
+ end
+ end
+
+ if config[:ssl_verify_mode]
+ client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
+ end
+
+ if config[:bootstrap_proxy]
+ client_rb << "\n"
+ client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n} if config[:bootstrap_no_proxy]
+ end
+
+ if config[:bootstrap_no_proxy]
+ client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
+ end
+
+ if secret
+ client_rb << %Q{encrypted_data_bag_secret "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\encrypted_data_bag_secret"\n}
+ end
+
+ unless trusted_certs_script.empty?
+ client_rb << %Q{trusted_certs_dir "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\trusted_certs"\n}
+ end
+
+ if chef_config[:fips]
+ client_rb << "fips true\n"
+ end
+
+ escape_and_echo(client_rb)
+ end
+
+ def get_log_location
+ if chef_config[:config_log_location].equal?(:win_evt)
+ %Q{:#{chef_config[:config_log_location]}\n}
+ elsif chef_config[:config_log_location].equal?(:syslog)
+ raise "syslog is not supported for log_location on Windows OS\n"
+ elsif chef_config[:config_log_location].equal?(STDOUT)
+ "STDOUT\n"
+ elsif chef_config[:config_log_location].equal?(STDERR)
+ "STDERR\n"
+ elsif chef_config[:config_log_location].nil? || chef_config[:config_log_location].empty?
+ "STDOUT\n"
+ elsif chef_config[:config_log_location]
+ %Q{"#{chef_config[:config_log_location]}"\n}
+ else
+ "STDOUT\n"
+ end
+ end
+
+ def start_chef
+ c_opscode_dir = ChefConfig::PathHelper.cleanpath(ChefConfig::Config.c_opscode_dir, windows: true)
+ client_rb = clean_etc_chef_file("client.rb")
+ first_boot = clean_etc_chef_file("first-boot.json")
+
+ bootstrap_environment_option = bootstrap_environment.nil? ? "" : " -E #{bootstrap_environment}"
+
+ start_chef = "SET \"PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;#{c_opscode_dir}\\bin;#{c_opscode_dir}\\embedded\\bin\;%PATH%\"\n"
+ start_chef << "#{ChefUtils::Dist::Infra::CLIENT} -c #{client_rb} -j #{first_boot}#{bootstrap_environment_option}\n"
+ end
+
+ def win_wget
+ # I tried my best to figure out how to properly url decode and switch / to \
+ # but this is VBScript - so I don't really care that badly.
+ win_wget = <<~WGET
+ url = WScript.Arguments.Named("url")
+ path = WScript.Arguments.Named("path")
+ proxy = null
+ '* Vaguely attempt to handle file:// scheme urls by url unescaping and switching all
+ '* / into \. Also assume that file:/// is a local absolute path and that file://<foo>
+ '* is possibly a network file path.
+ If InStr(url, "file://") = 1 Then
+ url = Unescape(url)
+ If InStr(url, "file:///") = 1 Then
+ sourcePath = Mid(url, Len("file:///") + 1)
+ Else
+ sourcePath = Mid(url, Len("file:") + 1)
+ End If
+ sourcePath = Replace(sourcePath, "/", "\\")
+
+ Set objFSO = CreateObject("Scripting.FileSystemObject")
+ If objFSO.Fileexists(path) Then objFSO.DeleteFile path
+ objFSO.CopyFile sourcePath, path, true
+ Set objFSO = Nothing
+
+ Else
+ Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP")
+ Set wshShell = CreateObject( "WScript.Shell" )
+ Set objUserVariables = wshShell.Environment("USER")
+
+ rem http proxy is optional
+ rem attempt to read from HTTP_PROXY env var first
+ On Error Resume Next
+
+ If NOT (objUserVariables("HTTP_PROXY") = "") Then
+ proxy = objUserVariables("HTTP_PROXY")
+
+ rem fall back to named arg
+ ElseIf NOT (WScript.Arguments.Named("proxy") = "") Then
+ proxy = WScript.Arguments.Named("proxy")
+ End If
+
+ If NOT isNull(proxy) Then
+ rem setProxy method is only available on ServerXMLHTTP 6.0+
+ Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP.6.0")
+ objXMLHTTP.setProxy 2, proxy
+ End If
+
+ On Error Goto 0
+
+ objXMLHTTP.open "GET", url, false
+ objXMLHTTP.send()
+ If objXMLHTTP.Status = 200 Then
+ Set objADOStream = CreateObject("ADODB.Stream")
+ objADOStream.Open
+ objADOStream.Type = 1
+ objADOStream.Write objXMLHTTP.ResponseBody
+ objADOStream.Position = 0
+ Set objFSO = Createobject("Scripting.FileSystemObject")
+ If objFSO.Fileexists(path) Then objFSO.DeleteFile path
+ Set objFSO = Nothing
+ objADOStream.SaveToFile path
+ objADOStream.Close
+ Set objADOStream = Nothing
+ End If
+ Set objXMLHTTP = Nothing
+ End If
+ WGET
+ escape_and_echo(win_wget)
+ end
+
+ def win_wget_ps
+ win_wget_ps = <<~WGET_PS
+ param(
+ [String] $remoteUrl,
+ [String] $localPath
+ )
+
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+ $ProxyUrl = $env:http_proxy;
+ $webClient = new-object System.Net.WebClient;
+
+ if ($ProxyUrl -ne '') {
+ $WebProxy = New-Object System.Net.WebProxy($ProxyUrl,$true)
+ $WebClient.Proxy = $WebProxy
+ }
+
+ $webClient.DownloadFile($remoteUrl, $localPath);
+ WGET_PS
+
+ escape_and_echo(win_wget_ps)
+ end
+
+ def install_chef
+ # The normal install command uses regular double quotes in
+ # the install command, so request such a string from install_command
+ install_command('"') + "\n" + fallback_install_task_command
+ end
+
+ def clean_etc_chef_file(path)
+ ChefConfig::PathHelper.cleanpath(etc_chef_file(path), windows: true)
+ end
+
+ def etc_chef_file(path)
+ "#{bootstrap_directory}/#{path}"
+ end
+
+ def bootstrap_directory
+ ChefConfig::Config.etc_chef_dir(windows: true)
+ end
+
+ def local_download_path
+ "%TEMP%\\#{ChefUtils::Dist::Infra::CLIENT}-latest.msi"
+ end
+
+ # Build a URL that will redirect to the correct Chef Infra msi download.
+ def msi_url(machine_os = nil, machine_arch = nil, download_context = nil)
+ if config[:msi_url].nil? || config[:msi_url].empty?
+ url = "https://omnitruck.chef.io/chef/download?p=windows"
+ url += "&pv=#{machine_os}" unless machine_os.nil?
+ url += "&m=#{machine_arch}" unless machine_arch.nil?
+ url += "&DownloadContext=#{download_context}" unless download_context.nil?
+ url += "&channel=#{config[:channel]}"
+ url += "&v=#{version_to_install}"
+ else
+ config[:msi_url]
+ end
+ end
+
+ def first_boot
+ escape_and_echo(super.to_json)
+ end
+
+ # escape WIN BATCH special chars
+ # and prefixes each line with an
+ # echo
+ def escape_and_echo(file_contents)
+ file_contents.gsub(/^(.*)$/, 'echo.\1').gsub(/([(<|>)^])/, '^\1')
+ end
+
+ private
+
+ def install_command(executor_quote)
+ "msiexec /qn /log #{executor_quote}%CHEF_CLIENT_MSI_LOG_PATH%#{executor_quote} /i #{executor_quote}%LOCAL_DESTINATION_MSI_PATH%#{executor_quote}"
+ end
+
+ # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
+ # This string should contain both the commands necessary to both create the files, as well as their content
+ def trusted_certs_content
+ content = ""
+ if chef_config[:trusted_certs_dir]
+ Dir.glob(File.join(ChefConfig::PathHelper.escape_glob_dir(chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
+ content << "> #{bootstrap_directory}/trusted_certs/#{File.basename(cert)} (\n" +
+ escape_and_echo(IO.read(File.expand_path(cert))) + "\n)\n"
+ end
+ end
+ content
+ end
+
+ def client_d_content
+ content = ""
+ if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
+ root = Pathname(chef_config[:client_d_dir])
+ root.find do |f|
+ relative = f.relative_path_from(root)
+ if f != root
+ file_on_node = "#{bootstrap_directory}/client.d/#{relative}".tr("/", "\\")
+ if f.directory?
+ content << "mkdir #{file_on_node}\n"
+ else
+ content << "> #{file_on_node} (\n" +
+ escape_and_echo(IO.read(File.expand_path(f))) + "\n)\n"
+ end
+ end
+ end
+ end
+ content
+ end
+
+ def fallback_install_task_command
+ # This command will be executed by schtasks.exe in the batch
+ # code below. To handle tasks that contain arguments that
+ # need to be double quoted, schtasks allows the use of single
+ # quotes that will later be converted to double quotes
+ command = install_command("'")
+ <<~EOH
+ @set MSIERRORCODE=!ERRORLEVEL!
+ @if ERRORLEVEL 1 (
+ @echo WARNING: Failed to install #{ChefUtils::Dist::Infra::PRODUCT} MSI package in remote context with status code !MSIERRORCODE!.
+ @echo WARNING: This may be due to a defect in operating system update KB2918614: http://support.microsoft.com/kb/2918614
+ @set OLDLOGLOCATION="%CHEF_CLIENT_MSI_LOG_PATH%-fail.log"
+ @move "%CHEF_CLIENT_MSI_LOG_PATH%" "!OLDLOGLOCATION!" > NUL
+ @echo WARNING: Saving installation log of failure at !OLDLOGLOCATION!
+ @echo WARNING: Retrying installation with local context...
+ @schtasks /create /f /sc once /st 00:00:00 /tn chefclientbootstraptask /ru SYSTEM /rl HIGHEST /tr \"cmd /c #{command} & sleep 2 & waitfor /s %computername% /si chefclientinstalldone\"
+
+ @if ERRORLEVEL 1 (
+ @echo ERROR: Failed to create #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL! > "&2"
+ ) else (
+ @echo Successfully created scheduled task to install #{ChefUtils::Dist::Infra::PRODUCT}.
+ @schtasks /run /tn chefclientbootstraptask
+ @if ERRORLEVEL 1 (
+ @echo ERROR: Failed to execute #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL!. > "&2"
+ ) else (
+ @echo Successfully started #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task.
+ @echo Waiting for installation to complete -- this may take a few minutes...
+ waitfor chefclientinstalldone /t 600
+ if ERRORLEVEL 1 (
+ @echo ERROR: Timed out waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install
+ ) else (
+ @echo Finished waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install.
+ )
+ @schtasks /delete /f /tn chefclientbootstraptask > NUL
+ )
+ )
+ ) else (
+ @echo Successfully installed #{ChefUtils::Dist::Infra::PRODUCT} package.
+ )
+ EOH
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_create.rb b/knife/lib/chef/knife/data_bag_create.rb
new file mode 100644
index 0000000000..a8a9caf7e4
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_create.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "data_bag_secret_options"
+
+class Chef
+ class Knife
+ class DataBagCreate < Knife
+ include DataBagSecretOptions
+
+ deps do
+ require "chef/data_bag" unless defined?(Chef::DataBag)
+ require "chef/encrypted_data_bag_item" unless defined?(Chef::EncryptedDataBagItem)
+ end
+
+ banner "knife data bag create BAG [ITEM] (options)"
+ category "data bag"
+
+ def run
+ @data_bag_name, @data_bag_item_name = @name_args
+
+ if @data_bag_name.nil?
+ show_usage
+ ui.fatal("You must specify a data bag name")
+ exit 1
+ end
+
+ begin
+ Chef::DataBag.validate_name!(@data_bag_name)
+ rescue Chef::Exceptions::InvalidDataBagName => e
+ ui.fatal(e.message)
+ exit(1)
+ end
+
+ # Verify if the data bag exists
+ begin
+ rest.get("data/#{@data_bag_name}")
+ ui.info("Data bag #{@data_bag_name} already exists")
+ rescue Net::HTTPClientException => e
+ raise unless /^404/.match?(e.to_s)
+
+ # if it doesn't exists, try to create it
+ rest.post("data", { "name" => @data_bag_name })
+ ui.info("Created data_bag[#{@data_bag_name}]")
+ end
+
+ # if an item is specified, create it, as well
+ if @data_bag_item_name
+ create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output|
+ item = Chef::DataBagItem.from_hash(
+ if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
+ else
+ output
+ end
+ )
+ item.data_bag(@data_bag_name)
+ rest.post("data/#{@data_bag_name}", item)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_delete.rb b/knife/lib/chef/knife/data_bag_delete.rb
new file mode 100644
index 0000000000..a7b5a4b6fd
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_delete.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class DataBagDelete < Knife
+
+ deps do
+ require "chef/data_bag" unless defined?(Chef::DataBag)
+ end
+
+ banner "knife data bag delete BAG [ITEM] (options)"
+ category "data bag"
+
+ def run
+ if @name_args.length == 2
+ delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do
+ rest.delete("data/#{@name_args[0]}/#{@name_args[1]}")
+ end
+ elsif @name_args.length == 1
+ delete_object(Chef::DataBag, @name_args[0], "data_bag") do
+ rest.delete("data/#{@name_args[0]}")
+ end
+ else
+ show_usage
+ ui.fatal("You must specify at least a data bag name")
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_edit.rb b/knife/lib/chef/knife/data_bag_edit.rb
new file mode 100644
index 0000000000..92bff8d7f7
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_edit.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "data_bag_secret_options"
+
+class Chef
+ class Knife
+ class DataBagEdit < Knife
+ include DataBagSecretOptions
+
+ deps do
+ require "chef/data_bag_item" unless defined?(Chef::DataBagItem)
+ require "chef/encrypted_data_bag_item" unless defined?(Chef::EncryptedDataBagItem)
+ end
+
+ banner "knife data bag edit BAG ITEM (options)"
+ category "data bag"
+
+ def load_item(bag, item_name)
+ item = Chef::DataBagItem.load(bag, item_name)
+ if encrypted?(item.raw_data)
+ if encryption_secret_provided_ignore_encrypt_flag?
+ [Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true]
+ else
+ ui.fatal("You cannot edit an encrypted data bag without providing the secret.")
+ exit(1)
+ end
+ else
+ [item.raw_data, false]
+ end
+ end
+
+ def run
+ if @name_args.length != 2
+ stdout.puts "You must supply the data bag and an item to edit"
+ stdout.puts opt_parser
+ exit 1
+ end
+
+ item, was_encrypted = load_item(@name_args[0], @name_args[1])
+ edited_item = edit_hash(item)
+
+ if was_encrypted || encryption_secret_provided?
+ ui.info("Encrypting data bag using provided secret.")
+ item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret)
+ else
+ ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.")
+ item_to_save = edited_item
+ end
+
+ rest.put("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
+ stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
+ ui.output(edited_item) if config[:print_after]
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_from_file.rb b/knife/lib/chef/knife/data_bag_from_file.rb
new file mode 100644
index 0000000000..6c889e1927
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_from_file.rb
@@ -0,0 +1,113 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "data_bag_secret_options"
+
+class Chef
+ class Knife
+ class DataBagFromFile < Knife
+ include DataBagSecretOptions
+
+ deps do
+ require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+ require "chef/data_bag" unless defined?(Chef::DataBag)
+ require "chef/data_bag_item" unless defined?(Chef::DataBagItem)
+ require "chef/encrypted_data_bag_item" unless defined?(Chef::EncryptedDataBagItem)
+ require_relative "core/object_loader"
+ end
+
+ banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
+ category "data bag"
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Upload all data bags or all items for specified data bags."
+
+ def loader
+ @loader ||= Knife::Core::ObjectLoader.new(Chef::DataBagItem, ui)
+ end
+
+ def run
+ if config[:all] == true
+ load_all_data_bags(@name_args)
+ else
+ if @name_args.size < 2
+ ui.msg(opt_parser)
+ exit(1)
+ end
+ @data_bag = @name_args.shift
+ load_data_bag_items(@data_bag, @name_args)
+ end
+ end
+
+ private
+
+ def data_bags_path
+ @data_bag_path ||= "data_bags"
+ end
+
+ def find_all_data_bags
+ loader.find_all_object_dirs("./#{data_bags_path}")
+ end
+
+ def find_all_data_bag_items(data_bag)
+ loader.find_all_objects("./#{data_bags_path}/#{data_bag}")
+ end
+
+ def load_all_data_bags(args)
+ data_bags = args.empty? ? find_all_data_bags : [args.shift]
+ data_bags.each do |data_bag|
+ load_data_bag_items(data_bag)
+ end
+ end
+
+ def load_data_bag_items(data_bag, items = nil)
+ items ||= find_all_data_bag_items(data_bag)
+ item_paths = normalize_item_paths(items)
+ item_paths.each do |item_path|
+ item = loader.load_from((data_bags_path).to_s, data_bag, item_path)
+ item = if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret)
+ else
+ item
+ end
+ dbag = Chef::DataBagItem.new
+ dbag.data_bag(data_bag)
+ dbag.raw_data = item
+ dbag.save
+ ui.info("Updated data_bag_item[#{dbag.data_bag}::#{dbag.id}]")
+ end
+ end
+
+ def normalize_item_paths(args)
+ paths = []
+ args.each do |path|
+ if File.directory?(path)
+ paths.concat(Dir.glob(File.join(ChefConfig::PathHelper.escape_glob_dir(path), "*.json")))
+ else
+ paths << path
+ end
+ end
+ paths
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_list.rb b/knife/lib/chef/knife/data_bag_list.rb
new file mode 100644
index 0000000000..8a8e7ba89d
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_list.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class DataBagList < Knife
+
+ deps do
+ require "chef/data_bag" unless defined?(Chef::DataBag)
+ end
+
+ banner "knife data bag list (options)"
+ category "data bag"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ output(format_list_for_display(Chef::DataBag.list))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_secret_options.rb b/knife/lib/chef/knife/data_bag_secret_options.rb
new file mode 100644
index 0000000000..4d8ba90929
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_secret_options.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Tyler Ball (<tball@chef.io>)
+# Copyright:: Copyright (c) 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 "mixlib/cli" unless defined?(Mixlib::CLI)
+require "chef/config" unless defined?(Chef::Config)
+require "encrypted_data_bag_item/check_encrypted" unless defined?(Chef::EncryptedDataBagItem::CheckEncrypted)
+
+class Chef
+ class Knife
+ module DataBagSecretOptions
+ include Mixlib::CLI
+ include Chef::EncryptedDataBagItem::CheckEncrypted
+
+ # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do
+ # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file`
+ # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate
+ # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret`
+ # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they
+ # are provided.
+
+ def self.included(base)
+ base.option :cl_secret,
+ long: "--secret SECRET",
+ description: "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'."
+
+ base.option :cl_secret_file,
+ long: "--secret-file SECRET_FILE",
+ description: "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'."
+
+ base.option :encrypt,
+ long: "--encrypt",
+ description: "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it.",
+ boolean: true,
+ default: false
+ end
+
+ def encryption_secret_provided?
+ base_encryption_secret_provided?
+ end
+
+ def encryption_secret_provided_ignore_encrypt_flag?
+ base_encryption_secret_provided?(false)
+ end
+
+ def read_secret
+ # Moving the non 'compile-time' requires into here to speed up knife command loading
+ # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
+ require "chef/encrypted_data_bag_item" unless defined?(Chef::EncryptedDataBagItem)
+
+ if config[:cl_secret]
+ config[:cl_secret]
+ elsif config[:cl_secret_file]
+ Chef::EncryptedDataBagItem.load_secret(config[:cl_secret_file])
+ elsif secret = config[:secret]
+ secret
+ else
+ secret_file = config[:secret_file]
+ Chef::EncryptedDataBagItem.load_secret(secret_file)
+ end
+ end
+
+ def validate_secrets
+ if config[:cl_secret] && config[:cl_secret_file]
+ ui.fatal("Please specify only one of --secret, --secret-file")
+ exit(1)
+ end
+
+ if config[:secret] && config[:secret_file]
+ ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
+ exit(1)
+ end
+ end
+
+ private
+
+ ##
+ # Determine if the user has specified an appropriate secret for encrypting data bag items.
+ # @return boolean
+ def base_encryption_secret_provided?(need_encrypt_flag = true)
+ validate_secrets
+
+ return true if config[:cl_secret] || config[:cl_secret_file]
+
+ if need_encrypt_flag
+ if config[:encrypt]
+ unless config[:secret] || config[:secret_file]
+ ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
+ exit(1)
+ end
+ return true
+ end
+ return false
+ elsif config[:secret] || config[:secret_file]
+ # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
+ return true
+ end
+ false
+ end
+
+ def knife_config
+ Chef.deprecated(:knife_bootstrap_apis, "The `knife_config` bootstrap helper has been deprecated, use the properly merged `config` helper instead")
+ Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/data_bag_show.rb b/knife/lib/chef/knife/data_bag_show.rb
new file mode 100644
index 0000000000..3270f45ee2
--- /dev/null
+++ b/knife/lib/chef/knife/data_bag_show.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "data_bag_secret_options"
+
+class Chef
+ class Knife
+ class DataBagShow < Knife
+ include DataBagSecretOptions
+
+ deps do
+ require "chef/data_bag" unless defined?(Chef::DataBag)
+ require "chef/encrypted_data_bag_item" unless defined?(Chef::EncryptedDataBagItem)
+ end
+
+ banner "knife data bag show BAG [ITEM] (options)"
+ category "data bag"
+
+ def run
+ display = case @name_args.length
+ when 2 # Bag and Item names provided
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
+ encrypted = encrypted?(raw_data)
+
+ if encrypted && secret
+ # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
+ ui.info("Encrypted data bag detected, decrypting with provided secret.")
+ raw = Chef::EncryptedDataBagItem.load(@name_args[0],
+ @name_args[1],
+ secret)
+ format_for_display(raw.to_h)
+ elsif encrypted && !secret
+ ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+ format_for_display(raw_data)
+ else
+ ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") if secret
+ format_for_display(raw_data)
+ end
+
+ when 1 # Only Bag name provided
+ format_list_for_display(Chef::DataBag.load(@name_args[0]))
+ else
+ stdout.puts opt_parser
+ exit(1)
+ end
+ output(display)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/delete.rb b/knife/lib/chef/knife/delete.rb
new file mode 100644
index 0000000000..2853efc21c
--- /dev/null
+++ b/knife/lib/chef/knife/delete.rb
@@ -0,0 +1,125 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Delete < Chef::ChefFS::Knife
+ banner "knife delete [PATTERN1 ... PATTERNn]"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ end
+
+ option :recurse,
+ short: "-r",
+ long: "--[no-]recurse",
+ boolean: true,
+ default: false,
+ description: "Delete directories recursively."
+
+ option :both,
+ long: "--both",
+ boolean: true,
+ default: false,
+ description: "Delete both the local and remote copies."
+
+ option :local,
+ long: "--local",
+ boolean: true,
+ default: false,
+ description: "Delete the local copy (leave the remote copy)."
+
+ def run
+ if name_args.length == 0
+ show_usage
+ ui.fatal("You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"")
+ exit 1
+ end
+
+ # Get the matches (recursively)
+ error = false
+ if config[:local]
+ pattern_args.each do |pattern|
+ Chef::ChefFS::FileSystem.list(local_fs, pattern).each do |result|
+ if delete_result(result)
+ error = true
+ end
+ end
+ end
+ elsif config[:both]
+ pattern_args.each do |pattern|
+ Chef::ChefFS::FileSystem.list_pairs(pattern, chef_fs, local_fs).each do |chef_result, local_result|
+ if delete_result(chef_result, local_result)
+ error = true
+ end
+ end
+ end
+ else # Remote only
+ pattern_args.each do |pattern|
+ Chef::ChefFS::FileSystem.list(chef_fs, pattern).each do |result|
+ if delete_result(result)
+ error = true
+ end
+ end
+ end
+ end
+
+ if error
+ exit 1
+ end
+ end
+
+ def format_path_with_root(entry)
+ root = entry.root == chef_fs ? " (remote)" : " (local)"
+ "#{format_path(entry)}#{root}"
+ end
+
+ def delete_result(*results)
+ deleted_any = false
+ found_any = false
+ error = false
+ results.each do |result|
+
+ result.delete(config[:recurse])
+ deleted_any = true
+ found_any = true
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ # This is not an error unless *all* of them were not found
+ rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e
+ ui.error "#{format_path_with_root(e.entry)} must be deleted recursively! Pass -r to knife delete."
+ found_any = true
+ error = true
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path_with_root(e.entry)} #{e.reason}."
+ found_any = true
+ error = true
+
+ end
+ if deleted_any
+ output("Deleted #{format_path(results[0])}")
+ elsif !found_any
+ ui.error "#{format_path(results[0])}: No such file or directory"
+ error = true
+ end
+ error
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/deps.rb b/knife/lib/chef/knife/deps.rb
new file mode 100644
index 0000000000..fd419f15f9
--- /dev/null
+++ b/knife/lib/chef/knife/deps.rb
@@ -0,0 +1,156 @@
+#
+# 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 "../chef_fs/knife" unless defined?(Chef::ChefFS::Knife)
+
+class Chef
+ class Knife
+ class Deps < Chef::ChefFS::Knife
+ banner "knife deps PATTERN1 [PATTERNn]"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "chef/run_list" unless defined?(Chef::RunList)
+ end
+
+ option :recurse,
+ long: "--[no-]recurse",
+ boolean: true,
+ description: "List dependencies recursively (default: true). Only works with --tree."
+
+ option :tree,
+ long: "--tree",
+ boolean: true,
+ description: "Show dependencies in a visual tree. May show duplicates."
+
+ option :remote,
+ long: "--remote",
+ boolean: true,
+ description: "List dependencies on the server instead of the local filesystem."
+
+ attr_accessor :exit_code
+
+ def run
+ if config[:recurse] == false && !config[:tree]
+ ui.error "--no-recurse requires --tree"
+ exit(1)
+ end
+ config[:recurse] = true if config[:recurse].nil?
+
+ @root = config[:remote] ? chef_fs : local_fs
+ dependencies = {}
+ pattern_args.each do |pattern|
+ Chef::ChefFS::FileSystem.list(@root, pattern).each do |entry|
+ if config[:tree]
+ print_dependencies_tree(entry, dependencies)
+ else
+ print_flattened_dependencies(entry, dependencies)
+ end
+ end
+ end
+ exit exit_code if exit_code
+ end
+
+ def print_flattened_dependencies(entry, dependencies)
+ unless dependencies[entry.path]
+ dependencies[entry.path] = get_dependencies(entry)
+ dependencies[entry.path].each do |child|
+ child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
+ print_flattened_dependencies(child_entry, dependencies)
+ end
+ output format_path(entry)
+ end
+ end
+
+ def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0)
+ dependencies[entry.path] = get_dependencies(entry) unless dependencies[entry.path]
+ output "#{" " * depth}#{format_path(entry)}"
+ if !printed[entry.path] && (config[:recurse] || depth == 0)
+ printed[entry.path] = true
+ dependencies[entry.path].each do |child|
+ child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
+ print_dependencies_tree(child_entry, dependencies, printed, depth + 1)
+ end
+ end
+ end
+
+ def get_dependencies(entry)
+ if entry.parent && entry.parent.path == "/cookbooks"
+ entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
+
+ elsif entry.parent && entry.parent.path == "/nodes"
+ node = Chef::JSONCompat.parse(entry.read)
+ result = []
+ if node["chef_environment"] && node["chef_environment"] != "_default"
+ result << "/environments/#{node["chef_environment"]}.json"
+ end
+ if node["run_list"]
+ result += dependencies_from_runlist(node["run_list"])
+ end
+ result
+
+ elsif entry.parent && entry.parent.path == "/roles"
+ role = Chef::JSONCompat.parse(entry.read)
+ result = []
+ if role["run_list"]
+ dependencies_from_runlist(role["run_list"]).each do |dependency|
+ result << dependency unless result.include?(dependency)
+ end
+ end
+ if role["env_run_lists"]
+ role["env_run_lists"].each_pair do |env, run_list|
+ dependencies_from_runlist(run_list).each do |dependency|
+ result << dependency unless result.include?(dependency)
+ end
+ end
+ end
+ result
+
+ elsif !entry.exists?
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)
+
+ else
+ []
+ end
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ self.exit_code = 2
+ []
+ end
+
+ def dependencies_from_runlist(run_list)
+ chef_run_list = Chef::RunList.new
+ chef_run_list.reset!(run_list)
+ chef_run_list.map do |run_list_item|
+ case run_list_item.type
+ when :role
+ "/roles/#{run_list_item.name}.json"
+ when :recipe
+ if run_list_item.name =~ /(.+)::[^:]*/
+ "/cookbooks/#{$1}"
+ else
+ "/cookbooks/#{run_list_item.name}"
+ end
+ else
+ raise "Unknown run list item type #{run_list_item.type}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/diff.rb b/knife/lib/chef/knife/diff.rb
new file mode 100644
index 0000000000..971f7aa7f4
--- /dev/null
+++ b/knife/lib/chef/knife/diff.rb
@@ -0,0 +1,83 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Diff < Chef::ChefFS::Knife
+ banner "knife diff PATTERNS"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/command_line" unless defined?(Chef::ChefFS::CommandLine)
+ end
+
+ option :recurse,
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
+
+ option :name_only,
+ long: "--name-only",
+ boolean: true,
+ description: "Only show names of modified files."
+
+ option :name_status,
+ long: "--name-status",
+ boolean: true,
+ description: "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
+
+ option :diff_filter,
+ long: "--diff-filter=[(A|D|M|T)...[*]]",
+ description: "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
+
+ option :cookbook_version,
+ long: "--cookbook-version VERSION",
+ description: "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)."
+
+ def run
+ if config[:name_only]
+ output_mode = :name_only
+ end
+ if config[:name_status]
+ output_mode = :name_status
+ end
+ patterns = pattern_args_from(name_args.length > 0 ? name_args : [ "" ])
+
+ # Get the matches (recursively)
+ error = false
+ begin
+ patterns.each do |pattern|
+ found_error = Chef::ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) }, config[:diff_filter], ui ) do |diff|
+ stdout.print diff
+ end
+ error = true if found_error
+ end
+ rescue Chef::ChefFS::FileSystem::OperationFailedError => e
+ ui.error "Failed on #{format_path(e.entry)} in #{e.operation}: #{e.message}"
+ error = true
+ end
+
+ if error
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/download.rb b/knife/lib/chef/knife/download.rb
new file mode 100644
index 0000000000..2eda642979
--- /dev/null
+++ b/knife/lib/chef/knife/download.rb
@@ -0,0 +1,84 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Download < Chef::ChefFS::Knife
+ banner "knife download PATTERNS"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/command_line" unless defined?(Chef::ChefFS::CommandLine)
+ end
+
+ option :recurse,
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
+
+ option :purge,
+ long: "--[no-]purge",
+ boolean: true,
+ default: false,
+ description: "Delete matching local files and directories that do not exist remotely."
+
+ option :force,
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force download of files even if they match (quicker and harmless, but doesn't print out what it changed)."
+
+ option :dry_run,
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what would happen."
+
+ option :diff,
+ long: "--[no-]diff",
+ boolean: true,
+ default: true,
+ description: "Turn off to avoid downloading existing files; only new (and possibly deleted) files with --no-diff."
+
+ option :cookbook_version,
+ long: "--cookbook-version VERSION",
+ description: "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)."
+
+ def run
+ if name_args.length == 0
+ show_usage
+ ui.fatal("You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"")
+ exit 1
+ end
+
+ error = false
+ pattern_args.each do |pattern|
+ if Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
+ error = true
+ end
+ end
+ if error
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/edit.rb b/knife/lib/chef/knife/edit.rb
new file mode 100644
index 0000000000..45702d168b
--- /dev/null
+++ b/knife/lib/chef/knife/edit.rb
@@ -0,0 +1,88 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Edit < Chef::ChefFS::Knife
+ banner "knife edit [PATTERN1 ... PATTERNn]"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "chef/chef_fs/file_system/exceptions" unless defined?(Chef::ChefFS::FileSystem::Exceptions)
+ end
+
+ option :local,
+ long: "--local",
+ boolean: true,
+ description: "Show local files instead of remote."
+
+ def run
+ # Get the matches (recursively)
+ error = false
+ pattern_args.each do |pattern|
+ Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
+ if result.dir?
+ ui.error "#{format_path(result)}: is a directory" if pattern.exact_path
+ error = true
+ else
+ begin
+ new_value = edit_text(result.read, File.extname(result.name))
+ if new_value
+ result.write(new_value)
+ output "Updated #{format_path(result)}"
+ else
+ output "#{format_path(result)} unchanged"
+ end
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path(e.entry)}: #{e.reason}."
+ error = true
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ error = true
+ end
+ end
+ end
+ end
+ if error
+ exit 1
+ end
+ end
+
+ def edit_text(text, extension)
+ unless config[:disable_editing]
+ Tempfile.open([ "knife-edit-", extension ]) do |file|
+ # Write the text to a temporary file
+ file.write(text)
+ file.close
+
+ # Let the user edit the temporary file
+ unless system("#{config[:editor]} #{file.path}")
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details."
+ end
+
+ result_text = IO.read(file.path)
+
+ return result_text if result_text != text
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_compare.rb b/knife/lib/chef/knife/environment_compare.rb
new file mode 100644
index 0000000000..532d1fc159
--- /dev/null
+++ b/knife/lib/chef/knife/environment_compare.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Sander Botman (<sbotman@schubergphilis.com>)
+# Copyright:: Copyright 2013-2016, Sander Botman.
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentCompare < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment compare [ENVIRONMENT..] (options)"
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Show all cookbooks.",
+ boolean: true
+
+ option :mismatch,
+ short: "-m",
+ long: "--mismatch",
+ description: "Only show mismatching versions.",
+ boolean: true
+
+ def run
+ # Get the commandline environments or all if none are provided.
+ environments = environment_list
+
+ # Get a list of all cookbooks that have constraints and their environment.
+ constraints = constraint_list(environments)
+
+ # Get the total list of cookbooks that have constraints
+ cookbooks = cookbook_list(constraints)
+
+ # If we cannot find any cookbooks, we can stop here.
+ if cookbooks.nil? || cookbooks.empty?
+ ui.error "Cannot find any environment cookbook constraints"
+ exit 1
+ end
+
+ # Get all cookbooks so we can compare them all
+ cookbooks = rest.get("/cookbooks?num_versions=1") if config[:all]
+
+ # display matrix view of in the requested format.
+ if config[:format] == "summary"
+ matrix = matrix_output(cookbooks, constraints)
+ ui.output(matrix)
+ else
+ ui.output(constraints)
+ end
+ end
+
+ private
+
+ def environment_list
+ environments = []
+ unless @name_args.nil? || @name_args.empty?
+ @name_args.each { |name| environments << name }
+ else
+ environments = Chef::Environment.list
+ end
+ end
+
+ def constraint_list(environments)
+ constraints = {}
+ environments.each do |env, url| # rubocop:disable Style/HashEachMethods
+ # Because you cannot modify the default environment I filter it out here.
+ unless env == "_default"
+ envdata = Chef::Environment.load(env)
+ ver = envdata.cookbook_versions
+ constraints[env] = ver
+ end
+ end
+ constraints
+ end
+
+ def cookbook_list(constraints)
+ result = {}
+ constraints.each_value { |cb| result.merge!(cb) }
+ result
+ end
+
+ def matrix_output(cookbooks, constraints)
+ rows = [ "" ]
+ environments = []
+ constraints.each_key { |e| environments << e.to_s }
+ columns = environments.count + 1
+ environments.each { |env| rows << ui.color(env, :bold) }
+ cookbooks.each_key do |c|
+ total = []
+ environments.each { |n| total << constraints[n][c] }
+ if total.uniq.count == 1
+ next if config[:mismatch]
+
+ color = :white
+ else
+ color = :yellow
+ end
+ rows << ui.color(c, :bold)
+ environments.each do |e|
+ tag = constraints[e][c] || "latest"
+ rows << ui.color(tag, color)
+ end
+ end
+ ui.list(rows, :uneven_columns_across, columns)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_create.rb b/knife/lib/chef/knife/environment_create.rb
new file mode 100644
index 0000000000..cfb36957d4
--- /dev/null
+++ b/knife/lib/chef/knife/environment_create.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentCreate < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment create ENVIRONMENT (options)"
+
+ option :description,
+ short: "-d DESCRIPTION",
+ long: "--description DESCRIPTION",
+ description: "The environment description."
+
+ def run
+ env_name = @name_args[0]
+
+ if env_name.nil?
+ show_usage
+ ui.fatal("You must specify an environment name")
+ exit 1
+ end
+
+ env = Chef::Environment.new
+ env.name(env_name)
+ env.description(config[:description]) if config[:description]
+ create_object(env, object_class: Chef::Environment)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_delete.rb b/knife/lib/chef/knife/environment_delete.rb
new file mode 100644
index 0000000000..65e5a1eb5c
--- /dev/null
+++ b/knife/lib/chef/knife/environment_delete.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentDelete < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment delete ENVIRONMENT (options)"
+
+ def run
+ env_name = @name_args[0]
+
+ if env_name.nil?
+ show_usage
+ ui.fatal("You must specify an environment name")
+ exit 1
+ end
+
+ delete_object(Chef::Environment, env_name)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_edit.rb b/knife/lib/chef/knife/environment_edit.rb
new file mode 100644
index 0000000000..f2ad842069
--- /dev/null
+++ b/knife/lib/chef/knife/environment_edit.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentEdit < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment edit ENVIRONMENT (options)"
+
+ def run
+ env_name = @name_args[0]
+
+ if env_name.nil?
+ show_usage
+ ui.fatal("You must specify an environment name")
+ exit 1
+ end
+
+ edit_object(Chef::Environment, env_name)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_from_file.rb b/knife/lib/chef/knife/environment_from_file.rb
new file mode 100644
index 0000000000..4b84abd073
--- /dev/null
+++ b/knife/lib/chef/knife/environment_from_file.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentFromFile < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ require_relative "core/object_loader"
+ end
+
+ banner "knife environment from file FILE [FILE..] (options)"
+
+ option :all,
+ short: "-a",
+ long: "--all",
+ description: "Upload all environments."
+
+ def loader
+ @loader ||= Knife::Core::ObjectLoader.new(Chef::Environment, ui)
+ end
+
+ def environments_path
+ @environments_path ||= "environments"
+ end
+
+ def find_all_environments
+ loader.find_all_objects("./#{environments_path}/")
+ end
+
+ def load_all_environments
+ environments = find_all_environments
+ if environments.empty?
+ ui.fatal("Unable to find any environment files in '#{environments_path}'")
+ exit(1)
+ end
+ environments.each do |env|
+ load_environment(env)
+ end
+ end
+
+ def load_environment(env)
+ updated = loader.load_from("environments", env)
+ updated.save
+ output(format_for_display(updated)) if config[:print_after]
+ ui.info("Updated Environment #{updated.name}")
+ end
+
+ def run
+ if config[:all] == true
+ load_all_environments
+ else
+ if @name_args[0].nil?
+ show_usage
+ ui.fatal("You must specify a file to load")
+ exit 1
+ end
+
+ @name_args.each do |arg|
+ load_environment(arg)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_list.rb b/knife/lib/chef/knife/environment_list.rb
new file mode 100644
index 0000000000..7076670fb5
--- /dev/null
+++ b/knife/lib/chef/knife/environment_list.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentList < Knife
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment list (options)"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ output(format_list_for_display(Chef::Environment.list))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/environment_show.rb b/knife/lib/chef/knife/environment_show.rb
new file mode 100644
index 0000000000..0a4000151e
--- /dev/null
+++ b/knife/lib/chef/knife/environment_show.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class EnvironmentShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require "chef/environment" unless defined?(Chef::Environment)
+ end
+
+ banner "knife environment show ENVIRONMENT (options)"
+
+ def run
+ env_name = @name_args[0]
+
+ if env_name.nil?
+ show_usage
+ ui.fatal("You must specify an environment name")
+ exit 1
+ end
+
+ env = Chef::Environment.load(env_name)
+ output(format_for_display(env))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/exec.rb b/knife/lib/chef/knife/exec.rb
new file mode 100644
index 0000000000..b82476220e
--- /dev/null
+++ b/knife/lib/chef/knife/exec.rb
@@ -0,0 +1,99 @@
+#--
+# Author:: Daniel DeLeo (<dan@chef.io)
+# Copyright:: Copyright (c) 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 "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef::Knife::Exec < Chef::Knife
+
+ banner "knife exec [SCRIPT] (options)"
+
+ deps do
+ require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+ end
+
+ option :exec,
+ short: "-E CODE",
+ long: "--exec CODE",
+ description: "A string of #{ChefUtils::Dist::Infra::PRODUCT} code to execute."
+
+ option :script_path,
+ short: "-p PATH:PATH",
+ long: "--script-path PATH:PATH",
+ description: "A colon-separated path to look for scripts in.",
+ proc: lambda { |o| o.split(":") }
+
+ deps do
+ require "chef/shell/ext" unless defined?(Chef::Shell::Extensions)
+ end
+
+ def run
+ config[:script_path] = Array(config[:script_path] || Chef::Config[:script_path])
+
+ # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
+ config[:script_path] << File.join(Chef::Knife.chef_config_dir, "scripts") if Chef::Knife.chef_config_dir
+ ChefConfig::PathHelper.home(".chef", "scripts") { |p| config[:script_path] << p }
+
+ scripts = Array(name_args)
+ context = Object.new
+ Shell::Extensions.extend_context_object(context)
+ if config[:exec]
+ context.instance_eval(config[:exec], "-E Argument", 0)
+ elsif !scripts.empty?
+ scripts.each do |script|
+ file = find_script(script)
+ context.instance_eval(IO.read(file), file, 0)
+ end
+ else
+ puts "An interactive shell is opened"
+ puts
+ puts "Type your script and do:"
+ puts
+ puts "1. To run the script, use 'Ctrl D'"
+ puts "2. To exit, use 'Ctrl/Shift C'"
+ puts
+ puts "Type here a script..."
+ script = STDIN.read
+ context.instance_eval(script, "STDIN", 0)
+ end
+ end
+
+ def find_script(x)
+ # Try to find a script. First try expanding the path given.
+ script = File.expand_path(x)
+ return script if File.exist?(script)
+
+ # Failing that, try searching the script path. If we can't find
+ # anything, fail gracefully.
+ Chef::Log.trace("Searching script_path: #{config[:script_path].inspect}")
+
+ config[:script_path].each do |path|
+ path = File.expand_path(path)
+ test = File.join(path, x)
+ Chef::Log.trace("Testing: #{test}")
+ if File.exist?(test)
+ script = test
+ Chef::Log.trace("Found: #{test}")
+ return script
+ end
+ end
+ ui.error("\"#{x}\" not found in current directory or script_path, giving up.")
+ exit(1)
+ end
+
+end
diff --git a/lib/chef/knife/group_add.rb b/knife/lib/chef/knife/group_add.rb
index eccb7dd10c..eccb7dd10c 100644
--- a/lib/chef/knife/group_add.rb
+++ b/knife/lib/chef/knife/group_add.rb
diff --git a/lib/chef/knife/group_create.rb b/knife/lib/chef/knife/group_create.rb
index 4219188951..4219188951 100644
--- a/lib/chef/knife/group_create.rb
+++ b/knife/lib/chef/knife/group_create.rb
diff --git a/lib/chef/knife/group_destroy.rb b/knife/lib/chef/knife/group_destroy.rb
index 433a5cc627..433a5cc627 100644
--- a/lib/chef/knife/group_destroy.rb
+++ b/knife/lib/chef/knife/group_destroy.rb
diff --git a/lib/chef/knife/group_list.rb b/knife/lib/chef/knife/group_list.rb
index fc8f00ad6d..fc8f00ad6d 100644
--- a/lib/chef/knife/group_list.rb
+++ b/knife/lib/chef/knife/group_list.rb
diff --git a/lib/chef/knife/group_remove.rb b/knife/lib/chef/knife/group_remove.rb
index 07ab19693f..07ab19693f 100644
--- a/lib/chef/knife/group_remove.rb
+++ b/knife/lib/chef/knife/group_remove.rb
diff --git a/lib/chef/knife/group_show.rb b/knife/lib/chef/knife/group_show.rb
index 6ac53f6b6e..6ac53f6b6e 100644
--- a/lib/chef/knife/group_show.rb
+++ b/knife/lib/chef/knife/group_show.rb
diff --git a/knife/lib/chef/knife/key_create.rb b/knife/lib/chef/knife/key_create.rb
new file mode 100644
index 0000000000..e1baf08bb6
--- /dev/null
+++ b/knife/lib/chef/knife/key_create.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/key" unless defined?(Chef::Key)
+require "chef/json_compat" unless defined?(Chef::JSONCompat)
+require "chef/exceptions" unless defined?(Chef::Exceptions)
+
+class Chef
+ class Knife
+ # Service class for UserKeyCreate and ClientKeyCreate,
+ # Implements common functionality of knife [user | org client] key create.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyCreate and ClientKeyCreate for what could populate it
+ class KeyCreate
+
+ attr_accessor :config
+
+ def initialize(actor, actor_field_name, ui, config)
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ @config = config
+ end
+
+ def public_key_or_key_name_error_msg
+ <<~EOS
+ You must pass either --public-key or --key-name, or both.
+ If you only pass --public-key, a key name will be generated from the fingerprint of your key.
+ If you only pass --key-name, a key pair will be generated by the server.
+ EOS
+ end
+
+ def edit_data(key)
+ @ui.edit_data(key)
+ end
+
+ def edit_hash(key)
+ @ui.edit_hash(key)
+ end
+
+ def display_info(input)
+ @ui.info(input)
+ end
+
+ def display_private_key(private_key)
+ @ui.msg(private_key)
+ end
+
+ def output_private_key_to_file(private_key)
+ File.open(@config[:file], "w") do |f|
+ f.print(private_key)
+ end
+ end
+
+ def create_key_from_hash(output)
+ Chef::Key.from_hash(output).create
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ if !@config[:public_key] && !@config[:key_name]
+ raise Chef::Exceptions::KeyCommandInputError, public_key_or_key_name_error_msg
+ elsif !@config[:public_key]
+ key.create_key(true)
+ end
+
+ if @config[:public_key]
+ key.public_key(File.read(File.expand_path(@config[:public_key])))
+ end
+
+ if @config[:key_name]
+ key.name(@config[:key_name])
+ end
+
+ if @config[:expiration_date]
+ key.expiration_date(@config[:expiration_date])
+ else
+ key.expiration_date("infinity")
+ end
+
+ output = edit_hash(key)
+ key = create_key_from_hash(output)
+
+ display_info("Created key: #{key.name}")
+ if key.private_key
+ if @config[:file]
+ output_private_key_to_file(key.private_key)
+ else
+ display_private_key(key.private_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_create_base.rb b/knife/lib/chef/knife/key_create_base.rb
index a1d658e43c..a1d658e43c 100644
--- a/lib/chef/knife/key_create_base.rb
+++ b/knife/lib/chef/knife/key_create_base.rb
diff --git a/knife/lib/chef/knife/key_delete.rb b/knife/lib/chef/knife/key_delete.rb
new file mode 100644
index 0000000000..83b6a8b535
--- /dev/null
+++ b/knife/lib/chef/knife/key_delete.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/key" unless defined?(Chef::Key)
+
+class Chef
+ class Knife
+ # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys.
+ # Implements common functionality of knife [user | org client] key delete.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it
+ class KeyDelete
+ def initialize(name, actor, actor_field_name, ui)
+ @name = name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ end
+
+ def confirm!
+ @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def print_destroyed
+ @ui.info("Deleted key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ key.name(@name)
+ confirm!
+ key.destroy
+ print_destroyed
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/key_edit.rb b/knife/lib/chef/knife/key_edit.rb
new file mode 100644
index 0000000000..25d7b28437
--- /dev/null
+++ b/knife/lib/chef/knife/key_edit.rb
@@ -0,0 +1,118 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/key" unless defined?(Chef::Key)
+require "chef/json_compat" unless defined?(Chef::JSONCompat)
+require "chef/exceptions" unless defined?(Chef::Exceptions)
+
+class Chef
+ class Knife
+ # Service class for UserKeyEdit and ClientKeyEdit,
+ # Implements common functionality of knife [user | org client] key edit.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it
+ class KeyEdit
+
+ attr_accessor :config
+
+ def initialize(original_name, actor, actor_field_name, ui, config)
+ @original_name = original_name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ @config = config
+ end
+
+ def public_key_and_create_key_error_msg
+ <<~EOS
+ You passed both --public-key and --create-key. Only pass one, or the other, or neither.
+ Do not pass either if you do not want to change the public_key field of your key.
+ Pass --public-key if you want to update the public_key field of your key from a specific public key.
+ Pass --create-key if you want the server to generate a new key and use that to update the public_key field of your key.
+ EOS
+ end
+
+ def edit_data(key)
+ @ui.edit_data(key)
+ end
+
+ def edit_hash(key)
+ @ui.edit_hash(key)
+ end
+
+ def display_info(input)
+ @ui.info(input)
+ end
+
+ def display_private_key(private_key)
+ @ui.msg(private_key)
+ end
+
+ def output_private_key_to_file(private_key)
+ File.open(@config[:file], "w") do |f|
+ f.print(private_key)
+ end
+ end
+
+ def update_key_from_hash(output)
+ Chef::Key.from_hash(output).update(@original_name)
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ if @config[:public_key] && @config[:create_key]
+ raise Chef::Exceptions::KeyCommandInputError, public_key_and_create_key_error_msg
+ end
+
+ if @config[:create_key]
+ key.create_key(true)
+ end
+
+ if @config[:public_key]
+ key.public_key(File.read(File.expand_path(@config[:public_key])))
+ end
+
+ if @config[:key_name]
+ key.name(@config[:key_name])
+ else
+ key.name(@original_name)
+ end
+
+ if @config[:expiration_date]
+ key.expiration_date(@config[:expiration_date])
+ end
+
+ output = edit_hash(key)
+ key = update_key_from_hash(output)
+
+ to_display = "Updated key: #{key.name}"
+ to_display << " (formally #{@original_name})" if key.name != @original_name
+ display_info(to_display)
+ if key.private_key
+ if @config[:file]
+ output_private_key_to_file(key.private_key)
+ else
+ display_private_key(key.private_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_edit_base.rb b/knife/lib/chef/knife/key_edit_base.rb
index b094877190..b094877190 100644
--- a/lib/chef/knife/key_edit_base.rb
+++ b/knife/lib/chef/knife/key_edit_base.rb
diff --git a/knife/lib/chef/knife/key_list.rb b/knife/lib/chef/knife/key_list.rb
new file mode 100644
index 0000000000..e01e2807cf
--- /dev/null
+++ b/knife/lib/chef/knife/key_list.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/key" unless defined?(Chef::Key)
+require "chef/json_compat" unless defined?(Chef::JSONCompat)
+require "chef/exceptions" unless defined?(Chef::Exceptions)
+
+class Chef
+ class Knife
+ # Service class for UserKeyList and ClientKeyList, used to list keys.
+ # Implements common functionality of knife [user | org client] key list.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it
+ class KeyList
+
+ attr_accessor :config
+
+ def initialize(actor, list_method, ui, config)
+ @actor = actor
+ @list_method = list_method
+ @ui = ui
+ @config = config
+ end
+
+ def expired_and_non_expired_msg
+ <<~EOS
+ You cannot pass both --only-expired and --only-non-expired.
+ Please pass one or none.
+ EOS
+ end
+
+ def display_info(string)
+ @ui.output(string)
+ end
+
+ def colorize(string)
+ @ui.color(string, :cyan)
+ end
+
+ def run
+ if @config[:only_expired] && @config[:only_non_expired]
+ raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg
+ end
+
+ # call proper list function
+ keys = Chef::Key.send(@list_method, @actor)
+ if @config[:with_details]
+ max_length = 0
+ keys.each do |key|
+ key["name"] = key["name"] + ":"
+ max_length = key["name"].length if key["name"].length > max_length
+ end
+ keys.each do |key|
+ next if !key["expired"] && @config[:only_expired]
+ next if key["expired"] && @config[:only_non_expired]
+
+ display = "#{colorize(key["name"].ljust(max_length))} #{key["uri"]}"
+ display = "#{display} (expired)" if key["expired"]
+ display_info(display)
+ end
+ else
+ keys.each do |key|
+ next if !key["expired"] && @config[:only_expired]
+ next if key["expired"] && @config[:only_non_expired]
+
+ display_info(key["name"])
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/key_list_base.rb b/knife/lib/chef/knife/key_list_base.rb
index e06e908b69..e06e908b69 100644
--- a/lib/chef/knife/key_list_base.rb
+++ b/knife/lib/chef/knife/key_list_base.rb
diff --git a/knife/lib/chef/knife/key_show.rb b/knife/lib/chef/knife/key_show.rb
new file mode 100644
index 0000000000..719e79fc17
--- /dev/null
+++ b/knife/lib/chef/knife/key_show.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/key" unless defined?(Chef::Key)
+require "chef/json_compat" unless defined?(Chef::JSONCompat)
+require "chef/exceptions" unless defined?(Chef::Exceptions)
+
+class Chef
+ class Knife
+ # Service class for UserKeyShow and ClientKeyShow, used to show keys.
+ # Implements common functionality of knife [user | org client] key show.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it
+ class KeyShow
+
+ attr_accessor :config
+
+ def initialize(name, actor, load_method, ui)
+ @name = name
+ @actor = actor
+ @load_method = load_method
+ @ui = ui
+ end
+
+ def display_output(key)
+ @ui.output(@ui.format_for_display(key))
+ end
+
+ def run
+ key = Chef::Key.send(@load_method, @actor, @name)
+ key.public_key(key.public_key.strip)
+ display_output(key)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/list.rb b/knife/lib/chef/knife/list.rb
new file mode 100644
index 0000000000..7fc2231c5f
--- /dev/null
+++ b/knife/lib/chef/knife/list.rb
@@ -0,0 +1,177 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class List < Chef::ChefFS::Knife
+ banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn] (options)"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "tty-screen"
+ end
+
+ option :recursive,
+ short: "-R",
+ boolean: true,
+ description: "List directories recursively."
+
+ option :bare_directories,
+ short: "-d",
+ boolean: true,
+ description: "When directories match the pattern, do not show the directories' children."
+
+ option :local,
+ long: "--local",
+ boolean: true,
+ description: "List local directory instead of remote."
+
+ option :flat,
+ short: "-f",
+ long: "--flat",
+ boolean: true,
+ description: "Show a list of filenames rather than the prettified ls-like output normally produced."
+
+ option :one_column,
+ short: "-1",
+ boolean: true,
+ description: "Show only one column of results."
+
+ option :trailing_slashes,
+ short: "-p",
+ boolean: true,
+ description: "Show trailing slashes after directories."
+
+ attr_accessor :exit_code
+
+ def run
+ patterns = name_args.length == 0 ? [""] : name_args
+
+ # Get the top-level matches
+ all_results = parallelize(pattern_args_from(patterns)) do |pattern|
+ pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a
+
+ if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path
+ ui.error "#{format_path(pattern_results.first)}: No such file or directory"
+ self.exit_code = 1
+ end
+ pattern_results
+ end.flatten(1).to_a
+
+ # Process directories
+ if !config[:bare_directories]
+ dir_results = parallelize(all_results.select(&:dir?)) do |result|
+ add_dir_result(result)
+ end.flatten(1)
+
+ else
+ dir_results = []
+ end
+
+ # Process all other results
+ results = all_results.select { |result| result.exists? && (!result.dir? || config[:bare_directories]) }.to_a
+
+ # Flatten out directory results if necessary
+ if config[:flat]
+ dir_results.each do |result, children| # rubocop:disable Style/HashEachMethods
+ results += children
+ end
+ dir_results = []
+ end
+
+ # Sort by path for happy output
+ results = results.sort_by(&:path)
+ dir_results = dir_results.sort_by { |result| result[0].path }
+
+ # Print!
+ if results.length == 0 && dir_results.length == 1
+ results = dir_results[0][1]
+ dir_results = []
+ end
+
+ print_result_paths results
+ printed_something = results.length > 0
+ dir_results.each do |result, children|
+ if printed_something
+ output ""
+ else
+ printed_something = true
+ end
+ output "#{format_path(result)}:"
+ print_results(children.map { |result| maybe_add_slash(result.display_name, result.dir?) }.sort, "")
+ end
+
+ exit exit_code if exit_code
+ end
+
+ def add_dir_result(result)
+ begin
+ children = result.children.sort_by(&:name)
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ return []
+ end
+
+ result = [ [ result, children ] ]
+ if config[:recursive]
+ child_dirs = children.select(&:dir?)
+ result += parallelize(child_dirs) { |child| add_dir_result(child) }.flatten(1).to_a
+ end
+ result
+ end
+
+ def print_result_paths(results, indent = "")
+ print_results(results.map { |result| maybe_add_slash(format_path(result), result.dir?) }, indent)
+ end
+
+ def print_results(results, indent)
+ return if results.length == 0
+
+ print_space = results.map(&:length).max + 2
+ if config[:one_column] || !stdout.isatty
+ columns = 0
+ else
+ columns = TTY::Screen.columns
+ end
+ current_line = ""
+ results.each do |result|
+ if current_line.length > 0 && current_line.length + print_space > columns
+ output current_line.rstrip
+ current_line = ""
+ end
+ if current_line.length == 0
+ current_line << indent
+ end
+ current_line << result
+ current_line << (" " * (print_space - result.length))
+ end
+ output current_line.rstrip if current_line.length > 0
+ end
+
+ def maybe_add_slash(path, is_dir)
+ if config[:trailing_slashes] && is_dir
+ "#{path}/"
+ else
+ path
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_bulk_delete.rb b/knife/lib/chef/knife/node_bulk_delete.rb
new file mode 100644
index 0000000000..73975eebc7
--- /dev/null
+++ b/knife/lib/chef/knife/node_bulk_delete.rb
@@ -0,0 +1,75 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeBulkDelete < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node bulk delete REGEX (options)"
+
+ def run
+ if name_args.length < 1
+ ui.fatal("You must supply a regular expression to match the results against")
+ exit 42
+ end
+
+ nodes_to_delete = {}
+ matcher = /#{name_args[0]}/
+
+ all_nodes.each do |name, node|
+ next unless name&.match?(matcher)
+
+ nodes_to_delete[name] = node
+ end
+
+ if nodes_to_delete.empty?
+ ui.msg "No nodes match the expression /#{name_args[0]}/"
+ exit 0
+ end
+
+ ui.msg("The following nodes will be deleted:")
+ ui.msg("")
+ ui.msg(ui.list(nodes_to_delete.keys.sort, :columns_down))
+ ui.msg("")
+ ui.confirm("Are you sure you want to delete these nodes")
+
+ nodes_to_delete.sort.each do |name, node|
+ node.destroy
+ ui.msg("Deleted node #{name}")
+ end
+ end
+
+ def all_nodes
+ node_uris_by_name = Chef::Node.list
+
+ node_uris_by_name.keys.inject({}) do |nodes_by_name, name|
+ nodes_by_name[name] = Chef::Node.new.tap { |n| n.name(name) }
+ nodes_by_name
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_create.rb b/knife/lib/chef/knife/node_create.rb
new file mode 100644
index 0000000000..ed82cbe7aa
--- /dev/null
+++ b/knife/lib/chef/knife/node_create.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeCreate < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node create NODE (options)"
+
+ def run
+ @node_name = @name_args[0]
+
+ if @node_name.nil?
+ show_usage
+ ui.fatal("You must specify a node name")
+ exit 1
+ end
+
+ node = Chef::Node.new
+ node.name(@node_name)
+ create_object(node, object_class: Chef::Node)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_delete.rb b/knife/lib/chef/knife/node_delete.rb
new file mode 100644
index 0000000000..605d99b57f
--- /dev/null
+++ b/knife/lib/chef/knife/node_delete.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeDelete < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node delete [NODE [NODE]] (options)"
+
+ def run
+ if @name_args.length == 0
+ show_usage
+ ui.fatal("You must specify at least one node name")
+ exit 1
+ end
+
+ @name_args.each do |node_name|
+ delete_object(Chef::Node, node_name)
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_edit.rb b/knife/lib/chef/knife/node_edit.rb
new file mode 100644
index 0000000000..ebc98f5bff
--- /dev/null
+++ b/knife/lib/chef/knife/node_edit.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+
+ class NodeEdit < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ require_relative "core/node_editor"
+ end
+
+ banner "knife node edit NODE (options)"
+
+ option :all_attributes,
+ short: "-a",
+ long: "--all",
+ boolean: true,
+ description: "Display all attributes when editing."
+
+ def run
+ if node_name.nil?
+ show_usage
+ ui.fatal("You must specify a node name")
+ exit 1
+ end
+
+ updated_node = node_editor.edit_node
+ if updated_values = node_editor.updated?
+ ui.info "Saving updated #{updated_values.join(", ")} on node #{node.name}"
+ updated_node.save
+ else
+ ui.info "Node not updated, skipping node save"
+ end
+ end
+
+ def node_name
+ @node_name ||= @name_args[0]
+ end
+
+ def node_editor
+ @node_editor ||= Knife::NodeEditor.new(node, ui, config)
+ end
+
+ def node
+ @node ||= Chef::Node.load(node_name)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_environment_set.rb b/knife/lib/chef/knife/node_environment_set.rb
new file mode 100644
index 0000000000..84d5b3969b
--- /dev/null
+++ b/knife/lib/chef/knife/node_environment_set.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Jimmy McCrory (<jimmy.mccrory@gmail.com>)
+# Copyright:: Copyright 2014-2016, Jimmy McCrory
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeEnvironmentSet < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ end
+
+ banner "knife node environment set NODE ENVIRONMENT"
+
+ def run
+ if @name_args.size < 2
+ ui.fatal "You must specify a node name and an environment."
+ show_usage
+ exit 1
+ else
+ @node_name = @name_args[0]
+ @environment = @name_args[1]
+ end
+
+ node = Chef::Node.load(@node_name)
+
+ node.chef_environment = @environment
+
+ node.save
+
+ config[:environment] = @environment
+ output(format_for_display(node))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_from_file.rb b/knife/lib/chef/knife/node_from_file.rb
new file mode 100644
index 0000000000..4f1935641a
--- /dev/null
+++ b/knife/lib/chef/knife/node_from_file.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeFromFile < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ require_relative "core/object_loader"
+ end
+
+ banner "knife node from file FILE (options)"
+
+ def loader
+ @loader ||= Knife::Core::ObjectLoader.new(Chef::Node, ui)
+ end
+
+ def run
+ @name_args.each do |arg|
+ updated = loader.load_from("nodes", arg)
+
+ updated.save
+
+ output(format_for_display(updated)) if config[:print_after]
+
+ ui.info("Updated Node #{updated.name}")
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_list.rb b/knife/lib/chef/knife/node_list.rb
new file mode 100644
index 0000000000..6aae4a617d
--- /dev/null
+++ b/knife/lib/chef/knife/node_list.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeList < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node list (options)"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ env = Chef::Config[:environment]
+ output(format_list_for_display( env ? Chef::Node.list_by_environment(env) : Chef::Node.list ))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_policy_set.rb b/knife/lib/chef/knife/node_policy_set.rb
new file mode 100644
index 0000000000..3f55529b3d
--- /dev/null
+++ b/knife/lib/chef/knife/node_policy_set.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodePolicySet < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node policy set NODE POLICY_GROUP POLICY_NAME (options)"
+
+ def run
+ validate_node!
+ validate_options!
+ node = Chef::Node.load(@name_args[0])
+ set_policy(node)
+ if node.save
+ ui.info "Successfully set the policy on node #{node.name}"
+ else
+ ui.info "Error in updating node #{node.name}"
+ end
+ end
+
+ private
+
+ # Set policy name and group to node
+ def set_policy(node)
+ policy_group, policy_name = @name_args[1..]
+ node.policy_name = policy_name
+ node.policy_group = policy_group
+ end
+
+ # Validate policy name and policy group
+ def validate_options!
+ if incomplete_policyfile_options?
+ ui.error("Policy group and name must be specified together")
+ exit 1
+ end
+ true
+ end
+
+ # Validate node pass in CLI
+ def validate_node!
+ if @name_args[0].nil?
+ ui.error("You must specify a node name")
+ show_usage
+ exit 1
+ end
+ end
+
+ # True if one of policy_name or policy_group was given, but not both
+ def incomplete_policyfile_options?
+ policy_group, policy_name = @name_args[1..]
+ (policy_group.nil? || policy_name.nil? || @name_args[1..-1].size > 2)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_run_list_add.rb b/knife/lib/chef/knife/node_run_list_add.rb
new file mode 100644
index 0000000000..695344496a
--- /dev/null
+++ b/knife/lib/chef/knife/node_run_list_add.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeRunListAdd < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node run_list add [NODE] [ENTRY [ENTRY]] (options)"
+
+ option :after,
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
+
+ option :before,
+ short: "-b ITEM",
+ long: "--before ITEM",
+ description: "Place the ENTRY in the run list before ITEM."
+
+ def run
+ node = Chef::Node.load(@name_args[0])
+ if @name_args.size > 2
+ # Check for nested lists and create a single plain one
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[1].split(",").map(&:strip)
+ end
+
+ if config[:after] && config[:before]
+ ui.fatal("You cannot specify both --before and --after!")
+ exit 1
+ end
+
+ if config[:after]
+ add_to_run_list_after(node, entries, config[:after])
+ elsif config[:before]
+ add_to_run_list_before(node, entries, config[:before])
+ else
+ add_to_run_list_after(node, entries)
+ end
+
+ node.save
+
+ config[:run_list] = true
+
+ output(format_for_display(node))
+ end
+
+ private
+
+ def add_to_run_list_after(node, entries, after = nil)
+ if after
+ nlist = []
+ node.run_list.each do |entry|
+ nlist << entry
+ if entry == after
+ entries.each { |e| nlist << e }
+ end
+ end
+ node.run_list.reset!(nlist)
+ else
+ entries.each { |e| node.run_list << e }
+ end
+ end
+
+ def add_to_run_list_before(node, entries, before)
+ nlist = []
+ node.run_list.each do |entry|
+ if entry == before
+ entries.each { |e| nlist << e }
+ end
+ nlist << entry
+ end
+ node.run_list.reset!(nlist)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_run_list_remove.rb b/knife/lib/chef/knife/node_run_list_remove.rb
new file mode 100644
index 0000000000..0c88f8c184
--- /dev/null
+++ b/knife/lib/chef/knife/node_run_list_remove.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeRunListRemove < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node run_list remove [NODE] [ENTRY [ENTRY]] (options)"
+
+ def run
+ node = Chef::Node.load(@name_args[0])
+
+ if @name_args.size > 2
+ # Check for nested lists and create a single plain one
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[1].split(",").map(&:strip)
+ end
+
+ # iterate over the list of things to remove,
+ # warning if one of them was not found
+ entries.each do |e|
+ if node.run_list.find { |rli| e == rli.to_s }
+ node.run_list.remove(e)
+ else
+ ui.warn "#{e} is not in the run list"
+ unless /^(recipe|role)\[/.match?(e)
+ ui.warn "(did you forget recipe[] or role[] around it?)"
+ end
+ end
+ end
+
+ node.save
+
+ config[:run_list] = true
+
+ output(format_for_display(node))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_run_list_set.rb b/knife/lib/chef/knife/node_run_list_set.rb
new file mode 100644
index 0000000000..37b9aef3d6
--- /dev/null
+++ b/knife/lib/chef/knife/node_run_list_set.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class NodeRunListSet < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node run_list set NODE ENTRIES (options)"
+
+ def run
+ if @name_args.size < 2
+ ui.fatal "You must supply both a node name and a run list."
+ show_usage
+ exit 1
+ elsif @name_args.size > 2
+ # Check for nested lists and create a single plain one
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[1].split(",").map(&:strip)
+ end
+ node = Chef::Node.load(@name_args[0])
+
+ set_run_list(node, entries)
+
+ node.save
+
+ config[:run_list] = true
+
+ output(format_for_display(node))
+ end
+
+ # Clears out any existing run_list_items and sets them to the
+ # specified entries
+ def set_run_list(node, entries)
+ node.run_list.run_list_items.clear
+ entries.each { |e| node.run_list << e }
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/node_show.rb b/knife/lib/chef/knife/node_show.rb
new file mode 100644
index 0000000000..bce2ee3fe9
--- /dev/null
+++ b/knife/lib/chef/knife/node_show.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "core/node_presenter"
+require_relative "core/formatting_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class NodeShow < Knife
+
+ include Knife::Core::FormattingOptions
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife node show NODE (options)"
+
+ option :run_list,
+ short: "-r",
+ long: "--run-list",
+ description: "Show only the run list."
+
+ option :environment,
+ short: "-E",
+ long: "--environment",
+ description: "Show only the #{ChefUtils::Dist::Infra::PRODUCT} environment."
+
+ def run
+ ui.use_presenter Knife::Core::NodePresenter
+ @node_name = @name_args[0]
+
+ if @node_name.nil?
+ show_usage
+ ui.fatal("You must specify a node name")
+ exit 1
+ end
+
+ node = Chef::Node.load(@node_name)
+ output(format_for_display(node))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/null.rb b/knife/lib/chef/knife/null.rb
index 7221eee9f5..7221eee9f5 100644
--- a/lib/chef/knife/null.rb
+++ b/knife/lib/chef/knife/null.rb
diff --git a/knife/lib/chef/knife/org_create.rb b/knife/lib/chef/knife/org_create.rb
new file mode 100644
index 0000000000..cb5ded26f5
--- /dev/null
+++ b/knife/lib/chef/knife/org_create.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgCreate < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org create ORG_SHORT_NAME ORG_FULL_NAME (options)"
+
+ option :filename,
+ long: "--filename FILENAME",
+ short: "-f FILENAME",
+ description: "Write validator private key to FILENAME rather than STDOUT"
+
+ option :association_user,
+ long: "--association_user USERNAME",
+ short: "-a USERNAME",
+ description: "Invite USERNAME to the new organization after creation"
+
+ attr_accessor :org_name, :org_full_name
+
+ deps do
+ require "chef/org" unless defined?(Chef::Org)
+ end
+
+ def run
+ @org_name, @org_full_name = @name_args
+
+ if !org_name || !org_full_name
+ ui.fatal "You must specify an ORG_NAME and an ORG_FULL_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.from_hash({ "name" => org_name,
+ "full_name" => org_full_name }).create
+ if config[:filename]
+ File.open(config[:filename], "w") do |f|
+ f.print(org.private_key)
+ end
+ else
+ ui.msg org.private_key
+ end
+
+ if config[:association_user]
+ org.associate_user(config[:association_user])
+ org.add_user_to_group("admins", config[:association_user])
+ org.add_user_to_group("billing-admins", config[:association_user])
+ end
+
+ ui.info("Created #{org_name}")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_delete.rb b/knife/lib/chef/knife/org_delete.rb
new file mode 100644
index 0000000000..340f6c529a
--- /dev/null
+++ b/knife/lib/chef/knife/org_delete.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgDelete < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org delete ORG_NAME"
+
+ def run
+ org_name = @name_args[0]
+ ui.confirm "Do you want to delete the organization #{org_name}"
+ ui.output root_rest.delete("organizations/#{org_name}")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_edit.rb b/knife/lib/chef/knife/org_edit.rb
new file mode 100644
index 0000000000..1d684ca0b4
--- /dev/null
+++ b/knife/lib/chef/knife/org_edit.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgEdit < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org edit ORG"
+
+ def run
+ org_name = @name_args[0]
+
+ if org_name.nil?
+ show_usage
+ ui.fatal("You must specify an organization name")
+ exit 1
+ end
+
+ original_org = root_rest.get("organizations/#{org_name}")
+ edited_org = edit_hash(original_org)
+
+ if original_org == edited_org
+ ui.msg("Organization unchanged, not saving.")
+ exit
+ end
+
+ ui.msg edited_org
+ root_rest.put("organizations/#{org_name}", edited_org)
+ ui.msg("Saved #{org_name}.")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_list.rb b/knife/lib/chef/knife/org_list.rb
new file mode 100644
index 0000000000..85a49ee4c5
--- /dev/null
+++ b/knife/lib/chef/knife/org_list.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgList < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org list"
+
+ option :with_uri,
+ long: "--with-uri",
+ short: "-w",
+ description: "Show corresponding URIs"
+
+ option :all_orgs,
+ long: "--all-orgs",
+ short: "-a",
+ description: "Show auto-generated hidden orgs in output"
+
+ def run
+ results = root_rest.get("organizations")
+ unless config[:all_orgs]
+ results = results.select { |k, v| !(k.length == 20 && k =~ /^[a-z]+$/) }
+ end
+ ui.output(ui.format_list_for_display(results))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_show.rb b/knife/lib/chef/knife/org_show.rb
new file mode 100644
index 0000000000..a8bb207c1d
--- /dev/null
+++ b/knife/lib/chef/knife/org_show.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgShow < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org show ORGNAME"
+
+ def run
+ org_name = @name_args[0]
+ ui.output root_rest.get("organizations/#{org_name}")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_user_add.rb b/knife/lib/chef/knife/org_user_add.rb
new file mode 100644
index 0000000000..d1fb3a2a5d
--- /dev/null
+++ b/knife/lib/chef/knife/org_user_add.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Marc Paradise (<marc@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgUserAdd < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org user add ORG_NAME USER_NAME"
+ attr_accessor :org_name, :username
+
+ option :admin,
+ long: "--admin",
+ short: "-a",
+ description: "Add user to admin group"
+
+ deps do
+ require "chef/org"
+ end
+
+ def run
+ @org_name, @username = @name_args
+
+ if !org_name || !username
+ ui.fatal "You must specify an ORG_NAME and USER_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.new(@org_name)
+ begin
+ org.associate_user(@username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ ui.msg "User #{username} already associated with organization #{org_name}"
+ else
+ raise e
+ end
+ end
+ if config[:admin]
+ org.add_user_to_group("admins", @username)
+ org.add_user_to_group("billing-admins", @username)
+ ui.msg "User #{username} is added to admins and billing-admins group"
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/org_user_remove.rb b/knife/lib/chef/knife/org_user_remove.rb
new file mode 100644
index 0000000000..fc78f5767c
--- /dev/null
+++ b/knife/lib/chef/knife/org_user_remove.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Marc Paradise (<marc@getchef.com>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class OrgUserRemove < Knife
+ category "CHEF ORGANIZATION MANAGEMENT"
+ banner "knife org user remove ORG_NAME USER_NAME"
+ attr_accessor :org_name, :username
+
+ option :force_remove_from_admins,
+ long: "--force",
+ short: "-f",
+ description: "Force removal of user from the organization's admins and billing-admins group."
+
+ deps do
+ require "chef/org" unless defined?(Chef::Org)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ def run
+ @org_name, @username = @name_args
+
+ if !org_name || !username
+ ui.fatal "You must specify an ORG_NAME and USER_NAME"
+ show_usage
+ exit 1
+ end
+
+ org = Chef::Org.new(@org_name)
+
+ if config[:force_remove_from_admins]
+ if org.actor_delete_would_leave_admins_empty?
+ failure_error_message(org_name, username)
+ ui.msg <<~EOF
+ You ran with --force which force removes the user from the admins and billing-admins groups.
+ However, removing #{username} from the admins group would leave it empty, which breaks the org.
+ Please add another user to org #{org_name} admins group and try again.
+ EOF
+ exit 1
+ end
+ remove_user_from_admin_group(org, org_name, username, "admins")
+ remove_user_from_admin_group(org, org_name, username, "billing-admins")
+ end
+
+ begin
+ org.dissociate_user(@username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ ui.msg "User #{username} is not associated with organization #{org_name}"
+ exit 1
+ elsif e.response.code == "403"
+ body = Chef::JSONCompat.from_json(e.response.body)
+ if body.key?("error") && body["error"] == "Please remove #{username} from this organization's admins group before removing him or her from the organization."
+ failure_error_message(org_name, username)
+ ui.msg <<~EOF
+ User #{username} is in the organization's admin group. Removing users from an organization without removing them from the admins group is not allowed.
+ Re-run this command with --force to remove this user from the admins prior to removing it from the organization.
+ EOF
+ exit 1
+ else
+ raise e
+ end
+ else
+ raise e
+ end
+ end
+ end
+
+ def failure_error_message(org_name, username)
+ ui.error "Error removing user #{username} from organization #{org_name}."
+ end
+
+ def remove_user_from_admin_group(org, org_name, username, admin_group_string)
+ org.remove_user_from_group(admin_group_string, username)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ ui.warn <<~EOF
+ User #{username} is not in the #{admin_group_string} group for organization #{org_name}.
+ You probably don't need to pass --force.
+ EOF
+ else
+ raise e
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/raw.rb b/knife/lib/chef/knife/raw.rb
new file mode 100644
index 0000000000..344de9effb
--- /dev/null
+++ b/knife/lib/chef/knife/raw.rb
@@ -0,0 +1,123 @@
+#
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class Raw < Chef::Knife
+ banner "knife raw REQUEST_PATH (options)"
+
+ deps do
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ require "chef/config" unless defined?(Chef::Config)
+ require "chef/http" unless defined?(Chef::HTTP)
+ require "chef/http/authenticator" unless defined?(Chef::HTTP::Authenticator)
+ require "chef/http/cookie_manager" unless defined?(Chef::HTTP::CookieManager)
+ require "chef/http/decompressor" unless defined?(Chef::HTTP::Decompressor)
+ require "chef/http/json_output" unless defined?(Chef::HTTP::JSONOutput)
+ end
+
+ option :method,
+ long: "--method METHOD",
+ short: "-m METHOD",
+ default: "GET",
+ description: "Request method (GET, POST, PUT or DELETE). Default: GET."
+
+ option :pretty,
+ long: "--[no-]pretty",
+ boolean: true,
+ default: true,
+ description: "Pretty-print JSON output. Default: true."
+
+ option :input,
+ long: "--input FILE",
+ short: "-i FILE",
+ description: "Name of file to use for PUT or POST."
+
+ option :proxy_auth,
+ long: "--proxy-auth",
+ boolean: true,
+ default: false,
+ description: "Use webui proxy authentication. Client key must be the webui key."
+
+ # We need a custom HTTP client class here because we don't want to even
+ # try to decode the body, in case we get back corrupted JSON or whatnot.
+ class RawInputServerAPI < Chef::HTTP
+ def initialize(options = {})
+ # If making a change here, also update Chef::ServerAPI.
+ options[:client_name] ||= Chef::Config[:node_name]
+ options[:raw_key] ||= Chef::Config[:client_key_contents]
+ options[:signing_key_filename] ||= Chef::Config[:client_key] unless options[:raw_key]
+ options[:ssh_agent_signing] ||= Chef::Config[:ssh_agent_signing]
+ super(Chef::Config[:chef_server_url], options)
+ end
+ use Chef::HTTP::JSONOutput
+ use Chef::HTTP::CookieManager
+ use Chef::HTTP::Decompressor
+ use Chef::HTTP::Authenticator
+ use Chef::HTTP::RemoteRequestID
+ end
+
+ def run
+ if name_args.length == 0
+ show_usage
+ ui.fatal("You must provide the path you want to hit on the server")
+ exit(1)
+ elsif name_args.length > 1
+ show_usage
+ ui.fatal("You must specify only a single path")
+ exit(1)
+ end
+
+ path = name_args[0]
+ data = false
+ if config[:input]
+ data = IO.read(config[:input])
+ end
+ begin
+ method = config[:method].to_sym
+
+ headers = { "Content-Type" => "application/json" }
+
+ if config[:proxy_auth]
+ headers["x-ops-request-source"] = "web"
+ end
+
+ if config[:pretty]
+ chef_rest = RawInputServerAPI.new
+ result = chef_rest.request(method, name_args[0], headers, data)
+ unless result.is_a?(String)
+ result = Chef::JSONCompat.to_json_pretty(result)
+ end
+ else
+ chef_rest = RawInputServerAPI.new(raw_output: true)
+ result = chef_rest.request(method, name_args[0], headers, data)
+ end
+ output result
+ rescue Timeout::Error => e
+ ui.error "Server timeout"
+ exit 1
+ rescue Net::HTTPClientException => e
+ ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\""
+ ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ""
+ exit 1
+ end
+ end
+
+ end # class Raw
+ end
+end
diff --git a/lib/chef/knife/recipe_list.rb b/knife/lib/chef/knife/recipe_list.rb
index 39e040a2f4..39e040a2f4 100644
--- a/lib/chef/knife/recipe_list.rb
+++ b/knife/lib/chef/knife/recipe_list.rb
diff --git a/lib/chef/knife/rehash.rb b/knife/lib/chef/knife/rehash.rb
index 69ee19229a..69ee19229a 100644
--- a/lib/chef/knife/rehash.rb
+++ b/knife/lib/chef/knife/rehash.rb
diff --git a/knife/lib/chef/knife/role_bulk_delete.rb b/knife/lib/chef/knife/role_bulk_delete.rb
new file mode 100644
index 0000000000..88399bae2c
--- /dev/null
+++ b/knife/lib/chef/knife/role_bulk_delete.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleBulkDelete < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role bulk delete REGEX (options)"
+
+ def run
+ if @name_args.length < 1
+ ui.error("You must supply a regular expression to match the results against")
+ exit 1
+ end
+
+ all_roles = Chef::Role.list(true)
+
+ matcher = /#{@name_args[0]}/
+ roles_to_delete = {}
+ all_roles.each do |name, role|
+ next unless name&.match?(matcher)
+
+ roles_to_delete[role.name] = role
+ end
+
+ if roles_to_delete.empty?
+ ui.info "No roles match the expression /#{@name_args[0]}/"
+ exit 0
+ end
+
+ ui.msg("The following roles will be deleted:")
+ ui.msg("")
+ ui.msg(ui.list(roles_to_delete.keys.sort, :columns_down))
+ ui.msg("")
+ ui.confirm("Are you sure you want to delete these roles")
+
+ roles_to_delete.sort.each do |name, role|
+ role.destroy
+ ui.msg("Deleted role #{name}")
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_create.rb b/knife/lib/chef/knife/role_create.rb
new file mode 100644
index 0000000000..91ff958fe4
--- /dev/null
+++ b/knife/lib/chef/knife/role_create.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleCreate < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role create ROLE (options)"
+
+ option :description,
+ short: "-d DESC",
+ long: "--description DESC",
+ description: "The role description."
+
+ def run
+ @role_name = @name_args[0]
+
+ if @role_name.nil?
+ show_usage
+ ui.fatal("You must specify a role name")
+ exit 1
+ end
+
+ role = Chef::Role.new
+ role.name(@role_name)
+ role.description(config[:description]) if config[:description]
+ create_object(role, object_class: Chef::Role)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_delete.rb b/knife/lib/chef/knife/role_delete.rb
new file mode 100644
index 0000000000..91ac7d3172
--- /dev/null
+++ b/knife/lib/chef/knife/role_delete.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleDelete < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role delete ROLE (options)"
+
+ def run
+ @role_name = @name_args[0]
+
+ if @role_name.nil?
+ show_usage
+ ui.fatal("You must specify a role name")
+ exit 1
+ end
+
+ delete_object(Chef::Role, @role_name)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_edit.rb b/knife/lib/chef/knife/role_edit.rb
new file mode 100644
index 0000000000..a1818019cb
--- /dev/null
+++ b/knife/lib/chef/knife/role_edit.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEdit < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role edit ROLE (options)"
+
+ def run
+ @role_name = @name_args[0]
+
+ if @role_name.nil?
+ show_usage
+ ui.fatal("You must specify a role name")
+ exit 1
+ end
+
+ ui.edit_object(Chef::Role, @role_name)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_env_run_list_add.rb b/knife/lib/chef/knife/role_env_run_list_add.rb
new file mode 100644
index 0000000000..a39bdcf5cd
--- /dev/null
+++ b/knife/lib/chef/knife/role_env_run_list_add.rb
@@ -0,0 +1,87 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEnvRunListAdd < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY [ENTRY]] (options)"
+
+ option :after,
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
+
+ def add_to_env_run_list(role, environment, entries, after = nil)
+ if after
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ role.run_list_for(environment).each do |entry|
+ nlist << entry
+ if entry == after
+ entries.each { |e| nlist << e }
+ end
+ end
+ role.env_run_lists_add(environment => nlist)
+ else
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ role.run_list_for(environment).each do |entry|
+ nlist << entry
+ end
+ entries.each { |e| nlist << e }
+ role.env_run_lists_add(environment => nlist)
+ end
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = @name_args[1]
+
+ if @name_args.size > 2
+ # Check for nested lists and create a single plain one
+ entries = @name_args[2..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[2].split(",").map(&:strip)
+ end
+
+ add_to_env_run_list(role, environment, entries, config[:after])
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_env_run_list_clear.rb b/knife/lib/chef/knife/role_env_run_list_clear.rb
new file mode 100644
index 0000000000..bb0eeabc16
--- /dev/null
+++ b/knife/lib/chef/knife/role_env_run_list_clear.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEnvRunListClear < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role env_run_list clear [ROLE] [ENVIRONMENT] (options)"
+ def clear_env_run_list(role, environment)
+ nlist = []
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ if @name_args.size > 2
+ ui.fatal "You must not supply an environment run list."
+ show_usage
+ exit 1
+ end
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = @name_args[1]
+
+ clear_env_run_list(role, environment)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_env_run_list_remove.rb b/knife/lib/chef/knife/role_env_run_list_remove.rb
new file mode 100644
index 0000000000..c1a028340b
--- /dev/null
+++ b/knife/lib/chef/knife/role_env_run_list_remove.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEnvRunListRemove < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
+
+ def remove_from_env_run_list(role, environment, item_to_remove)
+ nlist = []
+ role.run_list_for(environment).each do |entry|
+ nlist << entry unless entry == item_to_remove
+ # unless entry == @name_args[2]
+ # nlist << entry
+ # end
+ end
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = @name_args[1]
+ item_to_remove = @name_args[2]
+
+ remove_from_env_run_list(role, environment, item_to_remove)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_env_run_list_replace.rb b/knife/lib/chef/knife/role_env_run_list_replace.rb
new file mode 100644
index 0000000000..923a31331f
--- /dev/null
+++ b/knife/lib/chef/knife/role_env_run_list_replace.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEnvRunListReplace < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] (options)"
+
+ def replace_in_env_run_list(role, environment, old_entry, new_entry)
+ nlist = []
+ role.run_list_for(environment).each do |entry|
+ if entry == old_entry
+ nlist << new_entry
+ else
+ nlist << entry
+ end
+ end
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = @name_args[1]
+ old_entry = @name_args[2]
+ new_entry = @name_args[3]
+
+ replace_in_env_run_list(role, environment, old_entry, new_entry)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_env_run_list_set.rb b/knife/lib/chef/knife/role_env_run_list_set.rb
new file mode 100644
index 0000000000..55a50c6c0d
--- /dev/null
+++ b/knife/lib/chef/knife/role_env_run_list_set.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleEnvRunListSet < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
+
+ # Clears out any existing env_run_list_items and sets them to the
+ # specified entries
+ def set_env_run_list(role, environment, entries)
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ entries.each { |e| nlist << e }
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = @name_args[1]
+ if @name_args.size < 2
+ ui.fatal "You must supply both a role name and an environment run list."
+ show_usage
+ exit 1
+ elsif @name_args.size > 2
+ # Check for nested lists and create a single plain one
+ entries = @name_args[2..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[2].split(",").map(&:strip)
+ end
+
+ set_env_run_list(role, environment, entries )
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_from_file.rb b/knife/lib/chef/knife/role_from_file.rb
new file mode 100644
index 0000000000..7b51d8706d
--- /dev/null
+++ b/knife/lib/chef/knife/role_from_file.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleFromFile < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require_relative "core/object_loader"
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role from file FILE [FILE..] (options)"
+
+ def loader
+ @loader ||= Knife::Core::ObjectLoader.new(Chef::Role, ui)
+ end
+
+ def run
+ @name_args.each do |arg|
+ updated = loader.load_from("roles", arg)
+
+ updated.save
+
+ output(format_for_display(updated)) if config[:print_after]
+
+ ui.info("Updated Role #{updated.name}")
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_list.rb b/knife/lib/chef/knife/role_list.rb
new file mode 100644
index 0000000000..723d956b91
--- /dev/null
+++ b/knife/lib/chef/knife/role_list.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleList < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role list (options)"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ output(format_list_for_display(Chef::Role.list))
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_run_list_add.rb b/knife/lib/chef/knife/role_run_list_add.rb
new file mode 100644
index 0000000000..4276b9ab2d
--- /dev/null
+++ b/knife/lib/chef/knife/role_run_list_add.rb
@@ -0,0 +1,87 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleRunListAdd < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role run_list add [ROLE] [ENTRY [ENTRY]] (options)"
+
+ option :after,
+ short: "-a ITEM",
+ long: "--after ITEM",
+ description: "Place the ENTRY in the run list after ITEM."
+
+ def add_to_env_run_list(role, environment, entries, after = nil)
+ if after
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ role.run_list_for(environment).each do |entry|
+ nlist << entry
+ if entry == after
+ entries.each { |e| nlist << e }
+ end
+ end
+ role.env_run_lists_add(environment => nlist)
+ else
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ role.run_list_for(environment).each do |entry|
+ nlist << entry
+ end
+ entries.each { |e| nlist << e }
+ role.env_run_lists_add(environment => nlist)
+ end
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = "_default"
+
+ if @name_args.size > 1
+ # Check for nested lists and create a single plain one
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[1].split(",").map(&:strip)
+ end
+
+ add_to_env_run_list(role, environment, entries, config[:after])
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_run_list_clear.rb b/knife/lib/chef/knife/role_run_list_clear.rb
new file mode 100644
index 0000000000..150dccd7ba
--- /dev/null
+++ b/knife/lib/chef/knife/role_run_list_clear.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleRunListClear < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role run_list clear [ROLE] (options)"
+ def clear_env_run_list(role, environment)
+ nlist = []
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ if @name_args.size > 2
+ ui.fatal "You must not supply an environment run list."
+ show_usage
+ exit 1
+ end
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = "_default"
+
+ clear_env_run_list(role, environment)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_run_list_remove.rb b/knife/lib/chef/knife/role_run_list_remove.rb
new file mode 100644
index 0000000000..7a0f82c092
--- /dev/null
+++ b/knife/lib/chef/knife/role_run_list_remove.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleRunListRemove < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ end
+
+ banner "knife role run_list remove [ROLE] [ENTRY] (options)"
+
+ def remove_from_env_run_list(role, environment, item_to_remove)
+ nlist = []
+ role.run_list_for(environment).each do |entry|
+ nlist << entry unless entry == item_to_remove
+ # unless entry == @name_args[2]
+ # nlist << entry
+ # end
+ end
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = "_default"
+ item_to_remove = @name_args[1]
+
+ remove_from_env_run_list(role, environment, item_to_remove)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_run_list_replace.rb b/knife/lib/chef/knife/role_run_list_replace.rb
new file mode 100644
index 0000000000..63c7b87199
--- /dev/null
+++ b/knife/lib/chef/knife/role_run_list_replace.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleRunListReplace < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] (options)"
+
+ def replace_in_env_run_list(role, environment, old_entry, new_entry)
+ nlist = []
+ role.run_list_for(environment).each do |entry|
+ if entry == old_entry
+ nlist << new_entry
+ else
+ nlist << entry
+ end
+ end
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = "_default"
+ old_entry = @name_args[1]
+ new_entry = @name_args[2]
+
+ replace_in_env_run_list(role, environment, old_entry, new_entry)
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_run_list_set.rb b/knife/lib/chef/knife/role_run_list_set.rb
new file mode 100644
index 0000000000..6cddc7376c
--- /dev/null
+++ b/knife/lib/chef/knife/role_run_list_set.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Author:: William Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleRunListSet < Knife
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ end
+
+ banner "knife role run_list set [ROLE] [ENTRIES] (options)"
+
+ # Clears out any existing env_run_list_items and sets them to the
+ # specified entries
+ def set_env_run_list(role, environment, entries)
+ nlist = []
+ unless role.env_run_lists.key?(environment)
+ role.env_run_lists_add(environment => nlist)
+ end
+ entries.each { |e| nlist << e }
+ role.env_run_lists_add(environment => nlist)
+ end
+
+ def run
+ role = Chef::Role.load(@name_args[0])
+ role.name(@name_args[0])
+ environment = "_default"
+ if @name_args.size < 1
+ ui.fatal "You must supply both a role name and an environment run list."
+ show_usage
+ exit 1
+ elsif @name_args.size > 1
+ # Check for nested lists and create a single plain one
+ entries = @name_args[1..].map do |entry|
+ entry.split(",").map(&:strip)
+ end.flatten
+ else
+ # Convert to array and remove the extra spaces
+ entries = @name_args[1].split(",").map(&:strip)
+ end
+
+ set_env_run_list(role, environment, entries )
+ role.save
+ config[:env_run_list] = true
+ output(format_for_display(role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/role_show.rb b/knife/lib/chef/knife/role_show.rb
new file mode 100644
index 0000000000..3a2df8b782
--- /dev/null
+++ b/knife/lib/chef/knife/role_show.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class RoleShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require "chef/role" unless defined?(Chef::Role)
+ end
+
+ banner "knife role show ROLE (options)"
+
+ def run
+ @role_name = @name_args[0]
+
+ if @role_name.nil?
+ show_usage
+ ui.fatal("You must specify a role name.")
+ exit 1
+ end
+
+ role = Chef::Role.load(@role_name)
+ output(format_for_display(config[:environment] ? role.environment(config[:environment]) : role))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/search.rb b/knife/lib/chef/knife/search.rb
new file mode 100644
index 0000000000..306761f109
--- /dev/null
+++ b/knife/lib/chef/knife/search.rb
@@ -0,0 +1,194 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require_relative "core/node_presenter"
+require_relative "core/formatting_options"
+
+class Chef
+ class Knife
+ class Search < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ require "chef/environment" unless defined?(Chef::Environment)
+ require "chef/api_client" unless defined?(Chef::APIClient)
+ require "chef/search/query" unless defined?(Chef::Search::Query)
+ end
+
+ include Knife::Core::FormattingOptions
+
+ banner "knife search INDEX QUERY (options)"
+
+ option :start,
+ short: "-b ROW",
+ long: "--start ROW",
+ description: "The row to start returning results at.",
+ default: 0,
+ proc: lambda { |i| i.to_i }
+
+ option :rows,
+ short: "-R INT",
+ long: "--rows INT",
+ description: "The number of rows to return.",
+ default: nil,
+ proc: lambda { |i| i.to_i }
+
+ option :run_list,
+ short: "-r",
+ long: "--run-list",
+ description: "Show only the run list."
+
+ option :id_only,
+ short: "-i",
+ long: "--id-only",
+ description: "Show only the ID of matching objects."
+
+ option :query,
+ short: "-q QUERY",
+ long: "--query QUERY",
+ description: "The search query; useful to protect queries starting with -."
+
+ option :filter_result,
+ short: "-f FILTER",
+ long: "--filter-result FILTER",
+ description: "Only return specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\"."
+
+ def run
+ read_cli_args
+
+ if @type == "node"
+ ui.use_presenter Knife::Core::NodePresenter
+ end
+
+ q = Chef::Search::Query.new
+
+ result_items = []
+ result_count = 0
+
+ search_args = {}
+ search_args[:fuzz] = true
+ search_args[:start] = config[:start] if config[:start]
+ search_args[:rows] = config[:rows] if config[:rows]
+ if config[:filter_result]
+ search_args[:filter_result] = create_result_filter(config[:filter_result])
+ elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?)
+ search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute])
+ elsif config[:id_only]
+ search_args[:filter_result] = create_result_filter_from_attributes([])
+ end
+
+ begin
+ q.search(@type, @query, search_args) do |item|
+ formatted_item = {}
+ if config[:id_only]
+ formatted_item = format_for_display({ "id" => item["__display_name"] })
+ elsif item.is_a?(Hash)
+ # doing a little magic here to set the correct name
+ formatted_item[item["__display_name"]] = item.reject { |k| k == "__display_name" }
+ else
+ formatted_item = format_for_display(item)
+ end
+ result_items << formatted_item
+ result_count += 1
+ end
+ rescue Net::HTTPClientException => e
+ msg = Chef::JSONCompat.from_json(e.response.body)["error"].first
+ ui.error("knife search failed: #{msg}")
+ exit 99
+ end
+
+ if ui.interchange?
+ output({ results: result_count, rows: result_items })
+ else
+ ui.log "#{result_count} items found"
+ ui.log("\n")
+ result_items.each do |item|
+ output(item)
+ unless config[:id_only]
+ ui.msg("\n")
+ end
+ end
+ end
+
+ # return a "failure" code to the shell so that knife search can be used in pipes similar to grep
+ exit 1 if result_count == 0
+ end
+
+ def read_cli_args
+ if config[:query]
+ if @name_args[1]
+ ui.error "Please specify query as an argument or an option via -q, not both"
+ ui.msg opt_parser
+ exit 1
+ end
+ @type = name_args[0]
+ @query = config[:query]
+ else
+ case name_args.size
+ when 0
+ ui.error "No query specified"
+ ui.msg opt_parser
+ exit 1
+ when 1
+ @type = "node"
+ @query = name_args[0]
+ when 2
+ @type = name_args[0]
+ @query = name_args[1]
+ end
+ end
+ end
+
+ # This method turns a set of key value pairs in a string into the appropriate data structure that the
+ # chef-server search api is expecting.
+ # expected input is in the form of:
+ # -f "return_var1=path.to.attribute, return_var2=shorter.path"
+ #
+ # a more concrete example might be:
+ # -f "env=chef_environment, ruby_platform=languages.ruby.platform"
+ #
+ # The end result is a hash where the key is a symbol in the hash (the return variable)
+ # and the path is an array with the path elements as strings (in order)
+ # See lib/chef/search/query.rb for more examples of this.
+ def create_result_filter(filter_string)
+ final_filter = {}
+ filter_string.delete!(" ")
+ filters = filter_string.split(",")
+ filters.each do |f|
+ return_id, attr_path = f.split("=")
+ final_filter[return_id.to_sym] = attr_path.split(".")
+ end
+ final_filter
+ end
+
+ def create_result_filter_from_attributes(filter_array)
+ final_filter = {}
+ filter_array.each do |f|
+ final_filter[f] = f.split(".")
+ end
+ # adding magic filter so we can actually pull the name as before
+ final_filter["__display_name"] = [ "name" ]
+ final_filter
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/serve.rb b/knife/lib/chef/knife/serve.rb
new file mode 100644
index 0000000000..30e4a28f9a
--- /dev/null
+++ b/knife/lib/chef/knife/serve.rb
@@ -0,0 +1,65 @@
+#
+# 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 "../knife"
+require "local_mode" unless defined?(Chef::LocalMode)
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class Serve < Knife
+
+ banner "knife serve (options)"
+
+ option :repo_mode,
+ long: "--repo-mode MODE",
+ description: "Specifies the local repository layout. Values: static (only environments/roles/data_bags/cookbooks), everything (includes nodes/clients/users), hosted_everything (includes acls/groups/etc. for Enterprise/Hosted Chef). Default: everything/hosted_everything."
+
+ option :chef_repo_path,
+ long: "--chef-repo-path PATH",
+ description: "Overrides the location of #{ChefUtils::Dist::Infra::PRODUCT} repo. Default is specified by chef_repo_path in the config."
+
+ option :chef_zero_host,
+ long: "--chef-zero-host IP",
+ description: "Overrides the host upon which #{ChefUtils::Dist::Zero::PRODUCT} listens. Default is 127.0.0.1."
+
+ def configure_chef
+ super
+ Chef::Config.local_mode = true
+ Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
+
+ # --chef-repo-path forcibly overrides all other paths
+ if config[:chef_repo_path]
+ Chef::Config.chef_repo_path = config[:chef_repo_path]
+ %w{acl client cookbook container data_bag environment group node role user}.each do |variable_name|
+ Chef::Config.delete("#{variable_name}_path".to_sym)
+ end
+ end
+ end
+
+ def run
+ server = Chef::LocalMode.chef_zero_server
+ begin
+ output "Serving files from:\n#{Chef::LocalMode.chef_fs.fs_description}"
+ server.stop
+ server.start(stdout) # to print header
+ ensure
+ server.stop
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/show.rb b/knife/lib/chef/knife/show.rb
new file mode 100644
index 0000000000..cdee271c63
--- /dev/null
+++ b/knife/lib/chef/knife/show.rb
@@ -0,0 +1,72 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Show < Chef::ChefFS::Knife
+ banner "knife show [PATTERN1 ... PATTERNn] (options)"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "chef/chef_fs/file_system/exceptions" unless defined?(Chef::ChefFS::FileSystem::Exceptions)
+ end
+
+ option :local,
+ long: "--local",
+ boolean: true,
+ description: "Show local files instead of remote."
+
+ def run
+ # Get the matches (recursively)
+ error = false
+ entry_values = parallelize(pattern_args) do |pattern|
+ parallelize(Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern)) do |entry|
+ if entry.dir?
+ ui.error "#{format_path(entry)}: is a directory" if pattern.exact_path
+ error = true
+ nil
+ else
+ begin
+ [entry, entry.read]
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path(e.entry)}: #{e.reason}."
+ error = true
+ nil
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ error = true
+ nil
+ end
+ end
+ end
+ end.flatten(1)
+ entry_values.each do |entry, value|
+ if entry
+ output "#{format_path(entry)}:"
+ output(format_for_display(value))
+ end
+ end
+ if error
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/ssh.rb b/knife/lib/chef/knife/ssh.rb
new file mode 100644
index 0000000000..e69de62bc2
--- /dev/null
+++ b/knife/lib/chef/knife/ssh.rb
@@ -0,0 +1,645 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class Ssh < Knife
+
+ deps do
+ require "chef/mixin/shell_out" unless defined?(Chef::Mixin::ShellOut)
+ require "net/ssh" unless defined?(Net::SSH)
+ require "net/ssh/multi"
+ require "readline"
+ require "chef/exceptions" unless defined?(Chef::Exceptions)
+ require "chef/search/query" unless defined?(Chef::Search::Query)
+ require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper)
+
+ include Chef::Mixin::ShellOut
+ end
+
+ attr_writer :password
+
+ banner "knife ssh QUERY COMMAND (options)"
+
+ option :concurrency,
+ short: "-C NUM",
+ long: "--concurrency NUM",
+ description: "The number of concurrent connections.",
+ default: nil,
+ proc: lambda { |o| o.to_i }
+
+ option :ssh_attribute,
+ short: "-a ATTR",
+ long: "--attribute ATTR",
+ description: "The attribute to use for opening the connection - default depends on the context."
+
+ option :manual,
+ short: "-m",
+ long: "--manual-list",
+ boolean: true,
+ description: "QUERY is a space separated list of servers.",
+ default: false
+
+ option :prefix_attribute,
+ long: "--prefix-attribute ATTR",
+ description: "The attribute to use for prefixing the output - default depends on the context."
+
+ option :ssh_user,
+ short: "-x USERNAME",
+ long: "--ssh-user USERNAME",
+ description: "The ssh username."
+
+ option :ssh_password,
+ short: "-P [PASSWORD]",
+ long: "--ssh-password [PASSWORD]",
+ description: "The ssh password - will prompt if flag is specified but no password is given.",
+ # default to a value that can not be a password (boolean)
+ # so we can effectively test if this parameter was specified
+ # without a value
+ default: false
+
+ option :ssh_port,
+ short: "-p PORT",
+ long: "--ssh-port PORT",
+ description: "The ssh port.",
+ proc: Proc.new { |key| key.strip }
+
+ option :ssh_timeout,
+ short: "-t SECONDS",
+ long: "--ssh-timeout SECONDS",
+ description: "The ssh connection timeout.",
+ proc: Proc.new { |key| key.strip.to_i },
+ default: 120
+
+ option :ssh_gateway,
+ short: "-G GATEWAY",
+ long: "--ssh-gateway GATEWAY",
+ description: "The ssh gateway.",
+ proc: Proc.new { |key| key.strip }
+
+ option :ssh_gateway_identity,
+ long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
+ description: "The SSH identity file used for gateway authentication."
+
+ option :forward_agent,
+ short: "-A",
+ long: "--forward-agent",
+ description: "Enable SSH agent forwarding.",
+ boolean: true
+
+ option :ssh_identity_file,
+ short: "-i IDENTITY_FILE",
+ long: "--ssh-identity-file IDENTITY_FILE",
+ description: "The SSH identity file used for authentication."
+
+ option :host_key_verify,
+ long: "--[no-]host-key-verify",
+ description: "Verify host key, enabled by default.",
+ boolean: true,
+ default: true
+
+ option :on_error,
+ short: "-e",
+ long: "--exit-on-error",
+ description: "Immediately exit if an error is encountered.",
+ boolean: true,
+ default: false
+
+ option :duplicated_fqdns,
+ long: "--duplicated-fqdns",
+ description: "Behavior if FQDNs are duplicated, ignored by default.",
+ proc: Proc.new { |key| key.strip.to_sym },
+ default: :ignore
+
+ option :tmux_split,
+ long: "--tmux-split",
+ description: "Split tmux window.",
+ boolean: true,
+ default: false
+
+ def session
+ ssh_error_handler = Proc.new do |server|
+ if config[:on_error]
+ # Net::SSH::Multi magic to force exception to be re-raised.
+ throw :go, :raise
+ else
+ ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}"
+ $!.backtrace.each { |l| Chef::Log.debug(l) }
+ end
+ end
+
+ @session ||= Net::SSH::Multi.start(concurrent_connections: config[:concurrency], on_error: ssh_error_handler)
+ end
+
+ def configure_gateway
+ if config[:ssh_gateway]
+ gw_host, gw_user = config[:ssh_gateway].split("@").reverse
+ gw_host, gw_port = gw_host.split(":")
+ gw_opts = session_options(gw_host, gw_port, gw_user, gateway: true)
+ user = gw_opts.delete(:user)
+
+ begin
+ # Try to connect with a key.
+ session.via(gw_host, user, gw_opts)
+ rescue Net::SSH::AuthenticationFailed
+ prompt = "Enter the password for #{user}@#{gw_host}: "
+ gw_opts[:password] = prompt_for_password(prompt)
+ # Try again with a password.
+ session.via(gw_host, user, gw_opts)
+ end
+ end
+ end
+
+ def configure_session
+ list = config[:manual] ? @name_args[0].split(" ") : search_nodes
+ if list.length == 0
+ if @search_count == 0
+ ui.fatal("No nodes returned from search")
+ else
+ ui.fatal("#{@search_count} #{@search_count > 1 ? "nodes" : "node"} found, " +
+ "but does not have the required attribute to establish the connection. " +
+ "Try setting another attribute to open the connection using --attribute.")
+ end
+ exit 10
+ end
+ if %i{warn fatal}.include?(config[:duplicated_fqdns])
+ fqdns = list.map { |v| v[0] }
+ if fqdns.count != fqdns.uniq.count
+ duplicated_fqdns = fqdns.uniq
+ ui.send(config[:duplicated_fqdns],
+ "SSH #{duplicated_fqdns.count > 1 ? "nodes are" : "node is"} " +
+ "duplicated: #{duplicated_fqdns.join(",")}")
+ exit 10 if config[:duplicated_fqdns] == :fatal
+ end
+ end
+ session_from_list(list)
+ end
+
+ def get_prefix_attribute(item)
+ # Order of precedence for prefix
+ # 1) config value (cli or knife config)
+ # 2) nil
+ msg = "Using node attribute '%s' as the prefix: %s"
+ if item["prefix"]
+ Chef::Log.debug(sprintf(msg, config[:prefix_attribute], item["prefix"]))
+ item["prefix"]
+ else
+ nil
+ end
+ end
+
+ def get_ssh_attribute(item)
+ # Order of precedence for ssh target
+ # 1) config value (cli or knife config)
+ # 2) cloud attribute
+ # 3) fqdn
+ msg = "Using node attribute '%s' as the ssh target: %s"
+ if item["target"]
+ Chef::Log.debug(sprintf(msg, config[:ssh_attribute], item["target"]))
+ item["target"]
+ elsif !item.dig("cloud", "public_hostname").to_s.empty?
+ Chef::Log.debug(sprintf(msg, "cloud.public_hostname", item["cloud"]["public_hostname"]))
+ item["cloud"]["public_hostname"]
+ else
+ Chef::Log.debug(sprintf(msg, "fqdn", item["fqdn"]))
+ item["fqdn"]
+ end
+ end
+
+ def search_nodes
+ list = []
+ query = Chef::Search::Query.new
+ required_attributes = { fqdn: ["fqdn"], cloud: ["cloud"] }
+
+ separator = ui.presenter.attribute_field_separator
+
+ if config[:prefix_attribute]
+ required_attributes[:prefix] = config[:prefix_attribute].split(separator)
+ end
+
+ if config[:ssh_attribute]
+ required_attributes[:target] = config[:ssh_attribute].split(separator)
+ end
+
+ @search_count = 0
+ query.search(:node, @name_args[0], filter_result: required_attributes, fuzz: true) do |item|
+ @search_count += 1
+ # we should skip the loop to next iteration if the item
+ # returned by the search is nil
+ next if item.nil?
+
+ # next if we couldn't find the specified attribute in the
+ # returned node object
+ host = get_ssh_attribute(item)
+ next if host.nil?
+
+ prefix = get_prefix_attribute(item)
+ ssh_port = item.dig("cloud", "public_ssh_port")
+ srv = [host, ssh_port, prefix]
+ list.push(srv)
+ end
+
+ list
+ end
+
+ # Net::SSH session options hash for global options. These should be
+ # options that will apply to the gateway connection in addition to the
+ # main one.
+ #
+ # @since 12.5.0
+ # @param host [String] Hostname for this session.
+ # @param port [String] SSH port for this session.
+ # @param user [String] Optional username for this session.
+ # @param gateway [Boolean] Flag: host or gateway key
+ # @return [Hash<Symbol, Object>]
+ def session_options(host, port, user = nil, gateway: false)
+ ssh_config = Net::SSH.configuration_for(host, true)
+ {}.tap do |opts|
+ opts[:user] = user || config[:ssh_user] || ssh_config[:user]
+ if !gateway && config[:ssh_identity_file]
+ opts[:keys] = File.expand_path(config[:ssh_identity_file])
+ opts[:keys_only] = true
+ elsif gateway && config[:ssh_gateway_identity]
+ opts[:keys] = File.expand_path(config[:ssh_gateway_identity])
+ opts[:keys_only] = true
+ elsif config[:ssh_password]
+ opts[:password] = config[:ssh_password]
+ end
+ # Don't set the keys to nil if we don't have them.
+ forward_agent = config[:forward_agent] || ssh_config[:forward_agent]
+ opts[:forward_agent] = forward_agent unless forward_agent.nil?
+ port ||= ssh_config[:port]
+ opts[:port] = port unless port.nil?
+ opts[:logger] = Chef::Log.with_child(subsystem: "net/ssh") if Chef::Log.level == :trace
+ unless config[:host_key_verify]
+ opts[:verify_host_key] = :never
+ opts[:user_known_hosts_file] = "/dev/null"
+ end
+ if ssh_config[:keepalive]
+ opts[:keepalive] = true
+ opts[:keepalive_interval] = ssh_config[:keepalive_interval]
+ end
+ # maintain support for legacy key types / ciphers / key exchange algorithms.
+ # most importantly this adds back support for DSS host keys
+ # See https://github.com/net-ssh/net-ssh/pull/709
+ opts[:append_all_supported_algorithms] = true
+ end
+ end
+
+ def session_from_list(list)
+ list.each do |item|
+ host, ssh_port, prefix = item
+ prefix = host unless prefix
+ Chef::Log.debug("Adding #{host}")
+ session_opts = session_options(host, ssh_port, gateway: false)
+ # Handle port overrides for the main connection.
+ session_opts[:port] = config[:ssh_port] if config[:ssh_port]
+ # Handle connection timeout
+ session_opts[:timeout] = config[:ssh_timeout] if config[:ssh_timeout]
+ # Handle session prefix
+ session_opts[:properties] = { prefix: prefix }
+ # Create the hostspec.
+ hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host
+ # Connect a new session on the multi.
+ session.use(hostspec, session_opts)
+
+ @longest = prefix.length if prefix.length > @longest
+ end
+
+ session
+ end
+
+ def fixup_sudo(command)
+ command.sub(/^sudo/, "sudo -p 'knife sudo password: '")
+ end
+
+ def print_data(host, data)
+ @buffers ||= {}
+ if leftover = @buffers[host]
+ @buffers[host] = nil
+ print_data(host, leftover + data)
+ else
+ if newline_index = data.index("\n")
+ line = data.slice!(0...newline_index)
+ data.slice!(0)
+ print_line(host, line)
+ print_data(host, data)
+ else
+ @buffers[host] = data
+ end
+ end
+ end
+
+ def print_line(host, data)
+ padding = @longest - host.length
+ str = ui.color(host, :cyan) + (" " * (padding + 1)) + data
+ ui.msg(str)
+ end
+
+ def ssh_command(command, subsession = nil)
+ exit_status = 0
+ subsession ||= session
+ command = fixup_sudo(command)
+ command.force_encoding("binary") if command.respond_to?(:force_encoding)
+ begin
+ open_session(subsession, command)
+ rescue => e
+ open_session(subsession, command, true)
+ end
+ end
+
+ def open_session(subsession, command, pty = false)
+ stderr = ""
+ exit_status = 0
+ subsession.open_channel do |chan|
+ if config[:on_error] && exit_status != 0
+ chan.close
+ else
+ chan.request_pty if pty
+ chan.exec command do |ch, success|
+ raise ArgumentError, "Cannot execute #{command}" unless success
+
+ ch.on_data do |ichannel, data|
+ print_data(ichannel.connection[:prefix], data)
+ if /^knife sudo password: /.match?(data)
+ print_data(ichannel.connection[:prefix], "\n")
+ ichannel.send_data("#{get_password}\n")
+ end
+ end
+
+ ch.on_extended_data do |_, _type, data|
+ raise ArgumentError if data.eql?("sudo: no tty present and no askpass program specified\n")
+
+ stderr += data
+ end
+
+ ch.on_request "exit-status" do |ichannel, data|
+ exit_status = [exit_status, data.read_long].max
+ end
+ end
+ end
+ end
+ session.loop
+ exit_status
+ end
+
+ def get_password
+ @password ||= prompt_for_password
+ end
+
+ def prompt_for_password(prompt = "Enter your password: ")
+ ui.ask(prompt, echo: false)
+ end
+
+ # Present the prompt and read a single line from the console. It also
+ # detects ^D and returns "exit" in that case. Adds the input to the
+ # history, unless the input is empty. Loops repeatedly until a non-empty
+ # line is input.
+ def read_line
+ loop do
+ command = reader.readline("#{ui.color("knife-ssh>", :bold)} ", true)
+
+ if command.nil?
+ command = "exit"
+ puts(command)
+ else
+ command.strip!
+ end
+
+ unless command.empty?
+ return command
+ end
+ end
+ end
+
+ def reader
+ Readline
+ end
+
+ def interactive
+ puts "Connected to #{ui.list(session.servers_for.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
+ puts
+ puts "To run a command on a list of servers, do:"
+ puts " on SERVER1 SERVER2 SERVER3; COMMAND"
+ puts " Example: on latte foamy; echo foobar"
+ puts
+ puts "To exit interactive mode, use 'quit!'"
+ puts
+ loop do
+ command = read_line
+ case command
+ when "quit!"
+ puts "Bye!"
+ break
+ when /^on (.+?); (.+)$/
+ raw_list = $1.split(" ")
+ server_list = []
+ session.servers.each do |session_server|
+ server_list << session_server if raw_list.include?(session_server.host)
+ end
+ command = $2
+ ssh_command(command, session.on(*server_list))
+ else
+ ssh_command(command)
+ end
+ end
+ end
+
+ def screen
+ tf = Tempfile.new("knife-ssh-screen")
+ ChefConfig::PathHelper.home(".screenrc") do |screenrc_path|
+ if File.exist? screenrc_path
+ tf.puts("source #{screenrc_path}")
+ end
+ end
+ tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'")
+ tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'")
+ window = 0
+ session.servers_for.each do |server|
+ tf.print("screen -t \"#{server.host}\" #{window} ssh ")
+ tf.print("-i #{config[:ssh_identity_file]} ") if config[:ssh_identity_file]
+ server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host)
+ window += 1
+ end
+ tf.close
+ exec("screen -c #{tf.path}")
+ end
+
+ def tmux
+ ssh_dest = lambda do |server|
+ identity = "-i #{config[:ssh_identity_file]} " if config[:ssh_identity_file]
+ prefix = server.user ? "#{server.user}@" : ""
+ "'ssh #{identity}#{prefix}#{server.host}'"
+ end
+
+ new_window_cmds = lambda do
+ if session.servers_for.size > 1
+ [""] + session.servers_for[1..].map do |server|
+ if config[:tmux_split]
+ "split-window #{ssh_dest.call(server)}; tmux select-layout tiled"
+ else
+ "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
+ end
+ end
+ else
+ []
+ end.join(" \\; ")
+ end
+
+ tmux_name = "'knife ssh #{@name_args[0].tr(":.", "=-")}'"
+ begin
+ server = session.servers_for.first
+ cmd = ["tmux new-session -d -s #{tmux_name}",
+ "-n '#{server.host}'", ssh_dest.call(server),
+ new_window_cmds.call].join(" ")
+ shell_out!(cmd)
+ exec("tmux attach-session -t #{tmux_name}")
+ rescue Chef::Exceptions::Exec
+ end
+ end
+
+ def macterm
+ begin
+ require "appscript" unless defined?(Appscript)
+ rescue LoadError
+ STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
+ raise
+ end
+
+ Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", using: :command_down)
+ term = Appscript.app("Terminal")
+ window = term.windows.first.get
+
+ (session.servers_for.size - 1).times do |i|
+ window.activate
+ Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", using: :command_down)
+ end
+
+ session.servers_for.each_with_index do |server, tab_number|
+ cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}"
+ Appscript.app("Terminal").do_script(cmd, in: window.tabs[tab_number + 1].get)
+ end
+ end
+
+ def cssh
+ cssh_cmd = nil
+ %w{csshX cssh}.each do |cmd|
+
+ # Unix and Mac only
+ cssh_cmd = shell_out!("which #{cmd}").stdout.strip
+ break
+ rescue Mixlib::ShellOut::ShellCommandFailed
+
+ end
+ raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
+
+ # pass in the consolidated identity file option to cssh(X)
+ if config[:ssh_identity_file]
+ cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:ssh_identity_file])}'"
+ end
+
+ session.servers_for.each do |server|
+ cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
+ end
+ Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}")
+ exec(cssh_cmd)
+ end
+
+ def get_stripped_unfrozen_value(value)
+ return nil unless value
+
+ value.strip
+ end
+
+ def configure_user
+ config[:ssh_user] = get_stripped_unfrozen_value(config[:ssh_user] ||
+ Chef::Config[:knife][:ssh_user])
+ end
+
+ def configure_password
+ if config.key?(:ssh_password) && config[:ssh_password].nil?
+ # if we have an actual nil that means someone called "--ssh-password" with no value, so we prompt for a password
+ config[:ssh_password] = get_password
+ else
+ # the false default of ssh_password results in a nil here
+ config[:ssh_password] = get_stripped_unfrozen_value(config[:ssh_password])
+ end
+ end
+
+ def configure_ssh_identity_file
+ config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file])
+ end
+
+ def configure_ssh_gateway_identity
+ config[:ssh_gateway_identity] = get_stripped_unfrozen_value(config[:ssh_gateway_identity])
+ end
+
+ def run
+ @longest = 0
+
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the SEARCH QUERY.")
+ exit(1)
+ end
+
+ configure_user
+ configure_password
+ @password = config[:ssh_password] if config[:ssh_password]
+
+ # If a password was not given, check for SSH identity file.
+ unless @password
+ configure_ssh_identity_file
+ configure_ssh_gateway_identity
+ end
+
+ configure_gateway
+ configure_session
+
+ exit_status =
+ case @name_args[1]
+ when "interactive"
+ interactive
+ when "screen"
+ screen
+ when "tmux"
+ tmux
+ when "macterm"
+ macterm
+ when "cssh"
+ cssh
+ else
+ ssh_command(@name_args[1..].join(" "))
+ end
+
+ session.close
+ if exit_status && exit_status != 0
+ exit exit_status
+ else
+ exit_status
+ end
+ end
+
+ private :search_nodes
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/ssl_check.rb b/knife/lib/chef/knife/ssl_check.rb
new file mode 100644
index 0000000000..c829e7938b
--- /dev/null
+++ b/knife/lib/chef/knife/ssl_check.rb
@@ -0,0 +1,284 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class SslCheck < Chef::Knife
+
+ deps do
+ require "chef/config" unless defined?(Chef::Config)
+ require "pp" unless defined?(PP)
+ require "socket" unless defined?(Socket)
+ require "uri" unless defined?(URI)
+ require "chef/http/ssl_policies" unless defined?(Chef::HTTP::DefaultSSLPolicy)
+ require "openssl" unless defined?(OpenSSL)
+ require "chef/mixin/proxified_socket" unless defined?(Chef::Mixin::ProxifiedSocket)
+ include Chef::Mixin::ProxifiedSocket
+ end
+
+ banner "knife ssl check [URL] (options)"
+
+ def initialize(*args)
+ @host = nil
+ @verify_peer_socket = nil
+ @ssl_policy = HTTP::DefaultSSLPolicy
+ super
+ end
+
+ def uri
+ @uri ||= begin
+ Chef::Log.trace("Checking SSL cert on #{given_uri}")
+ URI.parse(given_uri)
+ end
+ end
+
+ def given_uri
+ (name_args[0] || Chef::Config.chef_server_url)
+ end
+
+ def host
+ uri.host
+ end
+
+ def port
+ uri.port
+ end
+
+ def validate_uri
+ unless host && port
+ invalid_uri!
+ end
+ rescue URI::Error
+ invalid_uri!
+ end
+
+ def invalid_uri!
+ ui.error("Given URI: `#{given_uri}' is invalid")
+ show_usage
+ exit 1
+ end
+
+ def verify_peer_socket
+ @verify_peer_socket ||= begin
+ tcp_connection = proxified_socket(host, port)
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client.hostname = host
+ ssl_client
+ end
+ end
+
+ def verify_peer_ssl_context
+ @verify_peer_ssl_context ||= begin
+ verify_peer_context = OpenSSL::SSL::SSLContext.new
+ @ssl_policy.apply_to(verify_peer_context)
+ verify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ verify_peer_context
+ end
+ end
+
+ def noverify_socket
+ @noverify_socket ||= begin
+ tcp_connection = proxified_socket(host, port)
+ OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
+ end
+ end
+
+ def noverify_peer_ssl_context
+ @noverify_peer_ssl_context ||= begin
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
+ @ssl_policy.apply_to(noverify_peer_context)
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ noverify_peer_context
+ end
+ end
+
+ def verify_X509
+ cert_debug_msg = ""
+ trusted_certificates.each do |cert_name|
+ message = check_X509_certificate(cert_name)
+ unless message.nil?
+ cert_debug_msg << File.expand_path(cert_name) + ": " + message + "\n"
+ end
+ end
+
+ unless cert_debug_msg.empty?
+ debug_invalid_X509(cert_debug_msg)
+ end
+
+ true # Maybe the bad certs won't hurt...
+ end
+
+ def verify_cert
+ ui.msg("Connecting to host #{host}:#{port}")
+ verify_peer_socket.connect
+ true
+ rescue OpenSSL::SSL::SSLError => e
+ ui.error "The SSL certificate of #{host} could not be verified"
+ Chef::Log.trace e.message
+ debug_invalid_cert
+ false
+ end
+
+ def verify_cert_host
+ verify_peer_socket.post_connection_check(host)
+ true
+ rescue OpenSSL::SSL::SSLError => e
+ ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname"
+ Chef::Log.trace(e)
+ debug_invalid_host
+ false
+ end
+
+ def debug_invalid_X509(cert_debug_msg)
+ ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n")
+ debug_ssl_settings
+ debug_chef_ssl_config
+
+ ui.warn(<<~BAD_CERTS)
+ There are invalid certificates in your trusted_certs_dir.
+ OpenSSL will not use the following certificates when verifying SSL connections:
+
+ #{cert_debug_msg}
+
+ #{ui.color("TO FIX THESE WARNINGS:", :bold)}
+
+ We are working on documentation for resolving common issues uncovered here.
+
+ * If the certificate is generated by the server, you may try redownloading the
+ server's certificate. By default, the certificate is stored in the following
+ location on the host where your chef-server runs:
+
+ /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
+
+ Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
+ using SSH/SCP or some other secure method, then re-run this command to confirm
+ that the server's certificate is now trusted.
+
+ BAD_CERTS
+ # @TODO: ^ needs URL once documentation is posted.
+ end
+
+ def debug_invalid_cert
+ noverify_socket.connect
+ issuer_info = noverify_socket.peer_cert.issuer
+ ui.msg("Certificate issuer data: #{issuer_info}")
+
+ ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n")
+ debug_ssl_settings
+ debug_chef_ssl_config
+
+ ui.err(<<~ADVICE)
+
+ #{ui.color("TO FIX THIS ERROR:", :bold)}
+
+ If the server you are connecting to uses a self-signed certificate, you must
+ configure #{ChefUtils::Dist::Infra::PRODUCT} to trust that server's certificate.
+
+ By default, the certificate is stored in the following location on the host
+ where your chef-server runs:
+
+ /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
+
+ Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
+ using SSH/SCP or some other secure method, then re-run this command to confirm
+ that the server's certificate is now trusted.
+
+ ADVICE
+ end
+
+ def debug_invalid_host
+ noverify_socket.connect
+ subject = noverify_socket.peer_cert.subject
+ cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
+ cn = cn_field_tuple[1]
+
+ ui.error("You are attempting to connect to: '#{host}'")
+ ui.error("The server's certificate belongs to '#{cn}'")
+ ui.err(<<~ADVICE)
+
+ #{ui.color("TO FIX THIS ERROR:", :bold)}
+
+ The solution for this issue depends on your networking configuration. If you
+ are able to connect to this server using the hostname #{cn}
+ instead of #{host}, then you can resolve this issue by updating chef_server_url
+ in your configuration file.
+
+ If you are not able to connect to the server using the hostname #{cn}
+ you will have to update the certificate on the server to use the correct hostname.
+ ADVICE
+ end
+
+ def debug_ssl_settings
+ ui.err "OpenSSL Configuration:"
+ ui.err "* Version: #{OpenSSL::OPENSSL_VERSION}"
+ ui.err "* Certificate file: #{OpenSSL::X509::DEFAULT_CERT_FILE}"
+ ui.err "* Certificate directory: #{OpenSSL::X509::DEFAULT_CERT_DIR}"
+ end
+
+ def debug_chef_ssl_config
+ ui.err "#{ChefUtils::Dist::Infra::PRODUCT} SSL Configuration:"
+ ui.err "* ssl_ca_path: #{configuration.ssl_ca_path.inspect}"
+ ui.err "* ssl_ca_file: #{configuration.ssl_ca_file.inspect}"
+ ui.err "* trusted_certs_dir: #{configuration.trusted_certs_dir.inspect}"
+ end
+
+ def configuration
+ Chef::Config
+ end
+
+ def run
+ validate_uri
+
+ if verify_X509 && verify_cert && verify_cert_host
+ ui.msg "Successfully verified certificates from `#{host}'"
+ else
+ exit 1
+ end
+ end
+
+ private
+
+ def trusted_certificates
+ if configuration.trusted_certs_dir && Dir.exist?(configuration.trusted_certs_dir)
+ glob_dir = ChefConfig::PathHelper.escape_glob_dir(configuration.trusted_certs_dir)
+ Dir.glob(File.join(glob_dir, "*.{crt,pem}"))
+ else
+ []
+ end
+ end
+
+ def check_X509_certificate(cert_file)
+ store = OpenSSL::X509::Store.new
+ cert = OpenSSL::X509::Certificate.new(IO.read(File.expand_path(cert_file)))
+ begin
+ store.add_cert(cert)
+ # test if the store can verify the cert we just added
+ unless store.verify(cert) # true if verified, false if not
+ return store.error_string
+ end
+ rescue OpenSSL::X509::StoreError => e
+ return e.message
+ end
+ nil
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/ssl_fetch.rb b/knife/lib/chef/knife/ssl_fetch.rb
new file mode 100644
index 0000000000..dfde59d3b5
--- /dev/null
+++ b/knife/lib/chef/knife/ssl_fetch.rb
@@ -0,0 +1,162 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class SslFetch < Chef::Knife
+
+ deps do
+ require "chef/config" unless defined?(Chef::Config)
+ require "pp" unless defined?(PP)
+ require "socket" unless defined?(Socket)
+ require "uri" unless defined?(URI)
+ require "openssl" unless defined?(OpenSSL)
+ require "chef/mixin/proxified_socket" unless defined?(Chef::Mixin::ProxifiedSocket)
+
+ include Chef::Mixin::ProxifiedSocket
+ end
+
+ banner "knife ssl fetch [URL] (options)"
+
+ def initialize(*args)
+ super
+ @uri = nil
+ end
+
+ def uri
+ @uri ||= begin
+ Chef::Log.trace("Checking SSL cert on #{given_uri}")
+ URI.parse(given_uri)
+ end
+ end
+
+ def given_uri
+ (name_args[0] || Chef::Config.chef_server_url)
+ end
+
+ def host
+ uri.host
+ end
+
+ def port
+ uri.port
+ end
+
+ def validate_uri
+ unless host && port
+ invalid_uri!
+ end
+ rescue URI::Error
+ invalid_uri!
+ end
+
+ def invalid_uri!
+ ui.error("Given URI: `#{given_uri}' is invalid")
+ show_usage
+ exit 1
+ end
+
+ def remote_cert_chain
+ tcp_connection = proxified_socket(host, port)
+ shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
+ shady_ssl_connection.connect
+ shady_ssl_connection.peer_cert_chain
+ end
+
+ def noverify_peer_ssl_context
+ @noverify_peer_ssl_context ||= begin
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ noverify_peer_context
+ end
+ end
+
+ def cn_of(certificate)
+ subject = certificate.subject
+ if cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
+ cn_field_tuple[1]
+ else
+ nil
+ end
+ end
+
+ # Convert the CN of a certificate into something that will work well as a
+ # filename. To do so, all `*` characters are converted to the string
+ # "wildcard" and then all characters other than alphanumeric and hyphen
+ # characters are converted to underscores.
+ # NOTE: There is some confusion about what the CN will contain when
+ # using internationalized domain names. RFC 6125 mandates that the ascii
+ # representation be used, but it is not clear whether this is followed in
+ # practice.
+ # https://tools.ietf.org/html/rfc6125#section-6.4.2
+ def normalize_cn(cn)
+ cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, "_")
+ end
+
+ def configuration
+ Chef::Config
+ end
+
+ def trusted_certs_dir
+ configuration.trusted_certs_dir
+ end
+
+ def write_cert(cert)
+ FileUtils.mkdir_p(trusted_certs_dir)
+ cn = cn_of(cert)
+ filename = cn.nil? ? "#{host}_#{Time.new.to_i}" : normalize_cn(cn)
+ full_path = File.join(trusted_certs_dir, "#{filename}.crt")
+ ui.msg("Adding certificate for #{filename} in #{full_path}")
+ File.open(full_path, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f|
+ f.print(cert.to_s)
+ end
+ end
+
+ def run
+ validate_uri
+ ui.warn(<<~TRUST_TRUST)
+ Certificates from #{host} will be fetched and placed in your trusted_cert
+ directory (#{trusted_certs_dir}).
+
+ Knife has no means to verify these are the correct certificates. You should
+ verify the authenticity of these certificates after downloading.
+
+ TRUST_TRUST
+ remote_cert_chain.each do |cert|
+ write_cert(cert)
+ end
+ rescue OpenSSL::SSL::SSLError => e
+ # 'unknown protocol' usually means you tried to connect to a non-ssl
+ # service. We handle that specially here, any other error we let bubble
+ # up (probably a bug of some sort).
+ raise unless e.message.include?("unknown protocol")
+
+ ui.error("The service at the given URI (#{uri}) does not accept SSL connections")
+
+ if uri.scheme == "http"
+ https_uri = uri.to_s.sub(/^http/, "https")
+ ui.error("Perhaps you meant to connect to '#{https_uri}'?")
+ end
+ exit 1
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/status.rb b/knife/lib/chef/knife/status.rb
new file mode 100644
index 0000000000..2e72f0a03b
--- /dev/null
+++ b/knife/lib/chef/knife/status.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
+# Copyright:: Copyright 2010-2020, Ian Meyer
+# 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 "../knife"
+require_relative "core/status_presenter"
+require_relative "core/formatting_options"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class Status < Knife
+ include Knife::Core::FormattingOptions
+
+ deps do
+ require "chef/search/query" unless defined?(Chef::Search::Query)
+ end
+
+ banner "knife status QUERY (options)"
+
+ option :run_list,
+ short: "-r",
+ long: "--run-list",
+ description: "Show the run list"
+
+ option :sort_reverse,
+ short: "-s",
+ long: "--sort-reverse",
+ description: "Sort the status list by last run time descending"
+
+ option :hide_by_mins,
+ long: "--hide-by-mins MINS",
+ description: "Hide nodes that have run #{ChefUtils::Dist::Infra::CLIENT} in the last MINS minutes"
+
+ def append_to_query(term)
+ @query << " AND " unless @query.empty?
+ @query << term
+ end
+
+ def run
+ ui.use_presenter Knife::Core::StatusPresenter
+
+ if config[:long_output]
+ opts = {}
+ else
+ opts = { filter_result:
+ { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
+ cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"],
+ platform_version: ["platform_version"], chef_environment: ["chef_environment"] } }
+ end
+
+ @query ||= ""
+ append_to_query(@name_args[0]) if @name_args[0]
+ append_to_query("chef_environment:#{config[:environment]}") if config[:environment]
+
+ if config[:hide_by_mins]
+ hide_by_mins = config[:hide_by_mins].to_i
+ time = Time.now.to_i
+ # AND NOT is not valid lucene syntax, so don't use append_to_query
+ @query << " " unless @query.empty?
+ @query << "NOT ohai_time:[#{(time - hide_by_mins * 60)} TO #{time}]"
+ end
+
+ @query = @query.empty? ? "*:*" : @query
+
+ all_nodes = []
+ q = Chef::Search::Query.new
+ Chef::Log.info("Sending query: #{@query}")
+ q.search(:node, @query, opts) do |node|
+ all_nodes << node
+ end
+
+ all_nodes.sort_by! { |n| n["ohai_time"] || 0 }
+ all_nodes.reverse! if config[:sort_reverse] || config[:sort_status_reverse]
+
+ output(all_nodes)
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/supermarket_download.rb b/knife/lib/chef/knife/supermarket_download.rb
new file mode 100644
index 0000000000..88948f131d
--- /dev/null
+++ b/knife/lib/chef/knife/supermarket_download.rb
@@ -0,0 +1,119 @@
+#
+# Author:: Christopher Webber (<cwebber@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class SupermarketDownload < Knife
+
+ banner "knife supermarket download COOKBOOK [VERSION] (options)"
+ category "supermarket"
+
+ deps do
+ require "fileutils" unless defined?(FileUtils)
+ end
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "The filename to write to."
+
+ option :force,
+ long: "--force",
+ description: "Force download deprecated version."
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ if current_cookbook_deprecated?
+ message = "DEPRECATION: This cookbook has been deprecated. "
+ replacement = replacement_cookbook
+ if !replacement.to_s.strip.empty?
+ message << "It has been replaced by #{replacement}."
+ else
+ message << "No replacement has been defined."
+ end
+ ui.warn message
+
+ unless config[:force]
+ ui.warn "Use --force to force download deprecated cookbook."
+ return
+ end
+ end
+
+ download_cookbook
+ end
+
+ def version
+ @version = desired_cookbook_data["version"]
+ end
+
+ private
+
+ def cookbooks_api_url
+ "#{config[:supermarket_site]}/api/v1/cookbooks"
+ end
+
+ def current_cookbook_data
+ @current_cookbook_data ||= noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}"
+ end
+
+ def current_cookbook_deprecated?
+ current_cookbook_data["deprecated"] == true
+ end
+
+ def desired_cookbook_data
+ @desired_cookbook_data ||= begin
+ uri = if @name_args.length == 1
+ current_cookbook_data["latest_version"]
+ else
+ specific_cookbook_version_url
+ end
+
+ noauth_rest.get uri
+ end
+ end
+
+ def download_cookbook
+ ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
+ tf = noauth_rest.streaming_request(desired_cookbook_data["file"])
+
+ ::FileUtils.cp tf.path, download_location
+ ui.info "Cookbook saved: #{download_location}"
+ end
+
+ def download_location
+ config[:file] ||= File.join Dir.pwd, "#{@name_args[0]}-#{version}.tar.gz"
+ config[:file]
+ end
+
+ def replacement_cookbook
+ File.basename(current_cookbook_data["replacement"] || "")
+ end
+
+ def specific_cookbook_version_url
+ "#{cookbooks_api_url}/#{@name_args[0]}/versions/#{@name_args[1].tr(".", "_")}"
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/supermarket_install.rb b/knife/lib/chef/knife/supermarket_install.rb
new file mode 100644
index 0000000000..c979a4d6f4
--- /dev/null
+++ b/knife/lib/chef/knife/supermarket_install.rb
@@ -0,0 +1,192 @@
+#
+# Author:: Christopher Webber (<cwebber@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class SupermarketInstall < Knife
+
+ deps do
+ require "chef/exceptions" unless defined?(Chef::Exceptions)
+ require "shellwords" unless defined?(Shellwords)
+ require "mixlib/archive" unless defined?(Mixlib::Archive)
+ require_relative "core/cookbook_scm_repo"
+ require "chef/cookbook/metadata" unless defined?(Chef::Cookbook::Metadata)
+ end
+
+ banner "knife supermarket install COOKBOOK [VERSION] (options)"
+ category "supermarket"
+
+ option :no_deps,
+ short: "-D",
+ long: "--skip-dependencies",
+ boolean: true,
+ default: false,
+ description: "Skips automatic dependency installation."
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| o.split(":") }
+
+ option :default_branch,
+ short: "-B BRANCH",
+ long: "--branch BRANCH",
+ description: "Default branch to work with.",
+ default: "master"
+
+ option :use_current_branch,
+ short: "-b",
+ long: "--use-current-branch",
+ description: "Use the current branch.",
+ boolean: true,
+ default: false
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ attr_reader :cookbook_name
+ attr_reader :vendor_path
+
+ def run
+ if config[:cookbook_path]
+ Chef::Config[:cookbook_path] = config[:cookbook_path]
+ else
+ config[:cookbook_path] = Chef::Config[:cookbook_path]
+ end
+
+ @cookbook_name = parse_name_args!
+ # Check to ensure we have a valid source of cookbooks before continuing
+ #
+ @install_path = File.expand_path(Array(config[:cookbook_path]).first)
+ ui.info "Installing #{@cookbook_name} to #{@install_path}"
+
+ @repo = CookbookSCMRepo.new(@install_path, ui, config)
+ # cookbook_path = File.join(vendor_path, name_args[0])
+ upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
+
+ @repo.sanity_check
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ @repo.prepare_to_import(@cookbook_name)
+ end
+
+ downloader = download_cookbook_to(upstream_file)
+ clear_existing_files(File.join(@install_path, @cookbook_name))
+ extract_cookbook(upstream_file, downloader.version)
+
+ # TODO: it'd be better to store these outside the cookbook repo and
+ # keep them around, e.g., in ~/Library/Caches on macOS.
+ ui.info("Removing downloaded tarball")
+ File.unlink(upstream_file)
+
+ if @repo.finalize_updates_to(@cookbook_name, downloader.version)
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ @repo.merge_updates_from(@cookbook_name, downloader.version)
+ else
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ end
+
+ unless config[:no_deps]
+ preferred_metadata.dependencies.each_key do |cookbook|
+ # Doesn't do versions.. yet
+ nv = self.class.new
+ nv.config = config
+ nv.name_args = [ cookbook ]
+ nv.run
+ end
+ end
+ end
+
+ def parse_name_args!
+ if name_args.empty?
+ ui.error("Please specify a cookbook to download and install.")
+ exit 1
+ elsif name_args.size >= 2
+ unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2
+ ui.error("Installing multiple cookbooks at once is not supported.")
+ exit 1
+ end
+ end
+ name_args.first
+ end
+
+ def download_cookbook_to(download_path)
+ downloader = Chef::Knife::SupermarketDownload.new
+ downloader.config[:file] = download_path
+ downloader.config[:supermarket_site] = config[:supermarket_site]
+ downloader.name_args = name_args
+ downloader.run
+ downloader
+ end
+
+ def extract_cookbook(upstream_file, version)
+ ui.info("Uncompressing #{@cookbook_name} version #{version}.")
+ Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false)
+ end
+
+ def clear_existing_files(cookbook_path)
+ ui.info("Removing pre-existing version.")
+ FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
+ end
+
+ def convert_path(upstream_file)
+ # converts a Windows path (C:\foo) to a mingw path (/c/foo)
+ if ENV["MSYSTEM"] == "MINGW32"
+ upstream_file.sub(/^([[:alpha:]]):/, '/\1')
+ else
+ Shellwords.escape upstream_file
+ end
+ end
+
+ # Get the preferred metadata path on disk. Chef prefers the metadata.rb
+ # over the metadata.json.
+ #
+ # @raise if there is no metadata in the cookbook
+ #
+ # @return [Chef::Cookbook::Metadata]
+ def preferred_metadata
+ md = Chef::Cookbook::Metadata.new
+
+ rb = File.join(@install_path, @cookbook_name, "metadata.rb")
+ if File.exist?(rb)
+ md.from_file(rb)
+ return md
+ end
+
+ json = File.join(@install_path, @cookbook_name, "metadata.json")
+ if File.exist?(json)
+ json = IO.read(json)
+ md.from_json(json)
+ return md
+ end
+
+ raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/supermarket_list.rb b/knife/lib/chef/knife/supermarket_list.rb
index 7dca8d031b..7dca8d031b 100644
--- a/lib/chef/knife/supermarket_list.rb
+++ b/knife/lib/chef/knife/supermarket_list.rb
diff --git a/lib/chef/knife/supermarket_search.rb b/knife/lib/chef/knife/supermarket_search.rb
index 57befaed35..57befaed35 100644
--- a/lib/chef/knife/supermarket_search.rb
+++ b/knife/lib/chef/knife/supermarket_search.rb
diff --git a/knife/lib/chef/knife/supermarket_share.rb b/knife/lib/chef/knife/supermarket_share.rb
new file mode 100644
index 0000000000..61fe3b583b
--- /dev/null
+++ b/knife/lib/chef/knife/supermarket_share.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Christopher Webber (<cwebber@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class SupermarketShare < Knife
+
+ include Chef::Mixin::ShellOut
+
+ deps do
+ require "chef/cookbook_loader" unless defined?(Chef::CookbookLoader)
+ require "chef/cookbook_uploader" unless defined?(Chef::CookbookUploader)
+ require "chef/knife/core/cookbook_site_streaming_uploader" unless defined?(Chef::Knife::Core::CookbookSiteStreamingUploader)
+ require "chef/mixin/shell_out" unless defined?(Chef::Mixin::ShellOut)
+ end
+
+ banner "knife supermarket share COOKBOOK [CATEGORY] (options)"
+ category "supermarket"
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in.",
+ proc: lambda { |o| Chef::Config.cookbook_path = o.split(":") }
+
+ option :dry_run,
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what files will be uploaded to Supermarket."
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
+ if @name_args.length < 1
+ show_usage
+ ui.fatal("You must specify the cookbook name.")
+ exit(1)
+ elsif @name_args.length < 2
+ cookbook_name = @name_args[0]
+ category = get_category(cookbook_name)
+ else
+ cookbook_name = @name_args[0]
+ category = @name_args[1]
+ end
+
+ cl = Chef::CookbookLoader.new(config[:cookbook_path])
+ if cl.cookbook_exists?(cookbook_name)
+ cookbook = cl[cookbook_name]
+ Chef::CookbookUploader.new(cookbook).validate_cookbooks
+ tmp_cookbook_dir = Chef::Knife::Core::CookbookSiteStreamingUploader.create_build_dir(cookbook)
+ begin
+ Chef::Log.trace("Temp cookbook directory is #{tmp_cookbook_dir.inspect}")
+ ui.info("Making tarball #{cookbook_name}.tgz")
+ shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", cwd: tmp_cookbook_dir)
+ rescue => e
+ ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ if config[:dry_run]
+ ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.")
+ result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", cwd: tmp_cookbook_dir)
+ ui.info(result.stdout)
+ FileUtils.rm_rf tmp_cookbook_dir
+ return
+ end
+
+ begin
+ do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key])
+ ui.info("Upload complete")
+ Chef::Log.trace("Removing local staging directory at #{tmp_cookbook_dir}")
+ FileUtils.rm_rf tmp_cookbook_dir
+ rescue => e
+ ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ else
+ ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.")
+ exit(1)
+ end
+ end
+
+ def get_category(cookbook_name)
+ data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}")
+ data["category"]
+ rescue => e
+ return "Other" if e.is_a?(Net::HTTPClientException) && e.response.code == "404"
+
+ ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.trace("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+
+ def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
+ uri = "#{config[:supermarket_site]}/api/v1/cookbooks"
+
+ category_string = Chef::JSONCompat.to_json({ "category" => cookbook_category })
+
+ http_resp = Chef::Knife::Core::CookbookSiteStreamingUploader.post(uri, user_id, user_secret_filename, {
+ tarball: File.open(cookbook_filename),
+ cookbook: category_string,
+ })
+
+ res = Chef::JSONCompat.from_json(http_resp.body)
+ if http_resp.code.to_i != 201
+ if res["error_messages"]
+ if /Version already exists/.match?(res["error_messages"][0])
+ ui.error "The same version of this cookbook already exists on Supermarket."
+ exit(1)
+ else
+ ui.error (res["error_messages"][0]).to_s
+ exit(1)
+ end
+ else
+ ui.error "Unknown error while sharing cookbook"
+ ui.error "Server response: #{http_resp.body}"
+ exit(1)
+ end
+ end
+ res
+ end
+
+ def tar_cmd
+ unless @tar_cmd
+ @tar_cmd = "tar"
+ begin
+ # Unix and Mac only - prefer gnutar
+ if shell_out("which gnutar").exitstatus.equal?(0)
+ @tar_cmd = "gnutar"
+ end
+ rescue Errno::ENOENT
+ end
+ end
+ @tar_cmd
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/supermarket_show.rb b/knife/lib/chef/knife/supermarket_show.rb
index 7237cf0bc7..7237cf0bc7 100644
--- a/lib/chef/knife/supermarket_show.rb
+++ b/knife/lib/chef/knife/supermarket_show.rb
diff --git a/knife/lib/chef/knife/supermarket_unshare.rb b/knife/lib/chef/knife/supermarket_unshare.rb
new file mode 100644
index 0000000000..8c86769804
--- /dev/null
+++ b/knife/lib/chef/knife/supermarket_unshare.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Christopher Webber (<cwebber@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class SupermarketUnshare < Knife
+
+ deps do
+ require "chef/json_compat" unless defined?(Chef::JSONCompat)
+ end
+
+ banner "knife supermarket unshare COOKBOOK"
+ category "supermarket"
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "The URL of the Supermarket site.",
+ default: "https://supermarket.chef.io"
+
+ def run
+ @cookbook_name = @name_args[0]
+ if @cookbook_name.nil?
+ show_usage
+ ui.fatal "You must provide the name of the cookbook to unshare"
+ exit 1
+ end
+
+ confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
+
+ begin
+ rest.delete "#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}"
+ rescue Net::HTTPClientException => e
+ raise e unless /Forbidden/.match?(e.message)
+
+ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
+ exit 1
+ end
+
+ ui.info "Unshared all versions of the cookbook #{@cookbook_name}"
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/tag_create.rb b/knife/lib/chef/knife/tag_create.rb
new file mode 100644
index 0000000000..ed7d37e7b8
--- /dev/null
+++ b/knife/lib/chef/knife/tag_create.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Nuo Yan (<nuo@chef.io>)
+# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
+
+class Chef
+ class Knife
+ class TagCreate < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ end
+
+ banner "knife tag create NODE TAG ..."
+
+ def run
+ name = @name_args[0]
+ tags = @name_args[1..]
+
+ if name.nil? || tags.nil? || tags.empty?
+ show_usage
+ ui.fatal("You must specify a node name and at least one tag.")
+ exit 1
+ end
+
+ node = Chef::Node.load name
+ tags.each do |tag|
+ (node.tags << tag).uniq!
+ end
+ node.save
+ ui.info("Created tags #{tags.join(", ")} for node #{name}.")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/tag_delete.rb b/knife/lib/chef/knife/tag_delete.rb
new file mode 100644
index 0000000000..539ae39273
--- /dev/null
+++ b/knife/lib/chef/knife/tag_delete.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Nuo Yan (<nuo@chef.io>)
+# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
+
+class Chef
+ class Knife
+ class TagDelete < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ end
+
+ banner "knife tag delete NODE TAG ..."
+
+ def run
+ name = @name_args[0]
+ tags = @name_args[1..]
+
+ if name.nil? || tags.nil? || tags.empty?
+ show_usage
+ ui.fatal("You must specify a node name and at least one tag.")
+ exit 1
+ end
+
+ node = Chef::Node.load name
+ deleted_tags = []
+ tags.each do |tag|
+ unless node.tags.delete(tag).nil?
+ deleted_tags << tag
+ end
+ end
+ node.save
+ message = if deleted_tags.empty?
+ "Nothing has changed. The tags requested to be deleted do not exist."
+ else
+ "Deleted tags #{deleted_tags.join(", ")} for node #{name}."
+ end
+ ui.info(message)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/tag_list.rb b/knife/lib/chef/knife/tag_list.rb
new file mode 100644
index 0000000000..3ab960c361
--- /dev/null
+++ b/knife/lib/chef/knife/tag_list.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Nuo Yan (<nuo@chef.io>)
+# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
+
+class Chef
+ class Knife
+ class TagList < Knife
+
+ deps do
+ require "chef/node" unless defined?(Chef::Node)
+ end
+
+ banner "knife tag list NODE"
+
+ def run
+ name = @name_args[0]
+
+ if name.nil?
+ show_usage
+ ui.fatal("You must specify a node name.")
+ exit 1
+ end
+
+ node = Chef::Node.load(name)
+ output(node.tags)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/upload.rb b/knife/lib/chef/knife/upload.rb
new file mode 100644
index 0000000000..e8dd052e77
--- /dev/null
+++ b/knife/lib/chef/knife/upload.rb
@@ -0,0 +1,86 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Upload < Chef::ChefFS::Knife
+ banner "knife upload PATTERNS (options)"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/command_line" unless defined?(Chef::ChefFS::CommandLine)
+ end
+
+ option :recurse,
+ long: "--[no-]recurse",
+ boolean: true,
+ default: true,
+ description: "List directories recursively."
+
+ option :purge,
+ long: "--[no-]purge",
+ boolean: true,
+ default: false,
+ description: "Delete matching local files and directories that do not exist remotely."
+
+ option :force,
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks."
+
+ option :freeze,
+ long: "--[no-]freeze",
+ boolean: true,
+ default: false,
+ description: "Freeze cookbooks that get uploaded."
+
+ option :dry_run,
+ long: "--dry-run",
+ short: "-n",
+ boolean: true,
+ default: false,
+ description: "Don't take action, only print what would happen."
+
+ option :diff,
+ long: "--[no-]diff",
+ boolean: true,
+ default: true,
+ description: "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff."
+
+ def run
+ if name_args.length == 0
+ show_usage
+ ui.fatal("You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"")
+ exit 1
+ end
+
+ error = false
+ pattern_args.each do |pattern|
+ if Chef::ChefFS::FileSystem.copy_to(pattern, local_fs, chef_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
+ error = true
+ end
+ end
+ if error
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/user_create.rb b/knife/lib/chef/knife/user_create.rb
new file mode 100644
index 0000000000..009f193d46
--- /dev/null
+++ b/knife/lib/chef/knife/user_create.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Knife
+ class UserCreate < Knife
+
+ attr_accessor :user_field
+
+ deps do
+ require "chef/user_v1" unless defined?(Chef::UserV1)
+ end
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the private key to a file if the server generated one."
+
+ option :user_key,
+ long: "--user-key FILENAME",
+ description: "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ short: "-k",
+ long: "--prevent-keygen",
+ description: "API V1 (#{ChefUtils::Dist::Server::PRODUCT} 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+ boolean: true
+
+ option :orgname,
+ long: "--orgname ORGNAME",
+ short: "-o ORGNAME",
+ description: "Associate new user to an organization matching ORGNAME"
+
+ option :passwordprompt,
+ long: "--prompt-for-password",
+ short: "-p",
+ description: "Prompt for user password"
+
+ option :first_name,
+ long: "--first-name FIRST_NAME",
+ description: "First name for the user"
+
+ option :last_name,
+ long: "--last-name LAST_NAME",
+ description: "Last name for the user"
+
+ option :email,
+ long: "--email EMAIL",
+ description: "Email for the user"
+
+ option :password,
+ long: "--password PASSWORD",
+ description: "Password for the user"
+
+ banner "knife user create USERNAME --email EMAIL --password PASSWORD (options)"
+
+ def user
+ @user_field ||= Chef::UserV1.new
+ end
+
+ def run
+ test_mandatory_field(@name_args[0], "username")
+ user.username @name_args[0]
+
+ if @name_args.size > 1
+ ui.warn "[DEPRECATED] DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD options are deprecated and will be removed in future release. Use USERNAME --email --password TAGS option instead."
+ test_mandatory_field(@name_args[1], "display name")
+ user.display_name @name_args[1]
+ test_mandatory_field(@name_args[2], "first name")
+ user.first_name @name_args[2]
+ test_mandatory_field(@name_args[3], "last name")
+ user.last_name @name_args[3]
+ test_mandatory_field(@name_args[4], "email")
+ user.email @name_args[4]
+ password = config[:passwordprompt] ? prompt_for_password : @name_args[5]
+ else
+ test_mandatory_field(config[:email], "email")
+ test_mandatory_field(config[:password], "password") unless config[:passwordprompt]
+ user.display_name user.username
+ user.first_name config[:first_name] || ""
+ user.last_name config[:last_name] || ""
+ user.email config[:email]
+ password = config[:passwordprompt] ? prompt_for_password : config[:password]
+ end
+
+ unless password
+ ui.fatal "You must either provide a password or use the --prompt-for-password (-p) option"
+ exit 1
+ end
+
+ if config[:user_key] && config[:prevent_keygen]
+ show_usage
+ ui.fatal("You cannot pass --user-key and --prevent-keygen")
+ exit 1
+ end
+
+ if !config[:prevent_keygen] && !config[:user_key]
+ user.create_key(true)
+ end
+
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
+
+ if @name_args.size > 1
+ user_hash = {
+ username: user.username,
+ first_name: user.first_name,
+ last_name: user.last_name,
+ display_name: "#{user.first_name} #{user.last_name}",
+ email: user.email,
+ password: password,
+ }
+ else
+ user_hash = {
+ username: user.username,
+ first_name: user.first_name,
+ last_name: user.last_name,
+ display_name: user.display_name,
+ email: user.email,
+ password: password,
+ }
+ end
+
+ # Check the file before creating the user so the api is more transactional.
+ if config[:file]
+ file = config[:file]
+ unless File.exist?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
+ ui.fatal "File #{config[:file]} is not writable. Check permissions."
+ exit 1
+ end
+ end
+
+ final_user = root_rest.post("users/", user_hash)
+
+ if config[:orgname]
+ request_body = { user: user.username }
+ response = root_rest.post("organizations/#{config[:orgname]}/association_requests", request_body)
+ association_id = response["uri"].split("/").last
+ root_rest.put("users/#{user.username}/association_requests/#{association_id}", { response: "accept" })
+ end
+
+ ui.info("Created #{user.username}")
+ if final_user["private_key"]
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(final_user["private_key"])
+ end
+ else
+ ui.msg final_user["private_key"]
+ end
+ end
+ end
+
+ def prompt_for_password
+ ui.ask("Please enter the user's password: ", echo: false)
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/user_delete.rb b/knife/lib/chef/knife/user_delete.rb
new file mode 100644
index 0000000000..c1ab78174b
--- /dev/null
+++ b/knife/lib/chef/knife/user_delete.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class UserDelete < Knife
+
+ deps do
+ require "chef/org" unless defined? Chef::Org
+ end
+
+ banner "knife user delete USER (options)"
+
+ option :no_disassociate_user,
+ long: "--no-disassociate-user",
+ short: "-d",
+ description: "Don't disassociate the user first"
+
+ option :remove_from_admin_groups,
+ long: "--remove-from-admin-groups",
+ short: "-R",
+ description: "If the user is a member of any org admin groups, attempt to remove from those groups. Ignored if --no-disassociate-user is set."
+
+ attr_reader :username
+
+ def run
+ @username = @name_args[0]
+ admin_memberships = []
+ unremovable_memberships = []
+
+ if @username.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ ui.confirm "Do you want to delete the user #{username}"
+
+ unless config[:no_disassociate_user]
+ ui.stderr.puts("Checking organization memberships...")
+ orgs = org_memberships(username)
+ if orgs.length > 0
+ ui.stderr.puts("Checking admin group memberships for #{orgs.length} org(s).")
+ admin_memberships, unremovable_memberships = admin_group_memberships(orgs, username)
+ end
+
+ unless admin_memberships.empty?
+ unless config[:remove_from_admin_groups]
+ error_exit_admin_group_member!(username, admin_memberships)
+ end
+
+ unless unremovable_memberships.empty?
+ error_exit_cant_remove_admin_membership!(username, unremovable_memberships)
+ end
+ remove_from_admin_groups(admin_memberships, username)
+ end
+ disassociate_user(orgs, username)
+ end
+
+ delete_user(username)
+ end
+
+ def disassociate_user(orgs, username)
+ orgs.each { |org| org.dissociate_user(username) }
+ end
+
+ def org_memberships(username)
+ org_data = root_rest.get("users/#{username}/organizations")
+ org_data.map { |org| Chef::Org.new(org["organization"]["name"]) }
+ end
+
+ def remove_from_admin_groups(admin_of, username)
+ admin_of.each do |org|
+ ui.stderr.puts "Removing #{username} from admins group of '#{org.name}'"
+ org.remove_user_from_group("admins", username)
+ end
+ end
+
+ def admin_group_memberships(orgs, username)
+ admin_of = []
+ unremovable = []
+ orgs.each do |org|
+ if org.user_member_of_group?(username, "admins")
+ admin_of << org
+ if org.actor_delete_would_leave_admins_empty?
+ unremovable << org
+ end
+ end
+ end
+ [admin_of, unremovable]
+ end
+
+ def delete_user(username)
+ ui.stderr.puts "Deleting user #{username}."
+ root_rest.delete("users/#{username}")
+ end
+
+ # Error message that says how to removed from org
+ # admin groups before deleting
+ # Further
+ def error_exit_admin_group_member!(username, admin_of)
+ message = "#{username} is in the 'admins' group of the following organization(s):\n\n"
+ admin_of.each { |org| message << "- #{org.name}\n" }
+ message << <<~EOM
+
+ Run this command again with the --remove-from-admin-groups option to
+ remove the user from these admin group(s) automatically.
+
+ EOM
+ ui.fatal message
+ exit 1
+ end
+
+ def error_exit_cant_remove_admin_membership!(username, only_admin_of)
+ message = <<~EOM
+
+ #{username} is the only member of the 'admins' group of the
+ following organization(s):
+
+ EOM
+ only_admin_of.each { |org| message << "- #{org.name}\n" }
+ message << <<~EOM
+
+ Removing the only administrator of an organization can break it.
+ Assign additional users or groups to the admin group(s) before
+ deleting this user.
+
+ EOM
+ ui.fatal message
+ exit 1
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_dissociate.rb b/knife/lib/chef/knife/user_dissociate.rb
index 6af1559608..6af1559608 100644
--- a/lib/chef/knife/user_dissociate.rb
+++ b/knife/lib/chef/knife/user_dissociate.rb
diff --git a/knife/lib/chef/knife/user_edit.rb b/knife/lib/chef/knife/user_edit.rb
new file mode 100644
index 0000000000..fff8c6b70f
--- /dev/null
+++ b/knife/lib/chef/knife/user_edit.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class UserEdit < Knife
+
+ banner "knife user edit USER (options)"
+
+ option :input,
+ long: "--input FILENAME",
+ short: "-i FILENAME",
+ description: "Name of file to use for PUT or POST"
+
+ option :filename,
+ long: "--filename FILENAME",
+ short: "-f FILENAME",
+ description: "Write private key to FILENAME rather than STDOUT"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+ original_user = root_rest.get("users/#{@user_name}")
+ edited_user = get_updated_user(original_user)
+ if original_user != edited_user
+ result = root_rest.put("users/#{@user_name}", edited_user)
+ ui.msg("Saved #{@user_name}.")
+ unless result["private_key"].nil?
+ if config[:filename]
+ File.open(config[:filename], "w") do |f|
+ f.print(result["private_key"])
+ end
+ else
+ ui.msg result["private_key"]
+ end
+ end
+ else
+ ui.msg("User unchanged, not saving.")
+ end
+ end
+ end
+
+ private
+
+ # Check the options for ex: input or filename
+ # Read Or Open file to update user information
+ # return updated user
+ def get_updated_user(original_user)
+ if config[:input]
+ edited_user = JSON.parse(IO.read(config[:input]))
+ elsif config[:filename]
+ file = config[:filename]
+ unless File.exist?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
+ ui.fatal "File #{file} is not writable. Check permissions."
+ exit 1
+ else
+ output = Chef::JSONCompat.to_json_pretty(original_user)
+ File.open(file, "w") do |f|
+ f.sync = true
+ f.puts output
+ f.close
+ raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details." unless system("#{config[:editor]} #{f.path}")
+
+ edited_user = JSON.parse(IO.read(f.path))
+ end
+ end
+ else
+ edited_user = JSON.parse(edit_data(original_user, false))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_invite_add.rb b/knife/lib/chef/knife/user_invite_add.rb
index 1690147535..1690147535 100644
--- a/lib/chef/knife/user_invite_add.rb
+++ b/knife/lib/chef/knife/user_invite_add.rb
diff --git a/lib/chef/knife/user_invite_list.rb b/knife/lib/chef/knife/user_invite_list.rb
index 831774d1bf..831774d1bf 100644
--- a/lib/chef/knife/user_invite_list.rb
+++ b/knife/lib/chef/knife/user_invite_list.rb
diff --git a/lib/chef/knife/user_invite_rescind.rb b/knife/lib/chef/knife/user_invite_rescind.rb
index fd5804e10a..fd5804e10a 100644
--- a/lib/chef/knife/user_invite_rescind.rb
+++ b/knife/lib/chef/knife/user_invite_rescind.rb
diff --git a/lib/chef/knife/user_key_create.rb b/knife/lib/chef/knife/user_key_create.rb
index efc783dd7f..efc783dd7f 100644
--- a/lib/chef/knife/user_key_create.rb
+++ b/knife/lib/chef/knife/user_key_create.rb
diff --git a/lib/chef/knife/user_key_delete.rb b/knife/lib/chef/knife/user_key_delete.rb
index b4f84fdb7b..b4f84fdb7b 100644
--- a/lib/chef/knife/user_key_delete.rb
+++ b/knife/lib/chef/knife/user_key_delete.rb
diff --git a/lib/chef/knife/user_key_edit.rb b/knife/lib/chef/knife/user_key_edit.rb
index 15ef2ada1e..15ef2ada1e 100644
--- a/lib/chef/knife/user_key_edit.rb
+++ b/knife/lib/chef/knife/user_key_edit.rb
diff --git a/lib/chef/knife/user_key_list.rb b/knife/lib/chef/knife/user_key_list.rb
index 781998b301..781998b301 100644
--- a/lib/chef/knife/user_key_list.rb
+++ b/knife/lib/chef/knife/user_key_list.rb
diff --git a/lib/chef/knife/user_key_show.rb b/knife/lib/chef/knife/user_key_show.rb
index 2bf535c792..2bf535c792 100644
--- a/lib/chef/knife/user_key_show.rb
+++ b/knife/lib/chef/knife/user_key_show.rb
diff --git a/knife/lib/chef/knife/user_list.rb b/knife/lib/chef/knife/user_list.rb
new file mode 100644
index 0000000000..cb3b577541
--- /dev/null
+++ b/knife/lib/chef/knife/user_list.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class UserList < Knife
+
+ deps do
+ # is not used there, only in knife.
+ require "chef/user_v1" unless defined?(Chef::UserV1)
+ end
+
+ banner "knife user list (options)"
+
+ option :with_uri,
+ short: "-w",
+ long: "--with-uri",
+ description: "Show corresponding URIs."
+
+ def run
+ output(format_list_for_display(Chef::UserV1.list))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/user_password.rb b/knife/lib/chef/knife/user_password.rb
new file mode 100644
index 0000000000..2da3c3e285
--- /dev/null
+++ b/knife/lib/chef/knife/user_password.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Tyler Cloke (<tyler@getchef.com>)
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ class UserPassword < Knife
+ banner "knife user password USERNAME [PASSWORD | --enable-external-auth]"
+
+ option :enable_external_auth,
+ long: "--enable-external-auth",
+ short: "-e",
+ description: "Enable external authentication for this user (such as LDAP)"
+
+ def run
+ # check that correct number of args was passed, should be either
+ # USERNAME PASSWORD or USERNAME --enable-external-auth
+ #
+ # note that you can't pass USERNAME PASSWORD --enable-external-auth
+ unless (@name_args.length == 2 && !config[:enable_external_auth]) || (@name_args.length == 1 && config[:enable_external_auth])
+ show_usage
+ ui.fatal("You must pass two arguments")
+ ui.fatal("Note that --enable-external-auth cannot be passed with a password")
+ exit 1
+ end
+
+ user_name = @name_args[0]
+
+ # note that this will be nil if config[:enable_external_auth] is true
+ password = @name_args[1]
+
+ # since the API does not pass back whether recovery_authentication_enabled is
+ # true or false, there is no way of knowing if the user is using ldap or not,
+ # so we will update the user every time, instead of checking if we are actually
+ # changing anything before we PUT.
+ result = root_rest.get("users/#{user_name}")
+
+ result["password"] = password unless password.nil?
+
+ # if --enable-external-auth was passed, enable it, else disable it.
+ # there is never a situation where we would want to enable ldap
+ # AND change the password. changing the password means that the user
+ # wants to disable ldap and put user in recover (if they are using ldap).
+ result["recovery_authentication_enabled"] = !config[:enable_external_auth]
+
+ begin
+ root_rest.put("users/#{user_name}", result)
+ rescue => e
+ raise e
+ end
+
+ ui.msg("Authentication info updated for #{user_name}.")
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/user_reregister.rb b/knife/lib/chef/knife/user_reregister.rb
new file mode 100644
index 0000000000..cf2adbceb2
--- /dev/null
+++ b/knife/lib/chef/knife/user_reregister.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class UserReregister < Knife
+
+ deps do
+ require "chef/user_v1" unless defined?(Chef::UserV1)
+ end
+
+ banner "knife user reregister USER (options)"
+
+ option :file,
+ short: "-f FILE",
+ long: "--file FILE",
+ description: "Write the private key to a file."
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ user = Chef::UserV1.load(@user_name)
+ user.reregister
+ Chef::Log.trace("Updated user data: #{user.inspect}")
+ key = user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
+ end
+ end
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/user_show.rb b/knife/lib/chef/knife/user_show.rb
new file mode 100644
index 0000000000..ea2b06b753
--- /dev/null
+++ b/knife/lib/chef/knife/user_show.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "../knife"
+
+class Chef
+ class Knife
+ class UserShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ banner "knife user show USER (options)"
+
+ option :with_orgs,
+ long: "--with-orgs",
+ short: "-l"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ results = root_rest.get("users/#{@user_name}")
+ if config[:with_orgs]
+ orgs = root_rest.get("users/#{@user_name}/organizations")
+ results["organizations"] = orgs.map { |o| o["organization"]["name"] }
+ end
+ output(format_for_display(results))
+ end
+
+ end
+ end
+end
diff --git a/knife/lib/chef/knife/version.rb b/knife/lib/chef/knife/version.rb
new file mode 100644
index 0000000000..0fb6f441f4
--- /dev/null
+++ b/knife/lib/chef/knife/version.rb
@@ -0,0 +1,24 @@
+# Copyright:: Copyright (c) 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.
+#
+
+class Chef
+ class Knife
+ KNIFE_ROOT = File.expand_path("../..", __dir__)
+ VERSION = "17.3.0".freeze
+ end
+end
+
+
diff --git a/knife/lib/chef/knife/xargs.rb b/knife/lib/chef/knife/xargs.rb
new file mode 100644
index 0000000000..fc82d390cb
--- /dev/null
+++ b/knife/lib/chef/knife/xargs.rb
@@ -0,0 +1,282 @@
+#
+# 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 "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Xargs < Chef::ChefFS::Knife
+ banner "knife xargs [COMMAND] (options)"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "chef/chef_fs/file_system/exceptions" unless defined?(Chef::ChefFS::FileSystem::Exceptions)
+ end
+
+ # TODO modify to remote-only / local-only pattern (more like delete)
+ option :local,
+ long: "--local",
+ boolean: true,
+ description: "Xargs local files instead of remote."
+
+ option :patterns,
+ long: "--pattern [PATTERN]",
+ short: "-p [PATTERN]",
+ description: "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
+ arg_arity: [1, -1]
+
+ option :diff,
+ long: "--[no-]diff",
+ default: true,
+ boolean: true,
+ description: "Whether to show a diff when files change (default: true)."
+
+ option :dry_run,
+ long: "--dry-run",
+ boolean: true,
+ description: "Prevents changes from actually being uploaded to the server."
+
+ option :force,
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)."
+
+ option :replace_first,
+ long: "--replace-first REPLACESTR",
+ short: "-J REPLACESTR",
+ description: "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
+
+ option :replace_all,
+ long: "--replace REPLACESTR",
+ short: "-I REPLACESTR",
+ description: "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
+
+ option :max_arguments_per_command,
+ long: "--max-args MAXARGS",
+ short: "-n MAXARGS",
+ description: "Maximum number of arguments per command line."
+
+ option :max_command_line,
+ long: "--max-chars LENGTH",
+ short: "-s LENGTH",
+ description: "Maximum size of command line, in characters."
+
+ option :verbose_commands,
+ short: "-t",
+ description: "Print command to be run on the command line."
+
+ option :null_separator,
+ short: "-0",
+ boolean: true,
+ description: "Use the NULL character (\0) as a separator, instead of whitespace."
+
+ def run
+ error = false
+ # Get the matches (recursively)
+ files = []
+ pattern_args_from(get_patterns).each do |pattern|
+ Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
+ if result.dir?
+ # TODO option to include directories
+ ui.warn "#{format_path(result)}: is a directory. Will not run #{command} on it."
+ else
+ files << result
+ ran = false
+
+ # If the command would be bigger than max command line, back it off a bit
+ # and run a slightly smaller command (with one less arg)
+ if config[:max_command_line]
+ command, tempfiles = create_command(files)
+ begin
+ if command.length > config[:max_command_line].to_i
+ if files.length > 1
+ command, tempfiles_minus_one = create_command(files[0..-2])
+ begin
+ error = true if xargs_files(command, tempfiles_minus_one)
+ files = [ files[-1] ]
+ ran = true
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ else
+ error = true if xargs_files(command, tempfiles)
+ files = [ ]
+ ran = true
+ end
+ end
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+
+ # If the command has hit the limit for the # of arguments, run it
+ if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
+ command, tempfiles = create_command(files)
+ begin
+ error = true if xargs_files(command, tempfiles)
+ files = []
+ ran = true
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+ end
+ end
+ end
+
+ # Any leftovers commands shall be run
+ if files.size > 0
+ command, tempfiles = create_command(files)
+ begin
+ error = true if xargs_files(command, tempfiles)
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+
+ if error
+ exit 1
+ end
+ end
+
+ def get_patterns
+ if config[:patterns]
+ [ config[:patterns] ].flatten
+ elsif config[:null_separator]
+ stdin.binmode
+ stdin.read.split("\000")
+ else
+ stdin.read.split(/\s+/)
+ end
+ end
+
+ def create_command(files)
+ command = name_args.join(" ")
+
+ # Create the (empty) tempfiles
+ tempfiles = {}
+ begin
+ # Create the temporary files
+ files.each do |file|
+ tempfile = Tempfile.new(file.name)
+ tempfiles[tempfile] = { file: file }
+ end
+ rescue
+ destroy_tempfiles(files)
+ raise
+ end
+
+ # Create the command
+ paths = tempfiles.keys.map(&:path).join(" ")
+ if config[:replace_all]
+ final_command = command.gsub(config[:replace_all], paths)
+ elsif config[:replace_first]
+ final_command = command.sub(config[:replace_first], paths)
+ else
+ final_command = "#{command} #{paths}"
+ end
+
+ [final_command, tempfiles]
+ end
+
+ def destroy_tempfiles(tempfiles)
+ # Unlink the files now that we're done with them
+ tempfiles.each_key(&:close!)
+ end
+
+ def xargs_files(command, tempfiles)
+ error = false
+ # Create the temporary files
+ tempfiles.each_pair do |tempfile, file|
+
+ value = file[:file].read
+ file[:value] = value
+ tempfile.open
+ tempfile.write(value)
+ tempfile.close
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path(e.entry)}: #{e.reason}."
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+
+ end
+
+ return error if error && tempfiles.size == 0
+
+ # Run the command
+ if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
+ output sub_filenames(command, tempfiles)
+ end
+ command_output = `#{command}`
+ command_output = sub_filenames(command_output, tempfiles)
+ stdout.write command_output
+
+ # Check if the output is different
+ tempfiles.each_pair do |tempfile, file|
+ # Read the new output
+ new_value = IO.binread(tempfile.path)
+
+ # Upload the output if different
+ if config[:force] || new_value != file[:value]
+ if config[:dry_run]
+ output "Would update #{format_path(file[:file])}"
+ else
+ file[:file].write(new_value)
+ output "Updated #{format_path(file[:file])}"
+ end
+ end
+
+ # Print a diff of what was uploaded
+ if config[:diff] && new_value != file[:value]
+ old_file = Tempfile.open(file[:file].name)
+ begin
+ old_file.write(file[:value])
+ old_file.close
+
+ diff = `diff -u #{old_file.path} #{tempfile.path}`
+ diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
+ diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
+ stdout.write diff
+ ensure
+ old_file.close!
+ end
+ end
+ end
+
+ error
+ end
+
+ def sub_filenames(str, tempfiles)
+ tempfiles.each_pair do |tempfile, file|
+ str = str.gsub(tempfile.path, format_path(file[:file]))
+ end
+ str
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/yaml_convert.rb b/knife/lib/chef/knife/yaml_convert.rb
index 6bd2d1c0ea..6bd2d1c0ea 100644
--- a/lib/chef/knife/yaml_convert.rb
+++ b/knife/lib/chef/knife/yaml_convert.rb
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog b/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog
new file mode 100644
index 0000000000..bb34505e65
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/changelog
@@ -0,0 +1,5 @@
+chef-integration-test (1.0-1) unstable; urgency=low
+
+ * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/compat b/knife/spec/data/apt/chef-integration-test-1.0/debian/compat
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/control b/knife/spec/data/apt/chef-integration-test-1.0/debian/control
new file mode 100644
index 0000000000..e77b01b1d2
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright b/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright
new file mode 100644
index 0000000000..e840a11cca
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+ Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+ Opscode, Inc.
+
+Copyright:
+
+ Copyright 2010-2016, Chef Software Inc.
+
+License:
+
+ 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.
+
+The Debian packaging is:
+
+ Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/files b/knife/spec/data/apt/chef-integration-test-1.0/debian/files
new file mode 100644
index 0000000000..536f4beabc
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/files
@@ -0,0 +1 @@
+chef-integration-test_1.0-1_amd64.deb ruby extra
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/rules b/knife/spec/data/apt/chef-integration-test-1.0/debian/rules
new file mode 100755
index 0000000000..b760bee7f4
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
diff --git a/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format b/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format
new file mode 100644
index 0000000000..163aaf8d82
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.0/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog b/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog
new file mode 100644
index 0000000000..fc693c1ec8
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/changelog
@@ -0,0 +1,11 @@
+chef-integration-test (1.1-1) unstable; urgency=low
+
+ * New upstream release (1.1)
+
+ -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 10:09:34 -0600
+
+chef-integration-test (1.0-1) unstable; urgency=low
+
+ * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/compat b/knife/spec/data/apt/chef-integration-test-1.1/debian/compat
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/control b/knife/spec/data/apt/chef-integration-test-1.1/debian/control
new file mode 100644
index 0000000000..e77b01b1d2
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright b/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright
new file mode 100644
index 0000000000..e840a11cca
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+ Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+ Opscode, Inc.
+
+Copyright:
+
+ Copyright 2010-2016, Chef Software Inc.
+
+License:
+
+ 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.
+
+The Debian packaging is:
+
+ Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/files b/knife/spec/data/apt/chef-integration-test-1.1/debian/files
new file mode 100644
index 0000000000..d72553c027
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/files
@@ -0,0 +1 @@
+chef-integration-test_1.1-1_amd64.deb ruby extra
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/rules b/knife/spec/data/apt/chef-integration-test-1.1/debian/rules
new file mode 100755
index 0000000000..b760bee7f4
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
diff --git a/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format b/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format
new file mode 100644
index 0000000000..163aaf8d82
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test-1.1/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog b/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog
new file mode 100644
index 0000000000..1b846f8f4d
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/changelog
@@ -0,0 +1,5 @@
+chef-integration-test2 (1.0-1) unstable; urgency=low
+
+ * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua@opscode.com> Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log
new file mode 100644
index 0000000000..2d06fcdad9
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log
@@ -0,0 +1,45 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
+dh_install
+dh_installdocs
+dh_installchangelogs
+dh_installexamples
+dh_installman
+dh_installcatalogs
+dh_installcron
+dh_installdebconf
+dh_installemacsen
+dh_installifupdown
+dh_installinfo
+dh_pysupport
+dh_installinit
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installlogcheck
+dh_installlogrotate
+dh_installpam
+dh_installppp
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_bugfiles
+dh_lintian
+dh_gconf
+dh_icons
+dh_perl
+dh_usrlocal
+dh_link
+dh_compress
+dh_fixperms
+dh_strip
+dh_makeshlibs
+dh_shlibdeps
+dh_installdeb
+dh_gencontrol
+dh_md5sums
+dh_builddeb
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars
new file mode 100644
index 0000000000..abd3ebebc3
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars
@@ -0,0 +1 @@
+misc:Depends=
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles
new file mode 100644
index 0000000000..ac4307eadf
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles
@@ -0,0 +1 @@
+/usr/share/doc/chef-integration-test2/copyright
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control
new file mode 100644
index 0000000000..27d53d9750
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control
@@ -0,0 +1,10 @@
+Package: chef-integration-test2
+Version: 1.0-1
+Architecture: amd64
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Installed-Size: 36
+Section: ruby
+Priority: extra
+Homepage: http://tickets.opscode.com
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums
new file mode 100644
index 0000000000..144b7931de
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums
@@ -0,0 +1 @@
+8b3b9ff6cdfe7d7b2b8b8d4f7b9e381f usr/share/doc/chef-integration-test2/changelog.Debian.gz
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat b/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles b/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles
new file mode 100644
index 0000000000..ac4307eadf
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/conffiles
@@ -0,0 +1 @@
+/usr/share/doc/chef-integration-test2/copyright
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/control b/knife/spec/data/apt/chef-integration-test2-1.0/debian/control
new file mode 100644
index 0000000000..f2731a6848
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test2
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test2
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright b/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright
new file mode 100644
index 0000000000..e840a11cca
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+ Joshua Timberman <Joshua Timberman <joshua@opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+ Opscode, Inc.
+
+Copyright:
+
+ Copyright 2010-2016, Chef Software Inc.
+
+License:
+
+ 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.
+
+The Debian packaging is:
+
+ Copyright 2010-2016, Chef Software Inc. (<legal@chef.io>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/files b/knife/spec/data/apt/chef-integration-test2-1.0/debian/files
new file mode 100644
index 0000000000..640e4c6414
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/files
@@ -0,0 +1 @@
+chef-integration-test2_1.0-1_amd64.deb ruby extra
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules b/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules
new file mode 100755
index 0000000000..b760bee7f4
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
diff --git a/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format b/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format
new file mode 100644
index 0000000000..163aaf8d82
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2-1.0/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz b/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz
new file mode 100644
index 0000000000..6c002a7420
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc b/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc
new file mode 100644
index 0000000000..b247f49346
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0-1.dsc
@@ -0,0 +1,18 @@
+Format: 3.0 (quilt)
+Source: chef-integration-test2
+Binary: chef-integration-test2
+Architecture: any
+Version: 1.0-1
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Homepage: http://tickets.opscode.com
+Standards-Version: 3.8.4
+Build-Depends: debhelper (>= 7.0.50~)
+Checksums-Sha1:
+ 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz
+ 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz
+Checksums-Sha256:
+ 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz
+ 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz
+Files:
+ f1f7d7bbe63ad631d25d707f564a8d33 248 chef-integration-test2_1.0.orig.tar.gz
+ 4fab5c1cd9a7b47c4f319af776f48a1d 1369 chef-integration-test2_1.0-1.debian.tar.gz
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build
new file mode 100644
index 0000000000..8ef31d3ccf
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.build
@@ -0,0 +1,91 @@
+ dpkg-buildpackage -rfakeroot -D -us -uc
+dpkg-buildpackage: warning: using a gain-root-command while being root
+dpkg-buildpackage: set CFLAGS to default value: -g -O2
+dpkg-buildpackage: set CPPFLAGS to default value:
+dpkg-buildpackage: set LDFLAGS to default value: -Wl,-Bsymbolic-functions
+dpkg-buildpackage: set FFLAGS to default value: -g -O2
+dpkg-buildpackage: set CXXFLAGS to default value: -g -O2
+dpkg-buildpackage: source package chef-integration-test2
+dpkg-buildpackage: source version 1.0-1
+dpkg-buildpackage: source changed by Joshua Timberman <joshua@opscode.com>
+dpkg-buildpackage: host architecture amd64
+ fakeroot debian/rules clean
+dh clean
+ dh_testdir
+ dh_auto_clean
+ dh_clean
+ dpkg-source -b chef-integration-test2-1.0
+dpkg-source: info: using source format `3.0 (quilt)'
+dpkg-source: info: building chef-integration-test2 using existing ./chef-integration-test2_1.0.orig.tar.gz
+dpkg-source: warning: ignoring deletion of directory cache
+dpkg-source: warning: ignoring deletion of directory cache/chef-integration-test2
+dpkg-source: warning: ignoring deletion of file cache/chef-integration-test2/contents
+dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.debian.tar.gz
+dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.dsc
+ debian/rules build
+dh build
+ dh_testdir
+ dh_auto_configure
+ dh_auto_build
+ dh_auto_test
+ fakeroot debian/rules binary
+dh binary
+ dh_testroot
+ dh_prep
+ dh_installdirs
+ dh_auto_install
+ dh_install
+ dh_installdocs
+ dh_installchangelogs
+ dh_installexamples
+ dh_installman
+ dh_installcatalogs
+ dh_installcron
+ dh_installdebconf
+ dh_installemacsen
+ dh_installifupdown
+ dh_installinfo
+ dh_pysupport
+ dh_installinit
+ dh_installmenu
+ dh_installmime
+ dh_installmodules
+ dh_installlogcheck
+ dh_installlogrotate
+ dh_installpam
+ dh_installppp
+ dh_installudev
+ dh_installwm
+ dh_installxfonts
+ dh_bugfiles
+ dh_lintian
+ dh_gconf
+ dh_icons
+ dh_perl
+ dh_usrlocal
+ dh_link
+ dh_compress
+ dh_fixperms
+ dh_strip
+ dh_makeshlibs
+ dh_shlibdeps
+ dh_installdeb
+ dh_gencontrol
+dpkg-gencontrol: warning: unknown substitution variable ${shlibs:Depends}
+ dh_md5sums
+ dh_builddeb
+dpkg-deb: building package `chef-integration-test2' in `../chef-integration-test2_1.0-1_amd64.deb'.
+ dpkg-genchanges >../chef-integration-test2_1.0-1_amd64.changes
+dpkg-genchanges: including full source code in upload
+dpkg-buildpackage: full upload (original source is included)
+Now running lintian...
+warning: lintian's authors do not recommend running it with root privileges!
+E: chef-integration-test2 source: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+W: chef-integration-test2 source: changelog-should-mention-nmu
+W: chef-integration-test2 source: source-nmu-has-incorrect-version-number 1.0-1
+W: chef-integration-test2: new-package-should-close-itp-bug
+W: chef-integration-test2: wrong-bug-number-in-closes l3:#CHEF
+E: chef-integration-test2: file-in-usr-marked-as-conffile /usr/share/doc/chef-integration-test2/copyright
+E: chef-integration-test2: maintainer-address-malformed Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+W: chef-integration-test2: empty-binary-package
+Finished running lintian.
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes
new file mode 100644
index 0000000000..be3cd45343
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes
@@ -0,0 +1,31 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 09:53:45 -0600
+Source: chef-integration-test2
+Binary: chef-integration-test2
+Architecture: source amd64
+Version: 1.0-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Changed-By: Joshua Timberman <joshua@opscode.com>
+Description:
+ chef-integration-test2 - Chef integration tests for APT in Cucumber
+Changes:
+ chef-integration-test2 (1.0-1) unstable; urgency=low
+ .
+ * Initial release (Closes: #CHEF-1718)
+Checksums-Sha1:
+ 7e065fdf71f4d798312b318a29cec43b7bc1c8e1 885 chef-integration-test2_1.0-1.dsc
+ 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz
+ 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz
+ f3f89c051bce36d40ef1be12d231c44b2d5be05b 1694 chef-integration-test2_1.0-1_amd64.deb
+Checksums-Sha256:
+ 80d314349e1d978f242d05482ca81c9361739047daa4adcecc9e5e622fdc6fb4 885 chef-integration-test2_1.0-1.dsc
+ 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz
+ 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz
+ 19a767db0a947a350fb1c8492699e8a808fbe1838d4a582001106cfbe520ad8f 1694 chef-integration-test2_1.0-1_amd64.deb
+Files:
+ 9f927b32d95b5406c696b5b0b23177e8 885 ruby extra chef-integration-test2_1.0-1.dsc
+ f1f7d7bbe63ad631d25d707f564a8d33 248 ruby extra chef-integration-test2_1.0.orig.tar.gz
+ 4fab5c1cd9a7b47c4f319af776f48a1d 1369 ruby extra chef-integration-test2_1.0-1.debian.tar.gz
+ 9914e6152e278b6828bcade3b3f5580c 1694 ruby extra chef-integration-test2_1.0-1_amd64.deb
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb
new file mode 100644
index 0000000000..7b9b69d378
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz b/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz
new file mode 100644
index 0000000000..18f7aa17d6
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes
new file mode 100644
index 0000000000..4746b834e5
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.changes
@@ -0,0 +1,22 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 09:53:45 -0600
+Source: chef-integration-test
+Binary: chef-integration-test
+Architecture: amd64
+Version: 1.0-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Changed-By: Joshua Timberman <joshua@opscode.com>
+Description:
+ chef-integration-test - Chef integration tests for APT in Cucumber
+Changes:
+ chef-integration-test (1.0-1) unstable; urgency=low
+ .
+ * Initial release (Closes: #CHEF-1718)
+Checksums-Sha1:
+ b44685ff59626bc94c67e60665f06c4643fe9767 1680 chef-integration-test_1.0-1_amd64.deb
+Checksums-Sha256:
+ da176f4405fa21fd7207d4785680c6996d395a1ca132f2d5565a61c5479b1116 1680 chef-integration-test_1.0-1_amd64.deb
+Files:
+ 713722480408ecc8e7220aea52bdd76e 1680 ruby extra chef-integration-test_1.0-1_amd64.deb
diff --git a/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb
new file mode 100644
index 0000000000..458dd026ff
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.0-1_amd64.deb
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz b/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz
new file mode 100644
index 0000000000..3de028d486
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.0.orig.tar.gz
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes
new file mode 100644
index 0000000000..f014de813b
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.changes
@@ -0,0 +1,22 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 10:09:34 -0600
+Source: chef-integration-test
+Binary: chef-integration-test
+Architecture: amd64
+Version: 1.1-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Changed-By: Joshua Timberman <joshua@opscode.com>
+Description:
+ chef-integration-test - Chef integration tests for APT in Cucumber
+Changes:
+ chef-integration-test (1.1-1) unstable; urgency=low
+ .
+ * New upstream release (1.1)
+Checksums-Sha1:
+ 43c5653a9a5b9419849173a4ec3a9855cf0327a3 1722 chef-integration-test_1.1-1_amd64.deb
+Checksums-Sha256:
+ 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 1722 chef-integration-test_1.1-1_amd64.deb
+Files:
+ 4b05bace483dbca54efc21f97ee47e1d 1722 ruby extra chef-integration-test_1.1-1_amd64.deb
diff --git a/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb
new file mode 100644
index 0000000000..c4fac10dc1
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.1-1_amd64.deb
Binary files differ
diff --git a/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz b/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz
new file mode 100644
index 0000000000..5fda119eae
--- /dev/null
+++ b/knife/spec/data/apt/chef-integration-test_1.1.orig.tar.gz
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/conf/distributions b/knife/spec/data/apt/var/www/apt/conf/distributions
new file mode 100644
index 0000000000..285c1a88de
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/conf/distributions
@@ -0,0 +1,7 @@
+Origin: localhost
+Label: apt repository
+Codename: sid
+Architectures: amd64
+Components: main
+Description: Apt repository
+Pull: sid
diff --git a/knife/spec/data/apt/var/www/apt/conf/incoming b/knife/spec/data/apt/var/www/apt/conf/incoming
new file mode 100644
index 0000000000..d44e59c51b
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/conf/incoming
@@ -0,0 +1,4 @@
+Name: default
+IncomingDir: /tmp/incoming
+TempDir: /tmp
+Allow: sid unstable>sid
diff --git a/knife/spec/data/apt/var/www/apt/conf/pulls b/knife/spec/data/apt/var/www/apt/conf/pulls
new file mode 100644
index 0000000000..0fc3358279
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/conf/pulls
@@ -0,0 +1,3 @@
+Name: sid
+From: sid
+Components: main
diff --git a/knife/spec/data/apt/var/www/apt/db/checksums.db b/knife/spec/data/apt/var/www/apt/db/checksums.db
new file mode 100644
index 0000000000..e36ade2079
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/checksums.db
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/db/contents.cache.db b/knife/spec/data/apt/var/www/apt/db/contents.cache.db
new file mode 100644
index 0000000000..04a0c4aed5
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/contents.cache.db
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/db/packages.db b/knife/spec/data/apt/var/www/apt/db/packages.db
new file mode 100644
index 0000000000..43c70b0de3
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/packages.db
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/db/references.db b/knife/spec/data/apt/var/www/apt/db/references.db
new file mode 100644
index 0000000000..47c99fe152
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/references.db
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/db/release.caches.db b/knife/spec/data/apt/var/www/apt/db/release.caches.db
new file mode 100644
index 0000000000..0e251c5496
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/release.caches.db
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/db/version b/knife/spec/data/apt/var/www/apt/db/version
new file mode 100644
index 0000000000..a6908690d9
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/db/version
@@ -0,0 +1,4 @@
+4.2.0
+3.3.0
+bdb4.8.30
+bdb4.8.0
diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/Release b/knife/spec/data/apt/var/www/apt/dists/sid/Release
new file mode 100644
index 0000000000..44ccd079bf
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/dists/sid/Release
@@ -0,0 +1,19 @@
+Origin: localhost
+Label: apt repository
+Codename: sid
+Date: Thu, 30 Sep 2010 16:33:01 UTC
+Architectures: amd64
+Components: main
+Description: Apt repository
+MD5Sum:
+ 92ed2cc14e37e9ab23466b27857d29ac 596 main/binary-amd64/Packages
+ c7726773341137b71cc971d44ddec4f5 394 main/binary-amd64/Packages.gz
+ 46cd71c965ce0813c94ef78c836cc7d3 104 main/binary-amd64/Release
+SHA1:
+ cde25071c5fcee59cee8dcd773ca419dcb40d946 596 main/binary-amd64/Packages
+ ce04daff75d4b27371d691d645282b198045544a 394 main/binary-amd64/Packages.gz
+ 91ca9531e3afa7a540cabdc6030c6f75d315fec7 104 main/binary-amd64/Release
+SHA256:
+ af601ce143f33405425746462973adc0fda3aceb381d1c739851b95ee0814ca3 596 main/binary-amd64/Packages
+ 15e98119705a08018d4583caabc91d36ba12e6f1c8af0f799a3ec8ca5bfaa80d 394 main/binary-amd64/Packages.gz
+ 098c599ac5b0a98785336afb2bc9c47002570ffa07dd62321c6f70b9fdb74325 104 main/binary-amd64/Release
diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages
new file mode 100644
index 0000000000..209c23cd42
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages
@@ -0,0 +1,16 @@
+Package: chef-integration-test
+Version: 1.1-1
+Architecture: amd64
+Maintainer: Joshua Timberman <Joshua Timberman <joshua@opscode.com>>
+Installed-Size: 32
+Homepage: http://tickets.opscode.com
+Priority: extra
+Section: ruby
+Filename: pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
+Size: 1722
+SHA256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1
+SHA1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3
+MD5sum: 4b05bace483dbca54efc21f97ee47e1d
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
+
diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz
new file mode 100644
index 0000000000..8a2c1e8980
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release
new file mode 100644
index 0000000000..e913d702a1
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release
@@ -0,0 +1,5 @@
+Component: main
+Origin: localhost
+Label: apt repository
+Architecture: amd64
+Description: Apt repository
diff --git a/spec/data/trusted_certs_empty/.gitkeep b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages
index e69de29bb2..e69de29bb2 100644
--- a/spec/data/trusted_certs_empty/.gitkeep
+++ b/knife/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages
diff --git a/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb
new file mode 100644
index 0000000000..458dd026ff
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb
Binary files differ
diff --git a/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
new file mode 100644
index 0000000000..c4fac10dc1
--- /dev/null
+++ b/knife/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
Binary files differ
diff --git a/knife/spec/data/bad-config.rb b/knife/spec/data/bad-config.rb
new file mode 100644
index 0000000000..5477a69366
--- /dev/null
+++ b/knife/spec/data/bad-config.rb
@@ -0,0 +1 @@
+monkey_soup("tastes nice") \ No newline at end of file
diff --git a/knife/spec/data/bootstrap/encrypted_data_bag_secret b/knife/spec/data/bootstrap/encrypted_data_bag_secret
new file mode 100644
index 0000000000..ac88558a1a
--- /dev/null
+++ b/knife/spec/data/bootstrap/encrypted_data_bag_secret
@@ -0,0 +1 @@
+supersekret_from_file
diff --git a/knife/spec/data/bootstrap/no_proxy.erb b/knife/spec/data/bootstrap/no_proxy.erb
new file mode 100644
index 0000000000..6945704386
--- /dev/null
+++ b/knife/spec/data/bootstrap/no_proxy.erb
@@ -0,0 +1,2 @@
+bash -c '
+<%= config_content %>'
diff --git a/knife/spec/data/bootstrap/secret.erb b/knife/spec/data/bootstrap/secret.erb
new file mode 100644
index 0000000000..e0ad41576d
--- /dev/null
+++ b/knife/spec/data/bootstrap/secret.erb
@@ -0,0 +1,9 @@
+bash -c '
+<% if encrypted_data_bag_secret -%>
+awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP'
+<%= encrypted_data_bag_secret %>
+EOP
+chmod 0600 /etc/chef/encrypted_data_bag_secret
+<% end -%>
+
+<%= config_content %>'
diff --git a/knife/spec/data/bootstrap/test-hints.erb b/knife/spec/data/bootstrap/test-hints.erb
new file mode 100644
index 0000000000..7693fdc7c9
--- /dev/null
+++ b/knife/spec/data/bootstrap/test-hints.erb
@@ -0,0 +1,12 @@
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+<% end -%>
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+(
+cat <<'EOP'
+<%= Chef::JSONCompat.to_json(hash) %>
+EOP
+) > /etc/chef/ohai/hints/<%= name %>.json
+<% end -%>
diff --git a/knife/spec/data/bootstrap/test.erb b/knife/spec/data/bootstrap/test.erb
new file mode 100644
index 0000000000..3a383b47d0
--- /dev/null
+++ b/knife/spec/data/bootstrap/test.erb
@@ -0,0 +1 @@
+<%= Chef::JSONCompat.to_json(first_boot) %>
diff --git a/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt b/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/cookbook2/files/test.txt
diff --git a/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb b/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc b/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc
new file mode 100644
index 0000000000..460d96b40d
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/README.rdoc
@@ -0,0 +1,3 @@
+= THIS RECIPE
+* is for testing CookbookLoader/CookbookVersion
+* has at least one of every kind of file that cookbooks can have \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb b/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb
new file mode 100644
index 0000000000..47774459c2
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/attributes/default.rb
@@ -0,0 +1 @@
+#one_of_each default attributes
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb b/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb
new file mode 100644
index 0000000000..3912b37365
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb
@@ -0,0 +1 @@
+# IRL the runit_service is a definition to set up runit services \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz b/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz
new file mode 100644
index 0000000000..3e7496601e
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz
@@ -0,0 +1 @@
+# not really a giant blob # \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb b/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb
new file mode 100644
index 0000000000..fea05ba67b
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb
@@ -0,0 +1 @@
+# 0wnage \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb b/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb
new file mode 100644
index 0000000000..977ad19192
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb
@@ -0,0 +1 @@
+# a LWP \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb b/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb
new file mode 100644
index 0000000000..48eacf848b
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/recipes/default.rb
@@ -0,0 +1 @@
+# the default recipe \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb b/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb
new file mode 100644
index 0000000000..987114f4ca
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb
@@ -0,0 +1 @@
+# a LWR # \ No newline at end of file
diff --git a/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb b/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb
diff --git a/knife/spec/data/checksum/random.txt b/knife/spec/data/checksum/random.txt
new file mode 100644
index 0000000000..9570fff833
--- /dev/null
+++ b/knife/spec/data/checksum/random.txt
@@ -0,0 +1 @@
+e4010abcac515ef3b78e92ee1f848a0d3bc3a526fcb826e7b4d39a6d516aa0487085c9b1be35e8d909617b250dca36dd4a55f01b7cdd310826bfd748cb27e0e43dd52b22968383c8086b06ee2d16e13574f98c058ce2bc3475b92ecf9c16e504022d60b132643986a8e7908d067526e20b4bafe1eb75349f27a4d3de02b077e76a2f59b73c14413f11e7208ae0bf6a408d51a97d490530e23476960ab8780ad86349947d82f1c9e57c85f86d71f80a6709b58be5f993a6a6df80c5a0857627d4a01e71484f6a6e983985089c00fe538e947230813c3a3e19baf6dae6db7082d07392a239ec1be385646356db3e3d76571488a6c72f0b96997f6191beea9846fc99f82a828f05af95cfc234cf681002f830915b1f3d35b2178b54a861c05d2694c5f6cfeb613a4a3670d849180461cdedf2c3cbb022608d8b19c86179d2d6da6b9acefccfc34b59663ef1282fec262bef79b2fbdd9b6669c90d6817b3762164dc309616469b33b83b1ded3420ae9177bc8f456d83939ff3c91b0a3683f3157401ceadf679c9f876da2aa413e081ee4c41d4b04f49e0c254d0082fd9bf2cb8eb8b966285be2cdcaab0ab70ea970737244b6683283598c30bdc206a05df72048b342eb40c2cd750c815d5fa944167b103ec40d60a99c49941a9e76d874149524f35ca294d081cf221757df77e027640556d983978be6b4b51aff26cd74a2f300d71 \ No newline at end of file
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0 b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0
new file mode 100644
index 0000000000..81836588d5
--- /dev/null
+++ b/knife/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/knife/spec/data/client.d_00/00-foo.rb b/knife/spec/data/client.d_00/00-foo.rb
new file mode 100644
index 0000000000..44a763aca1
--- /dev/null
+++ b/knife/spec/data/client.d_00/00-foo.rb
@@ -0,0 +1,2 @@
+# 00-foo.rb
+# d6f9b976-289c-4149-baf7-81e6ffecf228
diff --git a/knife/spec/data/client.d_00/01-bar.rb b/knife/spec/data/client.d_00/01-bar.rb
new file mode 100644
index 0000000000..73f91386bc
--- /dev/null
+++ b/knife/spec/data/client.d_00/01-bar.rb
@@ -0,0 +1 @@
+# 01-bar.rb
diff --git a/knife/spec/data/client.d_00/02-strings.rb b/knife/spec/data/client.d_00/02-strings.rb
new file mode 100644
index 0000000000..7d9a49268c
--- /dev/null
+++ b/knife/spec/data/client.d_00/02-strings.rb
@@ -0,0 +1,2 @@
+# 02-strings.rb
+something '/foo/bar'
diff --git a/knife/spec/data/client.d_00/bar b/knife/spec/data/client.d_00/bar
new file mode 100644
index 0000000000..72dca4d5e4
--- /dev/null
+++ b/knife/spec/data/client.d_00/bar
@@ -0,0 +1 @@
+1 / 0
diff --git a/knife/spec/data/client.d_01/foo/bar.rb b/knife/spec/data/client.d_01/foo/bar.rb
new file mode 100644
index 0000000000..72dca4d5e4
--- /dev/null
+++ b/knife/spec/data/client.d_01/foo/bar.rb
@@ -0,0 +1 @@
+1 / 0
diff --git a/knife/spec/data/client.d_02/foo.rb/foo.txt b/knife/spec/data/client.d_02/foo.rb/foo.txt
new file mode 100644
index 0000000000..d724c93bef
--- /dev/null
+++ b/knife/spec/data/client.d_02/foo.rb/foo.txt
@@ -0,0 +1 @@
+# foo.txt
diff --git a/knife/spec/data/config.rb b/knife/spec/data/config.rb
new file mode 100644
index 0000000000..0b3340ce57
--- /dev/null
+++ b/knife/spec/data/config.rb
@@ -0,0 +1,6 @@
+#
+# Sample Chef Config File
+#
+
+cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook"
+
diff --git a/knife/spec/data/cookbooks/angrybash/metadata.rb b/knife/spec/data/cookbooks/angrybash/metadata.rb
new file mode 100644
index 0000000000..720b8085be
--- /dev/null
+++ b/knife/spec/data/cookbooks/angrybash/metadata.rb
@@ -0,0 +1,2 @@
+name "angrybash"
+version "1.0.0"
diff --git a/knife/spec/data/cookbooks/angrybash/recipes/default.rb b/knife/spec/data/cookbooks/angrybash/recipes/default.rb
new file mode 100644
index 0000000000..458a29103e
--- /dev/null
+++ b/knife/spec/data/cookbooks/angrybash/recipes/default.rb
@@ -0,0 +1,8 @@
+bash "go off the rails" do
+ code <<-END
+ for i in localhost 127.0.0.1 #{Socket.gethostname()}
+ do
+ echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1
+ done
+ END
+end
diff --git a/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl b/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl
new file mode 100644
index 0000000000..6cce6229c4
--- /dev/null
+++ b/knife/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl
@@ -0,0 +1,2 @@
+# apache2_module_conf_generate.pl
+# this is just here for show.
diff --git a/knife/spec/data/cookbooks/apache2/metadata.json b/knife/spec/data/cookbooks/apache2/metadata.json
new file mode 100644
index 0000000000..18f5e50bb3
--- /dev/null
+++ b/knife/spec/data/cookbooks/apache2/metadata.json
@@ -0,0 +1,33 @@
+{
+ "name": "apache2",
+ "description": "",
+ "long_description": "",
+ "maintainer": "",
+ "maintainer_email": "",
+ "license": "All rights reserved",
+ "platforms": {
+
+ },
+ "dependencies": {
+
+ },
+ "providing": {
+
+ },
+ "recipes": {
+
+ },
+ "version": "0.0.1",
+ "source_url": "",
+ "issues_url": "",
+ "privacy": false,
+ "chef_versions": [
+
+ ],
+ "ohai_versions": [
+
+ ],
+ "gems": [
+
+ ]
+}
diff --git a/knife/spec/data/cookbooks/apache2/metadata.rb b/knife/spec/data/cookbooks/apache2/metadata.rb
new file mode 100644
index 0000000000..1273d20d79
--- /dev/null
+++ b/knife/spec/data/cookbooks/apache2/metadata.rb
@@ -0,0 +1,2 @@
+name "apache2"
+version "0.0.1"
diff --git a/knife/spec/data/cookbooks/apache2/recipes/default.rb b/knife/spec/data/cookbooks/apache2/recipes/default.rb
new file mode 100644
index 0000000000..c2fa53be32
--- /dev/null
+++ b/knife/spec/data/cookbooks/apache2/recipes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing ot see here
+# \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/borken/metadata.rb b/knife/spec/data/cookbooks/borken/metadata.rb
new file mode 100644
index 0000000000..58700b2d8e
--- /dev/null
+++ b/knife/spec/data/cookbooks/borken/metadata.rb
@@ -0,0 +1,2 @@
+name "borken"
+version "1.0.0"
diff --git a/knife/spec/data/cookbooks/borken/recipes/default.rb b/knife/spec/data/cookbooks/borken/recipes/default.rb
new file mode 100644
index 0000000000..caf40b3974
--- /dev/null
+++ b/knife/spec/data/cookbooks/borken/recipes/default.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/borken/templates/default/borken.erb b/knife/spec/data/cookbooks/borken/templates/default/borken.erb
new file mode 100644
index 0000000000..cbb32c1cd7
--- /dev/null
+++ b/knife/spec/data/cookbooks/borken/templates/default/borken.erb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+<%= (*&)(*^^^^*******++_+_--- }}}}]]]end)%> \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/chefignore b/knife/spec/data/cookbooks/chefignore
new file mode 100644
index 0000000000..84b4f1e99f
--- /dev/null
+++ b/knife/spec/data/cookbooks/chefignore
@@ -0,0 +1,8 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme.rb
+ # comments can be indented
+ignored
diff --git a/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb b/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb
new file mode 100644
index 0000000000..8063e32a95
--- /dev/null
+++ b/knife/spec/data/cookbooks/ignorken/files/default/not_me.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
diff --git a/knife/spec/data/cookbooks/ignorken/metadata.rb b/knife/spec/data/cookbooks/ignorken/metadata.rb
new file mode 100644
index 0000000000..f91d92de32
--- /dev/null
+++ b/knife/spec/data/cookbooks/ignorken/metadata.rb
@@ -0,0 +1,2 @@
+name "ignorken"
+version "1.0.0"
diff --git a/knife/spec/data/cookbooks/ignorken/recipes/default.rb b/knife/spec/data/cookbooks/ignorken/recipes/default.rb
new file mode 100644
index 0000000000..0f3b3a5d8d
--- /dev/null
+++ b/knife/spec/data/cookbooks/ignorken/recipes/default.rb
@@ -0,0 +1 @@
+# This is fine! \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb b/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb
new file mode 100644
index 0000000000..caf40b3974
--- /dev/null
+++ b/knife/spec/data/cookbooks/ignorken/recipes/ignoreme.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb b/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb
new file mode 100644
index 0000000000..8063e32a95
--- /dev/null
+++ b/knife/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
diff --git a/knife/spec/data/cookbooks/irssi/files/default/irssi.response b/knife/spec/data/cookbooks/irssi/files/default/irssi.response
new file mode 100644
index 0000000000..6b67a12758
--- /dev/null
+++ b/knife/spec/data/cookbooks/irssi/files/default/irssi.response
@@ -0,0 +1,2 @@
+# Hi, I'm pretending to be the preseed file for installing the irssi on debian
+# or Ubuntu
diff --git a/knife/spec/data/cookbooks/java/files/default/java.response b/knife/spec/data/cookbooks/java/files/default/java.response
new file mode 100644
index 0000000000..eb4aa3c124
--- /dev/null
+++ b/knife/spec/data/cookbooks/java/files/default/java.response
@@ -0,0 +1,2 @@
+# Hi, I'm pretending to be the preseed file for installing the Sun JDK on debian
+# or Ubuntu \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/java/metadata.json b/knife/spec/data/cookbooks/java/metadata.json
new file mode 100644
index 0000000000..9d46842f3c
--- /dev/null
+++ b/knife/spec/data/cookbooks/java/metadata.json
@@ -0,0 +1,33 @@
+{
+ "name": "java",
+ "description": "",
+ "long_description": "",
+ "maintainer": "",
+ "maintainer_email": "",
+ "license": "All rights reserved",
+ "platforms": {
+
+ },
+ "dependencies": {
+
+ },
+ "providing": {
+
+ },
+ "recipes": {
+
+ },
+ "version": "0.0.1",
+ "source_url": "",
+ "issues_url": "",
+ "privacy": false,
+ "chef_versions": [
+
+ ],
+ "ohai_versions": [
+
+ ],
+ "gems": [
+
+ ]
+}
diff --git a/knife/spec/data/cookbooks/java/metadata.rb b/knife/spec/data/cookbooks/java/metadata.rb
new file mode 100644
index 0000000000..7c5585304c
--- /dev/null
+++ b/knife/spec/data/cookbooks/java/metadata.rb
@@ -0,0 +1,2 @@
+name "java"
+version "0.0.1"
diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md b/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md
new file mode 100644
index 0000000000..a61dc9a390
--- /dev/null
+++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/README.md
@@ -0,0 +1,4 @@
+# name-mismatch
+
+TODO: Enter the cookbook description here.
+
diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb b/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb
new file mode 100644
index 0000000000..81775bdcc8
--- /dev/null
+++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb
@@ -0,0 +1,8 @@
+name 'name-mismatch'
+maintainer ''
+maintainer_email ''
+license ''
+description 'Installs/Configures name-mismatch'
+long_description 'Installs/Configures name-mismatch'
+version '0.1.0'
+
diff --git a/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb b/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb
new file mode 100644
index 0000000000..0bb34e78e1
--- /dev/null
+++ b/knife/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb
@@ -0,0 +1,8 @@
+#
+# Cookbook Name:: name-mismatch
+# Recipe:: default
+#
+# Copyright 2014-2016,
+#
+#
+#
diff --git a/knife/spec/data/cookbooks/openldap/.root_dotfile b/knife/spec/data/cookbooks/openldap/.root_dotfile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/.root_dotfile
diff --git a/knife/spec/data/cookbooks/openldap/attributes/default.rb b/knife/spec/data/cookbooks/openldap/attributes/default.rb
new file mode 100644
index 0000000000..d0756f17e3
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/attributes/default.rb
@@ -0,0 +1,16 @@
+chef_env ||= nil
+case chef_env
+when "prod"
+ default[:ldap_server] = "ops1prod"
+ default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+ default[:ldap_replication_password] = "yes"
+when "corp"
+ default[:ldap_server] = "ops1prod"
+ default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+ default[:ldap_replication_password] = "yougotit"
+else
+
+ default[:ldap_server] = "ops1prod"
+ default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+ default[:ldap_replication_password] = "forsure"
+end
diff --git a/knife/spec/data/cookbooks/openldap/attributes/smokey.rb b/knife/spec/data/cookbooks/openldap/attributes/smokey.rb
new file mode 100644
index 0000000000..4489c6a7ac
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/attributes/smokey.rb
@@ -0,0 +1 @@
+default[:smokey] = "robinson"
diff --git a/knife/spec/data/cookbooks/openldap/definitions/client.rb b/knife/spec/data/cookbooks/openldap/definitions/client.rb
new file mode 100644
index 0000000000..ac81831d11
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/definitions/client.rb
@@ -0,0 +1,5 @@
+define :openldap_client, :mothra => "a big monster" do
+ cat "#{params[:name]}" do
+ pretty_kitty true
+ end
+end
diff --git a/knife/spec/data/cookbooks/openldap/definitions/server.rb b/knife/spec/data/cookbooks/openldap/definitions/server.rb
new file mode 100644
index 0000000000..2df437aa84
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/definitions/server.rb
@@ -0,0 +1,5 @@
+define :openldap_server, :mothra => "a big monster" do
+ cat "#{params[:name]}" do
+ pretty_kitty true
+ end
+end
diff --git a/knife/spec/data/cookbooks/openldap/files/default/.dotfile b/knife/spec/data/cookbooks/openldap/files/default/.dotfile
new file mode 100644
index 0000000000..35ae928f91
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/.dotfile
@@ -0,0 +1 @@
+I am here to test .dotfiles work in file directories.
diff --git a/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa b/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa
new file mode 100644
index 0000000000..20a3ea410a
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa
@@ -0,0 +1 @@
+FAKE KEY \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir b/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir
new file mode 100644
index 0000000000..f44a956c15
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir
@@ -0,0 +1 @@
+this is a dotfile in a dotdir
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb b/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb
new file mode 100644
index 0000000000..fc68ce83a1
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb
@@ -0,0 +1,2 @@
+# This file is not a chef template despite being and erb.
+# It should not be included in a cookbook syntax check
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt
new file mode 100644
index 0000000000..7632730912
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remote_dir_file1.txt \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt
new file mode 100644
index 0000000000..fab0433922
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remote_dir_file2.txt \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile
new file mode 100644
index 0000000000..9a2b2a4b5f
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile
@@ -0,0 +1 @@
+this is a file with a name beginning with a . dot
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt
new file mode 100644
index 0000000000..611294cb81
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remotesubdir/remote_dir_file1.txt \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt
new file mode 100644
index 0000000000..e0396542a4
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remotesubdir/remote_dir_file2.txt \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt b/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
new file mode 100644
index 0000000000..bc47369aad
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
diff --git a/knife/spec/data/cookbooks/openldap/libraries/openldap.rb b/knife/spec/data/cookbooks/openldap/libraries/openldap.rb
new file mode 100644
index 0000000000..0b9389c688
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/libraries/openldap.rb
@@ -0,0 +1,4 @@
+require_relative './openldap/version'
+
+class OpenLDAP
+end
diff --git a/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb b/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb
new file mode 100644
index 0000000000..4bff12b01c
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/libraries/openldap/version.rb
@@ -0,0 +1,3 @@
+class OpenLDAP
+ VERSION = '8.9.10'
+end
diff --git a/knife/spec/data/cookbooks/openldap/metadata.rb b/knife/spec/data/cookbooks/openldap/metadata.rb
new file mode 100644
index 0000000000..fc132946f2
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/metadata.rb
@@ -0,0 +1,8 @@
+name "openldap"
+maintainer "Chef Software, Inc."
+maintainer_email "cookbooks@chef.io"
+license "Apache 2.0"
+description "Installs and configures all aspects of openldap using Debian style symlinks with helper definitions"
+long_description "The long description for the openldap cookbook from metadata.rb"
+version "8.9.10"
+recipe "openldap", "Main Open LDAP configuration"
diff --git a/knife/spec/data/cookbooks/openldap/recipes/default.rb b/knife/spec/data/cookbooks/openldap/recipes/default.rb
new file mode 100644
index 0000000000..ba5c9d1507
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/recipes/default.rb
@@ -0,0 +1,4 @@
+
+cat "blanket" do
+ pretty_kitty true
+end
diff --git a/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb b/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000000..b450eca7cd
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+ pretty_kitty false
+end
diff --git a/knife/spec/data/cookbooks/openldap/recipes/one.rb b/knife/spec/data/cookbooks/openldap/recipes/one.rb
new file mode 100644
index 0000000000..d2d3cfd409
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/recipes/one.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-default"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/knife/spec/data/cookbooks/openldap/recipes/return.rb b/knife/spec/data/cookbooks/openldap/recipes/return.rb
new file mode 100644
index 0000000000..79bfb5e441
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/recipes/return.rb
@@ -0,0 +1,2 @@
+# CHEF-5199 regression test.
+return nil
diff --git a/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb b/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/spec/spec_helper.rb
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
new file mode 100644
index 0000000000..727db12387
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb b/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb
new file mode 100644
index 0000000000..92e6fe0427
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/helper_test.erb
@@ -0,0 +1 @@
+<%= helper_method %>
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb b/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb
new file mode 100644
index 0000000000..b973a5287c
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/helpers.erb
@@ -0,0 +1,14 @@
+<%= @cookbook_name %>
+<%= @recipe_name %>
+<%= @recipe_line_string %>
+<%= @recipe_path %>
+<%= @recipe_line %>
+<%= @template_name %>
+<%= @template_path %>
+<%= cookbook_name %>
+<%= recipe_name %>
+<%= recipe_line_string %>
+<%= recipe_path %>
+<%= recipe_line %>
+<%= template_name %>
+<%= template_path %>
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb b/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
new file mode 100644
index 0000000000..f94b6b5979
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
@@ -0,0 +1 @@
+<%= render("helper_test.erb").strip %>
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb b/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb
new file mode 100644
index 0000000000..2d356ec21d
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb
@@ -0,0 +1 @@
+before <%= render 'nested_partial.erb', :variables => { :hello => @test } %> after
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb b/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb
new file mode 100644
index 0000000000..415646ca31
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/nested_partial.erb
@@ -0,0 +1 @@
+{<%= @hello %>}
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
new file mode 100644
index 0000000000..e83c03b01a
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb
new file mode 100644
index 0000000000..5ebee33806
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_nested_variable_stuff.erb
@@ -0,0 +1 @@
+super secret is <%= @secret.first["key"] -%>
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
new file mode 100644
index 0000000000..af82f1d96c
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
@@ -0,0 +1 @@
+slappiness is <%= node[:slappiness] -%> \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb b/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
new file mode 100644
index 0000000000..e0041c9f99
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
@@ -0,0 +1 @@
+super secret is <%= @secret -%>
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb b/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb
new file mode 100644
index 0000000000..fa25999b6a
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/knife/spec/data/cookbooks/openldap/templates/default/test.erb b/knife/spec/data/cookbooks/openldap/templates/default/test.erb
new file mode 100644
index 0000000000..f39fa7da89
--- /dev/null
+++ b/knife/spec/data/cookbooks/openldap/templates/default/test.erb
@@ -0,0 +1 @@
+We could be diving for pearls!
diff --git a/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed b/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed
new file mode 100644
index 0000000000..164da3495d
--- /dev/null
+++ b/knife/spec/data/cookbooks/preseed/files/default/preseed-file.seed
@@ -0,0 +1 @@
+chef-integration-test chef-integration-test/sample-var string "hello world"
diff --git a/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed b/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed
new file mode 100644
index 0000000000..011bc40f1b
--- /dev/null
+++ b/knife/spec/data/cookbooks/preseed/files/default/preseed-template.seed
@@ -0,0 +1,4 @@
+# This file should never be executed by the preseeding tests
+# This is here to verify that templates are preferred over cookbook_files when
+# preseeding packages.
+chef-integration-test chef-integration-test/sample-var string "WRONG-cookbook file used instead of template!"
diff --git a/knife/spec/data/cookbooks/preseed/metadata.rb b/knife/spec/data/cookbooks/preseed/metadata.rb
new file mode 100644
index 0000000000..a48deec08b
--- /dev/null
+++ b/knife/spec/data/cookbooks/preseed/metadata.rb
@@ -0,0 +1,2 @@
+name "preseed"
+version "1.0.0"
diff --git a/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed
new file mode 100644
index 0000000000..0df0015f05
--- /dev/null
+++ b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed
@@ -0,0 +1 @@
+chef-integration-test chef-integration-test/sample-var string "<%= @template_variable -%>"
diff --git a/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed
new file mode 100644
index 0000000000..6229ac83f5
--- /dev/null
+++ b/knife/spec/data/cookbooks/preseed/templates/default/preseed-template.seed
@@ -0,0 +1 @@
+chef-integration-test chef-integration-test/sample-var string "<%= node[:preseed_value] -%>"
diff --git a/knife/spec/data/cookbooks/starter/chefignore b/knife/spec/data/cookbooks/starter/chefignore
new file mode 100644
index 0000000000..b9d6c768c2
--- /dev/null
+++ b/knife/spec/data/cookbooks/starter/chefignore
@@ -0,0 +1,8 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/default.rb
+ # comments can be indented
+ignored
diff --git a/knife/spec/data/cookbooks/starter/files/sample.txt b/knife/spec/data/cookbooks/starter/files/sample.txt
new file mode 100644
index 0000000000..e635a0f018
--- /dev/null
+++ b/knife/spec/data/cookbooks/starter/files/sample.txt
@@ -0,0 +1 @@
+This is a Chef cookbook file. It is used to copy content verbatim on to a server. \ No newline at end of file
diff --git a/knife/spec/data/cookbooks/starter/metadata.rb b/knife/spec/data/cookbooks/starter/metadata.rb
new file mode 100644
index 0000000000..fbd288e9c4
--- /dev/null
+++ b/knife/spec/data/cookbooks/starter/metadata.rb
@@ -0,0 +1,2 @@
+name "starter"
+version "1.0.0"
diff --git a/knife/spec/data/cookbooks/starter/recipes/default.rb b/knife/spec/data/cookbooks/starter/recipes/default.rb
new file mode 100644
index 0000000000..4b5f712879
--- /dev/null
+++ b/knife/spec/data/cookbooks/starter/recipes/default.rb
@@ -0,0 +1,4 @@
+# This is a Chef recipe file. It can be used to specify resources which will
+# apply configuration to a server.
+
+# For more information, see the documentation: https://docs.chef.io/recipes/
diff --git a/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb b/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb
new file mode 100644
index 0000000000..3620249d5f
--- /dev/null
+++ b/knife/spec/data/cookbooks/supports-platform-constraints/metadata.rb
@@ -0,0 +1,5 @@
+name 'supports-platform-constraints'
+version '0.1.0'
+
+supports 'centos', '>= 6'
+supports 'freebsd', '> 10.1-fake-p12'
diff --git a/knife/spec/data/cookbooks/wget/files/default/wget.response b/knife/spec/data/cookbooks/wget/files/default/wget.response
new file mode 100644
index 0000000000..b5f22f4d10
--- /dev/null
+++ b/knife/spec/data/cookbooks/wget/files/default/wget.response
@@ -0,0 +1,2 @@
+# Hi, I'm pretending to be the preseed file for installing the wget on debian
+# or Ubuntu
diff --git a/knife/spec/data/definitions/test.rb b/knife/spec/data/definitions/test.rb
new file mode 100644
index 0000000000..b0d0effc27
--- /dev/null
+++ b/knife/spec/data/definitions/test.rb
@@ -0,0 +1,5 @@
+define :rico_suave, :rich => "smooth" do
+ zen_master "test" do
+ something "#{params[:rich]}"
+ end
+end \ No newline at end of file
diff --git a/knife/spec/data/dsc_lcm.pfx b/knife/spec/data/dsc_lcm.pfx
new file mode 100644
index 0000000000..3912ed3753
--- /dev/null
+++ b/knife/spec/data/dsc_lcm.pfx
Binary files differ
diff --git a/knife/spec/data/environment-config.rb b/knife/spec/data/environment-config.rb
new file mode 100644
index 0000000000..a157ecf0ea
--- /dev/null
+++ b/knife/spec/data/environment-config.rb
@@ -0,0 +1,5 @@
+#
+# Sample Chef Config File
+#
+
+environment "production" \ No newline at end of file
diff --git a/knife/spec/data/file-providers-method-snapshot-chef-11-4.json b/knife/spec/data/file-providers-method-snapshot-chef-11-4.json
new file mode 100644
index 0000000000..f6695aacd6
--- /dev/null
+++ b/knife/spec/data/file-providers-method-snapshot-chef-11-4.json
@@ -0,0 +1,127 @@
+{
+ "Chef::Provider::CookbookFile": [
+ "action_create",
+ "file_cache_location",
+ "resource_cookbook",
+ "backup_new_resource",
+ "content_stale?",
+ "diff_current_from_content",
+ "is_binary?",
+ "diff_current",
+ "setup_acl",
+ "compare_content",
+ "set_content",
+ "update_new_file_state",
+ "set_all_access_controls",
+ "action_create_if_missing",
+ "action_delete",
+ "action_touch",
+ "backup",
+ "deploy_tempfile",
+ "shell_out",
+ "shell_out!",
+ "run_command_compatible_options",
+ "checksum",
+ "access_controls",
+ "enforce_ownership_and_permissions"
+ ],
+ "Chef::Provider::RemoteFile": [
+ "action_create",
+ "current_resource_matches_target_checksum?",
+ "matches_current_checksum?",
+ "backup_new_resource",
+ "source_file",
+ "http_client_opts",
+ "diff_current_from_content",
+ "is_binary?",
+ "diff_current",
+ "setup_acl",
+ "compare_content",
+ "set_content",
+ "update_new_file_state",
+ "set_all_access_controls",
+ "action_create_if_missing",
+ "action_delete",
+ "action_touch",
+ "backup",
+ "deploy_tempfile",
+ "shell_out",
+ "shell_out!",
+ "run_command_compatible_options",
+ "checksum",
+ "access_controls",
+ "enforce_ownership_and_permissions"
+ ],
+ "Chef::Provider::Template": [
+ "action_create",
+ "template_finder",
+ "template_location",
+ "resource_cookbook",
+ "rendered",
+ "content_matches?",
+ "render_template",
+ "diff_current_from_content",
+ "is_binary?",
+ "diff_current",
+ "setup_acl",
+ "compare_content",
+ "set_content",
+ "update_new_file_state",
+ "set_all_access_controls",
+ "action_create_if_missing",
+ "action_delete",
+ "action_touch",
+ "backup",
+ "deploy_tempfile",
+ "shell_out",
+ "shell_out!",
+ "run_command_compatible_options",
+ "checksum",
+ "access_controls",
+ "enforce_ownership_and_permissions"
+ ],
+ "Chef::Provider::Directory": [
+ "action_create",
+ "action_delete",
+ "diff_current_from_content",
+ "is_binary?",
+ "diff_current",
+ "setup_acl",
+ "compare_content",
+ "set_content",
+ "update_new_file_state",
+ "set_all_access_controls",
+ "action_create_if_missing",
+ "action_touch",
+ "backup",
+ "deploy_tempfile",
+ "shell_out",
+ "shell_out!",
+ "run_command_compatible_options",
+ "checksum",
+ "access_controls",
+ "enforce_ownership_and_permissions"
+ ],
+ "Chef::Provider::File": [
+ "diff_current_from_content",
+ "is_binary?",
+ "diff_current",
+ "setup_acl",
+ "compare_content",
+ "set_content",
+ "update_new_file_state",
+ "action_create",
+ "set_all_access_controls",
+ "action_create_if_missing",
+ "action_delete",
+ "action_touch",
+ "backup",
+ "deploy_tempfile",
+ "shell_out",
+ "shell_out!",
+ "run_command_compatible_options",
+ "checksum",
+ "access_controls",
+ "enforce_ownership_and_permissions"
+ ]
+}
diff --git a/knife/spec/data/fileedit/blank b/knife/spec/data/fileedit/blank
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/fileedit/blank
diff --git a/knife/spec/data/fileedit/hosts b/knife/spec/data/fileedit/hosts
new file mode 100644
index 0000000000..6fbdc0f59d
--- /dev/null
+++ b/knife/spec/data/fileedit/hosts
@@ -0,0 +1,4 @@
+127.0.0.1 localhost
+255.255.255.255 broadcasthost
+::1 localhost
+fe80::1%lo0 localhost
diff --git a/knife/spec/data/gems/chef-integration-test-0.1.0.gem b/knife/spec/data/gems/chef-integration-test-0.1.0.gem
new file mode 100644
index 0000000000..bcf1c77fe2
--- /dev/null
+++ b/knife/spec/data/gems/chef-integration-test-0.1.0.gem
Binary files differ
diff --git a/knife/spec/data/git_bundles/example-repo.gitbundle b/knife/spec/data/git_bundles/example-repo.gitbundle
new file mode 100644
index 0000000000..de08296dc3
--- /dev/null
+++ b/knife/spec/data/git_bundles/example-repo.gitbundle
Binary files differ
diff --git a/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
new file mode 100644
index 0000000000..24e36f7eeb
--- /dev/null
+++ b/knife/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
Binary files differ
diff --git a/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle
new file mode 100644
index 0000000000..0a96fbb24f
--- /dev/null
+++ b/knife/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle
Binary files differ
diff --git a/knife/spec/data/git_bundles/sinatra-test-app.gitbundle b/knife/spec/data/git_bundles/sinatra-test-app.gitbundle
new file mode 100644
index 0000000000..43c54eae5c
--- /dev/null
+++ b/knife/spec/data/git_bundles/sinatra-test-app.gitbundle
Binary files differ
diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md
new file mode 100644
index 0000000000..3ac2a4f90c
--- /dev/null
+++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md
@@ -0,0 +1,4 @@
+# incomplete-metadata
+
+TODO: Enter the cookbook description here.
+
diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb
new file mode 100644
index 0000000000..50284be3dd
--- /dev/null
+++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb
@@ -0,0 +1,13 @@
+# ## INCOMPLETE METADATA ##
+# This cookbook is invalid b/c it does not set the `name' of the cookbook.
+
+# Commented out for illustrative purposes
+# name 'incomplete-metadata'
+
+maintainer ''
+maintainer_email ''
+license ''
+description 'Installs/Configures incomplete-metadata'
+long_description 'Installs/Configures incomplete-metadata'
+version '0.1.0'
+
diff --git a/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb
new file mode 100644
index 0000000000..0d8cca1bda
--- /dev/null
+++ b/knife/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb
@@ -0,0 +1,8 @@
+#
+# Cookbook Name:: incomplete-metadata
+# Recipe:: default
+#
+# Copyright 2014-2016,
+#
+#
+#
diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md
new file mode 100644
index 0000000000..a41867f17c
--- /dev/null
+++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md
@@ -0,0 +1,4 @@
+# invalid-metadata
+
+TODO: Enter the cookbook description here.
+
diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb
new file mode 100644
index 0000000000..1130ee40e8
--- /dev/null
+++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb
@@ -0,0 +1,9 @@
+name 'invalid-metadata'
+maintainer ''
+maintainer_email ''
+license ''
+description 'Installs/Configures invalid-metadata'
+long_description 'Installs/Configures invalid-metadata'
+version '0.1.0'
+
+raise "THIS METADATA HAS A BUG"
diff --git a/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb
new file mode 100644
index 0000000000..33ec1144fe
--- /dev/null
+++ b/knife/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb
@@ -0,0 +1,8 @@
+#
+# Cookbook Name:: invalid-metadata
+# Recipe:: default
+#
+# Copyright 2014-2016,
+#
+#
+#
diff --git a/knife/spec/data/kitchen/chefignore b/knife/spec/data/kitchen/chefignore
new file mode 100644
index 0000000000..a90dc15ebe
--- /dev/null
+++ b/knife/spec/data/kitchen/chefignore
@@ -0,0 +1,6 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme\.rb
diff --git a/knife/spec/data/kitchen/openldap/attributes/default.rb b/knife/spec/data/kitchen/openldap/attributes/default.rb
new file mode 100644
index 0000000000..d208959475
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/attributes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing to see here, move along
+#
diff --git a/knife/spec/data/kitchen/openldap/attributes/robinson.rb b/knife/spec/data/kitchen/openldap/attributes/robinson.rb
new file mode 100644
index 0000000000..9d6b44d464
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/attributes/robinson.rb
@@ -0,0 +1,3 @@
+#
+# Smokey lives here
+# \ No newline at end of file
diff --git a/knife/spec/data/kitchen/openldap/definitions/client.rb b/knife/spec/data/kitchen/openldap/definitions/client.rb
new file mode 100644
index 0000000000..d4c2263b54
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/definitions/client.rb
@@ -0,0 +1,3 @@
+#
+# A sad client
+#
diff --git a/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb b/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
new file mode 100644
index 0000000000..510f0c35da
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
@@ -0,0 +1,3 @@
+#
+# Was in people magazine this month...
+# \ No newline at end of file
diff --git a/knife/spec/data/kitchen/openldap/recipes/gigantor.rb b/knife/spec/data/kitchen/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000000..70a41960eb
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+ pretty_kitty true
+end \ No newline at end of file
diff --git a/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb b/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb
new file mode 100644
index 0000000000..15095986c6
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/recipes/ignoreme.rb
@@ -0,0 +1,3 @@
+#
+# this file will never be seen
+# \ No newline at end of file
diff --git a/knife/spec/data/kitchen/openldap/recipes/woot.rb b/knife/spec/data/kitchen/openldap/recipes/woot.rb
new file mode 100644
index 0000000000..44893dae36
--- /dev/null
+++ b/knife/spec/data/kitchen/openldap/recipes/woot.rb
@@ -0,0 +1,3 @@
+#
+# Such a funny word..
+#
diff --git a/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb b/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
diff --git a/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb b/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
diff --git a/knife/spec/data/knife_subcommand/test_explicit_category.rb b/knife/spec/data/knife_subcommand/test_explicit_category.rb
new file mode 100644
index 0000000000..96d50691a1
--- /dev/null
+++ b/knife/spec/data/knife_subcommand/test_explicit_category.rb
@@ -0,0 +1,7 @@
+module KnifeSpecs
+ class TestExplicitCategory < Chef::Knife
+ # i.e., the cookbook site commands should be in the cookbook site
+ # category instead of cookbook (which is what would be assumed)
+ category "cookbook site"
+ end
+end \ No newline at end of file
diff --git a/knife/spec/data/knife_subcommand/test_name_mapping.rb b/knife/spec/data/knife_subcommand/test_name_mapping.rb
new file mode 100644
index 0000000000..25c49b0df0
--- /dev/null
+++ b/knife/spec/data/knife_subcommand/test_name_mapping.rb
@@ -0,0 +1,4 @@
+module KnifeSpecs
+ class TestNameMapping < Chef::Knife
+ end
+end
diff --git a/knife/spec/data/knife_subcommand/test_yourself.rb b/knife/spec/data/knife_subcommand/test_yourself.rb
new file mode 100644
index 0000000000..f18f565885
--- /dev/null
+++ b/knife/spec/data/knife_subcommand/test_yourself.rb
@@ -0,0 +1,21 @@
+module KnifeSpecs
+ class TestYourself < Chef::Knife
+
+ class << self
+ attr_reader :test_deps_loaded
+ end
+
+ deps do
+ @test_deps_loaded = true
+ end
+
+ option :scro, :short => '-s SCRO', :long => '--scro SCRO', :description => 'a configurable setting'
+
+ attr_reader :ran
+
+ def run
+ @ran = true
+ self # return self so tests can poke at me
+ end
+ end
+end
diff --git a/knife/spec/data/lwrp/providers/buck_passer.rb b/knife/spec/data/lwrp/providers/buck_passer.rb
new file mode 100644
index 0000000000..9d764277ce
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/buck_passer.rb
@@ -0,0 +1,28 @@
+provides :buck_passer
+
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ yield
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
+def action_pass_buck
+ lwrp_foo :prepared_thumbs do
+ action :prepare_thumbs
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_thumb_twiddler
+ end
+ end
+ lwrp_foo :twiddled_thumbs do
+ action :twiddle_thumbs
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_thumb_twiddler
+ end
+ end
+end
diff --git a/knife/spec/data/lwrp/providers/buck_passer_2.rb b/knife/spec/data/lwrp/providers/buck_passer_2.rb
new file mode 100644
index 0000000000..0b8f49f7bd
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/buck_passer_2.rb
@@ -0,0 +1,26 @@
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ yield
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
+def action_pass_buck
+ lwrp_bar :prepared_eyes do
+ action :prepare_eyes
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_paint_drying_watcher
+ end
+ end
+ lwrp_bar :dried_paint_watched do
+ action :watch_paint_dry
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_paint_drying_watcher
+ end
+ end
+end
diff --git a/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
new file mode 100644
index 0000000000..b86dc860d0
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
@@ -0,0 +1,28 @@
+# This action tests that embedded Resources have access to the enclosing Provider's
+# lexical scope (as demonstrated by the call to new_resource) and that all parameters
+# are passed properly (as demonstrated by the call to generate_new_name).
+attr_reader :enclosed_resource
+
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ yield
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
+def action_twiddle_thumbs
+ @enclosed_resource = lwrp_foo :foo do
+ monkey generate_new_name(new_resource.monkey){ 'the monkey' }
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_monkey_name_printer
+ end
+ end
+end
+
+def generate_new_name(str, &block)
+ "#{str}, #{block.call}"
+end
diff --git a/knife/spec/data/lwrp/providers/inline_compiler.rb b/knife/spec/data/lwrp/providers/inline_compiler.rb
new file mode 100644
index 0000000000..91a80b32af
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/inline_compiler.rb
@@ -0,0 +1,24 @@
+
+action :test do
+
+ ruby_block "interior-ruby-block-1" do
+ block do
+ # doesn't need to do anything
+ end
+ notifies :run, "ruby_block[interior-ruby-block-2]", :immediately
+ end
+
+ ruby_block "interior-ruby-block-2" do
+ block do
+ $interior_ruby_block_2 = "executed"
+ end
+ action :nothing
+ end
+end
+
+action :no_updates do
+ ruby_block "no-action" do
+ block {}
+ action :nothing
+ end
+end
diff --git a/knife/spec/data/lwrp/providers/monkey_name_printer.rb b/knife/spec/data/lwrp/providers/monkey_name_printer.rb
new file mode 100644
index 0000000000..97ca66c65d
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/monkey_name_printer.rb
@@ -0,0 +1,5 @@
+attr_reader :monkey_name
+
+action :twiddle_thumbs do
+ @monkey_name = "my monkey's name is '#{new_resource.monkey}'"
+end
diff --git a/knife/spec/data/lwrp/providers/paint_drying_watcher.rb b/knife/spec/data/lwrp/providers/paint_drying_watcher.rb
new file mode 100644
index 0000000000..04b4732dcc
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/paint_drying_watcher.rb
@@ -0,0 +1,7 @@
+action :prepare_eyes do
+ "Prepared"
+end
+
+action :watch_paint_dry do
+ "This isn't very interesting"
+end
diff --git a/knife/spec/data/lwrp/providers/thumb_twiddler.rb b/knife/spec/data/lwrp/providers/thumb_twiddler.rb
new file mode 100644
index 0000000000..7f014615db
--- /dev/null
+++ b/knife/spec/data/lwrp/providers/thumb_twiddler.rb
@@ -0,0 +1,7 @@
+action :prepare_thumbs do
+ "Prepared"
+end
+
+action :twiddle_thumbs do
+ "Twiddle twiddle"
+end
diff --git a/knife/spec/data/lwrp/resources/bar.rb b/knife/spec/data/lwrp/resources/bar.rb
new file mode 100644
index 0000000000..d36575917d
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/bar.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+provides :lwrp_bar # This makes sure that we cover the case of lwrps using provides
+actions :pass_buck, :prepare_eyes, :watch_paint_dry
diff --git a/knife/spec/data/lwrp/resources/buck_passer.rb b/knife/spec/data/lwrp/resources/buck_passer.rb
new file mode 100644
index 0000000000..6f542dc423
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/buck_passer.rb
@@ -0,0 +1,6 @@
+unified_mode true
+
+provides :buck_passer
+
+default_action :pass_buck
+actions :pass_buck
diff --git a/knife/spec/data/lwrp/resources/buck_passer_2.rb b/knife/spec/data/lwrp/resources/buck_passer_2.rb
new file mode 100644
index 0000000000..c0ab7d7885
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/buck_passer_2.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+default_action :pass_buck
+actions :pass_buck
diff --git a/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb b/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb
new file mode 100644
index 0000000000..faece8b582
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+default_action :twiddle_thumbs
+actions :twiddle_thumbs
diff --git a/knife/spec/data/lwrp/resources/foo.rb b/knife/spec/data/lwrp/resources/foo.rb
new file mode 100644
index 0000000000..d4d2155122
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/foo.rb
@@ -0,0 +1,6 @@
+unified_mode true
+
+actions :prepare_thumbs, :twiddle_thumbs
+default_action :pass_buck
+
+attribute :monkey, :kind_of => String
diff --git a/knife/spec/data/lwrp/resources/inline_compiler.rb b/knife/spec/data/lwrp/resources/inline_compiler.rb
new file mode 100644
index 0000000000..0e63e4ce43
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/inline_compiler.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+default_action :test
+actions :test, :no_updates
diff --git a/knife/spec/data/lwrp/resources/monkey_name_printer.rb b/knife/spec/data/lwrp/resources/monkey_name_printer.rb
new file mode 100644
index 0000000000..60c3203dca
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/monkey_name_printer.rb
@@ -0,0 +1,6 @@
+unified_mode true
+
+property :monkey
+
+default_action :twiddle_thumbs
+actions :twiddle_thumbs
diff --git a/knife/spec/data/lwrp/resources/paint_drying_watcher.rb b/knife/spec/data/lwrp/resources/paint_drying_watcher.rb
new file mode 100644
index 0000000000..1bff7c5b50
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/paint_drying_watcher.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+default_action :prepare_eyes
+actions :prepare_eyes, :watch_paint_dry
diff --git a/knife/spec/data/lwrp/resources/thumb_twiddler.rb b/knife/spec/data/lwrp/resources/thumb_twiddler.rb
new file mode 100644
index 0000000000..f4ba71cebc
--- /dev/null
+++ b/knife/spec/data/lwrp/resources/thumb_twiddler.rb
@@ -0,0 +1,4 @@
+unified_mode true
+
+default_action :prepare_thumbs
+actions :prepare_thumbs, :twiddle_thumbs
diff --git a/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb b/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
new file mode 100644
index 0000000000..ef6b694cb2
--- /dev/null
+++ b/knife/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
@@ -0,0 +1,3 @@
+unified_mode true
+
+attribute :penguin, :kind_of => String, :default => node[:penguin_name]
diff --git a/knife/spec/data/lwrp_const_scoping/resources/conflict.rb b/knife/spec/data/lwrp_const_scoping/resources/conflict.rb
new file mode 100644
index 0000000000..059460afc0
--- /dev/null
+++ b/knife/spec/data/lwrp_const_scoping/resources/conflict.rb
@@ -0,0 +1 @@
+unified_mode true
diff --git a/knife/spec/data/lwrp_override/providers/buck_passer.rb b/knife/spec/data/lwrp_override/providers/buck_passer.rb
new file mode 100644
index 0000000000..2061b391dc
--- /dev/null
+++ b/knife/spec/data/lwrp_override/providers/buck_passer.rb
@@ -0,0 +1,5 @@
+# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore
+
+action :buck_stops_here do
+ log "This should be overwritten by ../lwrp_override/buck_passer.rb"
+end
diff --git a/knife/spec/data/lwrp_override/resources/foo.rb b/knife/spec/data/lwrp_override/resources/foo.rb
new file mode 100644
index 0000000000..1d6be84e58
--- /dev/null
+++ b/knife/spec/data/lwrp_override/resources/foo.rb
@@ -0,0 +1,11 @@
+# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore
+unified_mode true
+
+actions :never_execute
+
+attribute :ever, :kind_of => String
+
+class ::Chef
+ def method_created_by_override_lwrp_foo
+ end
+end
diff --git a/knife/spec/data/mac_users/10.9.plist.xml b/knife/spec/data/mac_users/10.9.plist.xml
new file mode 100644
index 0000000000..be3456ea6c
--- /dev/null
+++ b/knife/spec/data/mac_users/10.9.plist.xml
@@ -0,0 +1,560 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KerberosKeys</key>
+ <array>
+ <data>
+ MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCAJxcIcjX3sMb98++d0
+ YvKqc351+CJJTMpyJO5mwWFMCaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEy
+ NDA4NDVBMjU0OUZCOEUwRjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQw
+ Y6EbMBmgAwIBEaESBBDzYvuM3CLsLOGCIX4FJ8vdokQwQqADAgEDoTsEOUxL
+ REM6U0hBMS40QTI0MDg0NUEyNTQ5RkI4RTBGMjg0RTU2RTI4MTc3NTZFRTlD
+ RDIydmFncmFudDBroSMwIaADAgEQoRoEGCkvuVvN92vqnm0cy+9GWNBoIEoW
+ XtUNx6JEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEyNDA4NDVBMjU0OUZCOEUw
+ RjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQ=
+ </data>
+ </array>
+ <key>ShadowHashData</key>
+ <array>
+ <data>
+ YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50
+ cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIASYBqQ2xfL+LpICOY4L7DTudimwaGQ
+ R3v2gKshr7YGVGcTblXMIIpvdBVuPa8g+xM2nvS3uvoEfYA1n7RqSKStzNVI
+ 67M4UbCTR8yoQ0Gn+TonFHND+J+4Q/tGwAF9Jkr6SXa6rPlBuRW9HsHKJMML
+ PnWeAkA+AvWf5/9ZOKdjbE8QIO6VS+Ry/cYN34lIR4FDOZNiXwBq9uyBDAj0
+ mn5BOUahEYayCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA
+ AAAAAOo=
+ </data>
+ </array>
+ <key>_writers_hint</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_jpegphoto</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_passwd</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_picture</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>authentication_authority</key>
+ <array>
+ <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA512-PBKDF2&gt;</string>
+ <string>;Kerberosv5;;vagrant@LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22;LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22</string>
+ </array>
+ <key>generateduid</key>
+ <array>
+ <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string>
+ </array>
+ <key>gid</key>
+ <array>
+ <string>80</string>
+ </array>
+ <key>home</key>
+ <array>
+ <string>/Users/vagrant</string>
+ </array>
+ <key>jpegphoto</key>
+ <array>
+ <data>
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw
+ bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs
+ AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA
+ AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH
+ MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk
+ AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH
+ ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv
+ ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo
+ AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA
+ AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv
+ S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD
+ Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa
+ AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA
+ AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp
+ RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE
+ 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg
+ AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA
+ QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo
+ AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E
+ FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy
+ AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA
+ RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl
+ AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA
+ bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg
+ AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA
+ bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs
+ AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA
+ RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv
+ AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD
+ wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl
+ AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A
+ ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH
+ AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
+ bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs
+ AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E
+ RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn
+ BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA
+ ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz
+ AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo
+ dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN
+ AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A
+ ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA
+ /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH
+ BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA
+ QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+ Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A
+ AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE
+ AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
+ GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+ eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI
+ ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+ AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
+ BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico
+ KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0
+ 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ
+ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i
+ P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4
+ mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK
+ ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W
+ gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q
+ fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7
+ j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8
+ zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6
+ 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX
+ 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4
+ 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3
+ XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a
+ ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus
+ K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW
+ VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A
+ Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux
+ +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D
+ CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ
+ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS
+ MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/
+ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP
+ FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/
+ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r
+ 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY
+ LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f
+ TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i
+ yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx
+ nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi
+ eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE
+ aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE
+ ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE
+ bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R
+ rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT
+ XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC
+ MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ
+ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN
+ 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA
+ Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL
+ LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp
+ yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2
+ JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl
+ JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF
+ FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy
+ e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY
+ jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP
+ JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2
+ a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW
+ tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9
+ yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM
+ yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A
+ Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz
+ MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z
+ b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n
+ w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR
+ /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f
+ iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv
+ 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z
+ ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe
+ RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr
+ CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca
+ UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU
+ tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL
+ eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX
+ x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2
+ x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1
+ gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN
+ N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3
+ i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC
+ as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8
+ KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm
+ OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9
+ Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY
+ 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT
+ QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H
+ s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz
+ /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm
+ r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+
+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw
+ dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7
+ PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op
+ a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc
+ R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6
+ INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW
+ MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo
+ ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry
+ P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y
+ TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51
+ H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX
+ n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG//
+ ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se
+ B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H
+ bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ
+ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl
+ 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt
+ P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4
+ vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+
+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy
+ zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A
+ 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U
+ Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7
+ J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd
+ zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba
+ tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V
+ /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx
+ H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf
+ q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK
+ eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz
+ 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf
+ GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR
+ QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf
+ DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5
+ EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR
+ 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP
+ paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf
+ EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG
+ 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry
+ f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95
+ 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO
+ FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m
+ Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g
+ kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9
+ ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM
+ x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7
+ x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F
+ fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm
+ qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf
+ FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z
+ m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG
+ lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A
+ UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro
+ /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm
+ t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y
+ H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O
+ oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY
+ NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs
+ PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7
+ iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf
+ Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw
+ F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/
+ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m
+ e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv
+ DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V
+ 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V
+ JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx
+ TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d
+ qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv
+ +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw
+ 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg
+ i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6
+ jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB
+ 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u
+ qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu
+ gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX
+ qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ
+ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk
+ blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb
+ MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A
+ fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0
+ vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r
+ zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV
+ rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+
+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/
+ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79
+ Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP
+ ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp
+ Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ
+ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/
+ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps
+ 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb
+ WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+
+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG
+ w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ
+ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF
+ FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX
+ 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af
+ u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A
+ yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT
+ +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V
+ 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5
+ MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH
+ pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP
+ sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r
+ /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP
+ JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f
+ 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd
+ 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH
+ SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av
+ QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B
+ X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t
+ ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk
+ 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ
+ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e
+ XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8
+ mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc
+ SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO
+ UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+
+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf
+ aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy
+ 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo
+ or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn
+ u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg
+ CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2
+ PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt
+ LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+
+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob
+ BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s
+ cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F
+ /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+
+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv
+ 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ
+ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/
+ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN
+ s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh
+ VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY
+ 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn
+ +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3
+ xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP
+ 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH
+ QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep
+ fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/
+ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS
+ GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5
+ J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/
+ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz
+ +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1
+ zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa
+ 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci
+ iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf
+ 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj
+ XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf
+ 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY
+ AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3
+ XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9
+ x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi
+ KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC
+ d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c
+ KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF
+ AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2
+ 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU
+ rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+
+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw
+ wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL
+ 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL
+ XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+
+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/
+ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a
+ hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux
+ 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH
+ tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6
+ 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi
+ ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6
+ +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt
+ RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A
+ Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw
+ J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9
+ F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA
+ BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ
+ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf
+ 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw
+ EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA
+ H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN
+ ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ
+ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA
+ q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j
+ X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K
+ PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A
+ MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT
+ gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff
+ n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO
+ c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4
+ /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE
+ fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O
+ v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA
+ CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO
+ ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM
+ kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ
+ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/
+ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB
+ 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo
+ oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK
+ KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY
+ 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky
+ 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr
+ FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6
+ D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j
+ aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj
+ b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v
+ mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF
+ FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN
+ 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f
+ fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG
+ wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP
+ xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw
+ /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY
+ OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J
+ 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/
+ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n
+ u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF
+ 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4
+ hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS
+ vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T
+ 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l
+ osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z
+ YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU
+ UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV
+ l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/
+ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U
+ up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S
+ ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63
+ hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW
+ htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC
+ 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9
+ ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j
+ dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk
+ +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB
+ cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ
+ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe
+ CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q
+ SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0
+ HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af
+ +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F
+ anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp
+ avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA
+ rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi
+ IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt
+ B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG
+ GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+ D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A
+ kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv
+ dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t
+ Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3
+ OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1
+ JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O
+ qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs
+ qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u
+ /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz
+ 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y
+ /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40
+ rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i
+ rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3
+ cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD
+ xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0
+ VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn
+ 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN
+ I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP
+ FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA
+ CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ
+ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH
+ /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV
+ /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz
+ kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM
+ 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX
+ gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj
+ rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R
+ 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+
+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U
+ 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9
+ 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0
+ iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl
+ okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb
+ V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6
+ /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA
+ FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh
+ mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf
+ RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1
+ /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW
+ p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS
+ vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV
+ tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs
+ +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+
+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ
+ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0
+ m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia
+ /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le
+ FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9
+ 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0
+ uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX
+ 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V
+ 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv
+ QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva
+ Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu
+ By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
+ UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9
+ piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s
+ fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/
+ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL
+ /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc
+ 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b
+ /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o
+ 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB
+ k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A
+ tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI
+ TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm
+ Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF
+ FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+ AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR
+ WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q==
+ </data>
+ </array>
+ <key>name</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>passwd</key>
+ <array>
+ <string>********</string>
+ </array>
+ <key>passwordpolicyoptions</key>
+ <array>
+ <data>
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp
+ bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr
+ ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt
+ MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8
+ L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl
+ eT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDE0LTAzLTA2
+ VDE4OjU0OjQ1WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo=
+ </data>
+ </array>
+ <key>realname</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>shell</key>
+ <array>
+ <string>/bin/bash</string>
+ </array>
+ <key>uid</key>
+ <array>
+ <string>501</string>
+ </array>
+</dict>
+</plist>
diff --git a/knife/spec/data/mac_users/10.9.shadow.xml b/knife/spec/data/mac_users/10.9.shadow.xml
new file mode 100644
index 0000000000..b8359d080a
--- /dev/null
+++ b/knife/spec/data/mac_users/10.9.shadow.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SALTED-SHA512-PBKDF2</key>
+ <dict>
+ <key>entropy</key>
+ <data>
+ EmAakNsXy/i6SAjmOC+w07nYpsGhkEd79oCrIa+2BlRnE25VzCCKb3QVbj2v
+ IPsTNp70t7r6BH2ANZ+0akikrczVSOuzOFGwk0fMqENBp/k6JxRzQ/ifuEP7
+ RsABfSZK+kl2uqz5QbkVvR7ByiTDCz51ngJAPgL1n+f/WTinY2w=
+ </data>
+ <key>iterations</key>
+ <integer>34482</integer>
+ <key>salt</key>
+ <data>
+ 7pVL5HL9xg3fiUhHgUM5k2JfAGr27IEMCPSafkE5RqE=
+ </data>
+ </dict>
+</dict>
+</plist>
diff --git a/knife/spec/data/metadata/quick_start/metadata.rb b/knife/spec/data/metadata/quick_start/metadata.rb
new file mode 100644
index 0000000000..e7ae9d1749
--- /dev/null
+++ b/knife/spec/data/metadata/quick_start/metadata.rb
@@ -0,0 +1,14 @@
+maintainer "Chef Software, Inc."
+maintainer_email "cookbooks@chef.io"
+license "Apache 2.0"
+description "Example cookbook for quick_start wiki document"
+version "0.7"
+
+%w{
+ redhat fedora centos
+ ubuntu debian
+ macosx freebsd openbsd
+ solaris
+}.each do |os|
+ supports os
+end
diff --git a/knife/spec/data/mixin/invalid_data.rb b/knife/spec/data/mixin/invalid_data.rb
new file mode 100644
index 0000000000..e6f6c3a783
--- /dev/null
+++ b/knife/spec/data/mixin/invalid_data.rb
@@ -0,0 +1,3 @@
+# For spec/functional/mixin/from_file_spec.rb
+a :foo
+c :bar
diff --git a/knife/spec/data/mixin/real_data.rb b/knife/spec/data/mixin/real_data.rb
new file mode 100644
index 0000000000..e15b86fc68
--- /dev/null
+++ b/knife/spec/data/mixin/real_data.rb
@@ -0,0 +1,2 @@
+# For spec/functional/mixin/from_file_spec.rb
+a :foo
diff --git a/knife/spec/data/nested.json b/knife/spec/data/nested.json
new file mode 100644
index 0000000000..775bb21981
--- /dev/null
+++ b/knife/spec/data/nested.json
@@ -0,0 +1,2 @@
+{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test"
+}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
diff --git a/knife/spec/data/nodes/default.rb b/knife/spec/data/nodes/default.rb
new file mode 100644
index 0000000000..1d6291f166
--- /dev/null
+++ b/knife/spec/data/nodes/default.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-default"
+
+##
+# Nodes can set arbitrary arguments
+##
+default[:sunshine] = "in"
+default[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
diff --git a/knife/spec/data/nodes/test.example.com.rb b/knife/spec/data/nodes/test.example.com.rb
new file mode 100644
index 0000000000..b30e8489e0
--- /dev/null
+++ b/knife/spec/data/nodes/test.example.com.rb
@@ -0,0 +1,17 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com"
+
+##
+# Nodes can set arbitrary arguments
+##
+normal[:sunshine] = "in"
+normal[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
+
+chef_environment "dev"
diff --git a/knife/spec/data/nodes/test.rb b/knife/spec/data/nodes/test.rb
new file mode 100644
index 0000000000..e1301130d2
--- /dev/null
+++ b/knife/spec/data/nodes/test.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-short"
+
+##
+# Nodes can set arbitrary arguments
+##
+default[:sunshine] = "in"
+default[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
diff --git a/knife/spec/data/null_config.rb b/knife/spec/data/null_config.rb
new file mode 100644
index 0000000000..8865745632
--- /dev/null
+++ b/knife/spec/data/null_config.rb
@@ -0,0 +1 @@
+$__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
diff --git a/knife/spec/data/object_loader/environments/test.json b/knife/spec/data/object_loader/environments/test.json
new file mode 100644
index 0000000000..744819c60a
--- /dev/null
+++ b/knife/spec/data/object_loader/environments/test.json
@@ -0,0 +1,7 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "description": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/object_loader/environments/test.rb b/knife/spec/data/object_loader/environments/test.rb
new file mode 100644
index 0000000000..8bf4ee52e0
--- /dev/null
+++ b/knife/spec/data/object_loader/environments/test.rb
@@ -0,0 +1,2 @@
+name "test"
+description "prod"
diff --git a/knife/spec/data/object_loader/environments/test_json_class.json b/knife/spec/data/object_loader/environments/test_json_class.json
new file mode 100644
index 0000000000..f51943f60e
--- /dev/null
+++ b/knife/spec/data/object_loader/environments/test_json_class.json
@@ -0,0 +1,8 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "json_class": "Chef::Environment",
+ "description": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/object_loader/nodes/test.json b/knife/spec/data/object_loader/nodes/test.json
new file mode 100644
index 0000000000..a370d6378d
--- /dev/null
+++ b/knife/spec/data/object_loader/nodes/test.json
@@ -0,0 +1,7 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "environment": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/object_loader/nodes/test.rb b/knife/spec/data/object_loader/nodes/test.rb
new file mode 100644
index 0000000000..9629539859
--- /dev/null
+++ b/knife/spec/data/object_loader/nodes/test.rb
@@ -0,0 +1,2 @@
+name "test"
+environment "prod"
diff --git a/knife/spec/data/object_loader/nodes/test_json_class.json b/knife/spec/data/object_loader/nodes/test_json_class.json
new file mode 100644
index 0000000000..3c5b12ce6c
--- /dev/null
+++ b/knife/spec/data/object_loader/nodes/test_json_class.json
@@ -0,0 +1,8 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "json_class": "Chef::Node",
+ "environment": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/object_loader/roles/test.json b/knife/spec/data/object_loader/roles/test.json
new file mode 100644
index 0000000000..744819c60a
--- /dev/null
+++ b/knife/spec/data/object_loader/roles/test.json
@@ -0,0 +1,7 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "description": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/object_loader/roles/test.rb b/knife/spec/data/object_loader/roles/test.rb
new file mode 100644
index 0000000000..8bf4ee52e0
--- /dev/null
+++ b/knife/spec/data/object_loader/roles/test.rb
@@ -0,0 +1,2 @@
+name "test"
+description "prod"
diff --git a/knife/spec/data/object_loader/roles/test_json_class.json b/knife/spec/data/object_loader/roles/test_json_class.json
new file mode 100644
index 0000000000..f975c8be43
--- /dev/null
+++ b/knife/spec/data/object_loader/roles/test_json_class.json
@@ -0,0 +1,8 @@
+{
+ /* testing that we support c-style comments */
+ // testing that we support c++-style comments as well
+ "name": "test",
+ "json_class": "Chef::Role",
+ "description": "prod",
+ "run_list": []
+}
diff --git a/knife/spec/data/old_home_dir/my-dot-emacs b/knife/spec/data/old_home_dir/my-dot-emacs
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/old_home_dir/my-dot-emacs
diff --git a/knife/spec/data/old_home_dir/my-dot-vim b/knife/spec/data/old_home_dir/my-dot-vim
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/old_home_dir/my-dot-vim
diff --git a/knife/spec/data/partial_one.erb b/knife/spec/data/partial_one.erb
new file mode 100644
index 0000000000..9fd79a0d65
--- /dev/null
+++ b/knife/spec/data/partial_one.erb
@@ -0,0 +1 @@
+partial one <%= render('test.erb', :cookbook => 'openldap').strip %> calling home
diff --git a/knife/spec/data/prefer_metadata_json/metadata.json b/knife/spec/data/prefer_metadata_json/metadata.json
new file mode 100644
index 0000000000..eff8836a3b
--- /dev/null
+++ b/knife/spec/data/prefer_metadata_json/metadata.json
@@ -0,0 +1,51 @@
+{
+ "name": "prefer_metadata_json",
+ "description": "",
+ "long_description": "",
+ "maintainer": null,
+ "maintainer_email": null,
+ "license": "All rights reserved",
+ "platforms": {
+
+ },
+ "dependencies": {
+
+ },
+ "recommendations": {
+
+ },
+ "suggestions": {
+
+ },
+ "conflicting": {
+
+ },
+ "providing": {
+
+ },
+ "replacing": {
+
+ },
+ "attributes": {
+
+ },
+ "groupings": {
+
+ },
+ "recipes": {
+
+ },
+ "version": "1.2.3",
+ "source_url": "",
+ "issues_url": "",
+ "privacy": false,
+ "chef_versions": [
+
+ ],
+ "ohai_versions": [
+
+ ],
+ "gems": [
+
+ ]
+}
diff --git a/knife/spec/data/prefer_metadata_json/metadata.rb b/knife/spec/data/prefer_metadata_json/metadata.rb
new file mode 100644
index 0000000000..a46aa29a5c
--- /dev/null
+++ b/knife/spec/data/prefer_metadata_json/metadata.rb
@@ -0,0 +1,6 @@
+# these deliberately do not match metadata.json
+name "test"
+version "0.0.1"
+
+# this raises hard if anything even tries to parse it
+raise "TEH SADNESS"
diff --git a/knife/spec/data/prefer_metadata_json/recipes/default.rb b/knife/spec/data/prefer_metadata_json/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/prefer_metadata_json/recipes/default.rb
diff --git a/knife/spec/data/recipes.tgz b/knife/spec/data/recipes.tgz
new file mode 100644
index 0000000000..a6c172a001
--- /dev/null
+++ b/knife/spec/data/recipes.tgz
Binary files differ
diff --git a/knife/spec/data/recipes/test.rb b/knife/spec/data/recipes/test.rb
new file mode 100644
index 0000000000..c33d714a2c
--- /dev/null
+++ b/knife/spec/data/recipes/test.rb
@@ -0,0 +1,7 @@
+
+file "/etc/nsswitch.conf" do
+ action :create
+ owner "root"
+ group "root"
+ mode 0644
+end
diff --git a/knife/spec/data/remote_directory_data/remote_dir_file.txt b/knife/spec/data/remote_directory_data/remote_dir_file.txt
new file mode 100644
index 0000000000..17aa3a6838
--- /dev/null
+++ b/knife/spec/data/remote_directory_data/remote_dir_file.txt
@@ -0,0 +1 @@
+I'm a file inside a the root remote_directory source dir. \ No newline at end of file
diff --git a/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt b/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt
new file mode 100644
index 0000000000..a73728a4bb
--- /dev/null
+++ b/knife/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt
@@ -0,0 +1 @@
+I'm a file in a subdirectory inside a remote_directory source \ No newline at end of file
diff --git a/knife/spec/data/remote_file/nyan_cat.png b/knife/spec/data/remote_file/nyan_cat.png
new file mode 100644
index 0000000000..14cd6acf2a
--- /dev/null
+++ b/knife/spec/data/remote_file/nyan_cat.png
Binary files differ
diff --git a/knife/spec/data/remote_file/nyan_cat.png.gz b/knife/spec/data/remote_file/nyan_cat.png.gz
new file mode 100644
index 0000000000..efa9d4427a
--- /dev/null
+++ b/knife/spec/data/remote_file/nyan_cat.png.gz
Binary files differ
diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb
new file mode 100644
index 0000000000..3a3bab96e1
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes.rb
@@ -0,0 +1 @@
+default["aliased"]["attr"] = "value"
diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb
new file mode 100644
index 0000000000..a6f6c78bb0
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_attr/attributes/default.rb
@@ -0,0 +1 @@
+default["aliased"]["attr"] = "other"
diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb
new file mode 100644
index 0000000000..703a73ab19
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_attr/metadata.rb
@@ -0,0 +1,2 @@
+name "dup_attr"
+version "1.0.0"
diff --git a/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb b/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb
new file mode 100644
index 0000000000..d82e58fbcd
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_attr/recipe.rb
@@ -0,0 +1,3 @@
+ruby_block "root alias" do
+ block { }
+end
diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb
new file mode 100644
index 0000000000..3a3bab96e1
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/attributes.rb
@@ -0,0 +1 @@
+default["aliased"]["attr"] = "value"
diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb
new file mode 100644
index 0000000000..62273a64d5
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/metadata.rb
@@ -0,0 +1,2 @@
+name "dup_recipe"
+version "1.0.0"
diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb
new file mode 100644
index 0000000000..d82e58fbcd
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipe.rb
@@ -0,0 +1,3 @@
+ruby_block "root alias" do
+ block { }
+end
diff --git a/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb
new file mode 100644
index 0000000000..3eb7c22809
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/dup_recipe/recipes/default.rb
@@ -0,0 +1,3 @@
+ruby_block "other" do
+ block { }
+end
diff --git a/knife/spec/data/root_alias_cookbooks/simple/attributes.rb b/knife/spec/data/root_alias_cookbooks/simple/attributes.rb
new file mode 100644
index 0000000000..3a3bab96e1
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/simple/attributes.rb
@@ -0,0 +1 @@
+default["aliased"]["attr"] = "value"
diff --git a/knife/spec/data/root_alias_cookbooks/simple/metadata.rb b/knife/spec/data/root_alias_cookbooks/simple/metadata.rb
new file mode 100644
index 0000000000..9147558459
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/simple/metadata.rb
@@ -0,0 +1,2 @@
+name "simple"
+version "1.0.0"
diff --git a/knife/spec/data/root_alias_cookbooks/simple/recipe.rb b/knife/spec/data/root_alias_cookbooks/simple/recipe.rb
new file mode 100644
index 0000000000..d82e58fbcd
--- /dev/null
+++ b/knife/spec/data/root_alias_cookbooks/simple/recipe.rb
@@ -0,0 +1,3 @@
+ruby_block "root alias" do
+ block { }
+end
diff --git a/knife/spec/data/rubygems.org/latest_specs.4.8.gz b/knife/spec/data/rubygems.org/latest_specs.4.8.gz
new file mode 100644
index 0000000000..ab6a175f32
--- /dev/null
+++ b/knife/spec/data/rubygems.org/latest_specs.4.8.gz
Binary files differ
diff --git a/knife/spec/data/rubygems.org/nonexistent_gem b/knife/spec/data/rubygems.org/nonexistent_gem
new file mode 100644
index 0000000000..0ba94359df
--- /dev/null
+++ b/knife/spec/data/rubygems.org/nonexistent_gem
Binary files differ
diff --git a/knife/spec/data/rubygems.org/nonexistent_gem-info b/knife/spec/data/rubygems.org/nonexistent_gem-info
new file mode 100644
index 0000000000..7e88a0e205
--- /dev/null
+++ b/knife/spec/data/rubygems.org/nonexistent_gem-info
@@ -0,0 +1 @@
+This gem could not be found \ No newline at end of file
diff --git a/knife/spec/data/rubygems.org/sexp_processor b/knife/spec/data/rubygems.org/sexp_processor
new file mode 100644
index 0000000000..37c6e97769
--- /dev/null
+++ b/knife/spec/data/rubygems.org/sexp_processor
Binary files differ
diff --git a/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz b/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz
new file mode 100644
index 0000000000..38840f2682
--- /dev/null
+++ b/knife/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz
Binary files differ
diff --git a/knife/spec/data/rubygems.org/sexp_processor-info b/knife/spec/data/rubygems.org/sexp_processor-info
new file mode 100644
index 0000000000..78add7f2fa
--- /dev/null
+++ b/knife/spec/data/rubygems.org/sexp_processor-info
@@ -0,0 +1,49 @@
+---
+3.0.0 |checksum:ff9abf0d904ba57b9654352b396aa28cf6ad5315af99d8bbf664f5ff6efd3a5d
+3.0.1 |checksum:d012a759dc6950dcda667a359051c2e62e4bd19790aeed698a5e47d013ef3ae7
+3.0.2 |checksum:ba85e835493e6099c2f52937b77ce518d65af39409befdd9b43927c0a604ed87
+3.0.3 |checksum:a433b01d821f5e81200fbec77fc26a1ecb186ad7d8e40d19ed34ea295287170a
+3.0.4 |checksum:5f394545863d5abe5c3f53c3b883128f58900cd792504076a765b53c9a49f10a
+3.0.5 |checksum:8e06c84ed3a0159a0f2e6f7b10bbd056954ac2d33548318ccc3088556c8a8891
+3.0.6 |checksum:e6e0f30ff14b73b28f8e5803646aa6d2ed81b3b239e76815fd8160ea4121c650
+3.0.7 |checksum:7647c24bbebea0ef57a892953fc96349091924a399ee5d98f41da5d9db484816
+3.0.8 |checksum:6c8ff89feab635e332e115356451614a16d171485e34b6b30a6dea243caaaed7
+3.0.9 |checksum:b55c35100f5e1e191ff67eac8667aea9433d1492697c9434cdb35550cf6e4dd0
+3.0.10 |checksum:e168db8d4eccfc721685d939654698f1b419f018f45e38d17ab40033102987f4
+3.1.0 |checksum:3d9dd950ba0b235c4901d04e410c7e716feb491148bf0ca7daa0f510838b3bf7
+3.2.0 |checksum:5951f8d33ede2f68686c701142c6cc1004d6f525b0aa8e8279a1bf075542b0f0
+4.0.0 |checksum:36c185f8caecacb178500cabdc3e038862df640536c2e84ab763ae134462896f
+4.0.1 |checksum:efd33857c0f41a413ec3ea20251f43c4826fe8a11b01099335f4a3b6777eb727
+4.1.0 |checksum:d26879b9a0675ea156c82e26971149349a1474aac3da4d0d2a04cc18e6df73a3
+4.1.1 |checksum:15df4e54e0fab19e225862b36dff823d5b87d57ea998f2e47c52ce01de82b3d9
+4.1.2 |checksum:7c2ed2d62d0305f2c33cba2e99b288df0f3f4343c367b8ee8cad8c735abc8568
+4.1.3 |checksum:d02f1465c7f012f77a61abdaa841a5273a2277247bc143bfa11cf139a29dbdf8
+4.1.4 |checksum:f7798b1682dcf750dab5f4f8da548fee36f30864a4e4b0d8a63295d159357c3b
+4.1.5 |checksum:741c7dfe5e392ae39e22399546d25fe00ffdfc7a55e653e6a99b6770b1c0066c
+4.2.0 |checksum:3cde88e3d440f63af3cd48edca88bd98872622403740ecda78b7d27161367486
+4.2.1 |checksum:dfc3eef6ef13c5750c3faab782c4db6c74a7bcc5d03e56e4edeec21aab034185
+4.3.0 |checksum:7accb37900d1599c6f0f40be92bc62a5db4e5a7eb64f33a858cf83e798dd1ddc
+4.4.0 |checksum:71591ddbda99b5e12e4a46d377c87513850ca7aa4b1aa800ae02792cadee6be7
+4.4.1 |checksum:8a10333552216bf3d3846476cfe78dbc9b5724864e3f5016837724622d828f16
+4.4.2 |checksum:adba9d17de5957532223a1bf0e7bdba5ab849d6576e9210439a7d99e0cfa2595
+4.4.3 |checksum:b3eb96da1fe998f1c00665a9c645878518134cca7c35d39c4bb716e866f4cc57
+4.4.4 |checksum:43cab5a67ca409d62411f869ddb7a0a4de0988b489d3f1d610d9b6e521964fd5
+4.4.5 |checksum:af8713761f1b6604865830c54324e57c33e7cc05107ebdbee4e6d458f8f8fe7c
+4.5.0 |checksum:54d94dc52cf98a51548c8f3e77031a3347508b542b8cb066100ed3ac40c03081
+4.5.1 |checksum:1456a9be103bf1de0d34ff6980b77a5a72cf3d4b35bbd2182ac62506981a234c
+4.6.0 |checksum:e2498f90c75bd4c19d1739afadde8c03af26a881c8bd775f71d2f180de65b43f
+4.6.1 |checksum:e2e96c2ee3ea81e1dc7b4b4abee23b6e552e669cfe456ee69126a29a03373cd7
+4.7.0 |checksum:963a1f5b21c95595fb3cf1e8531784bf3d8fe30302cf6f271b08aefdc63e453f
+4.8.0 |checksum:5b9325f28b5be80ba8d43b7660f60ad67c9304fe8181dee89d3a348b13d2fada
+4.9.0 |checksum:333619bc71d563ee60f26fc5f3a7f57bd89ee3191177fdce87a014dcb1b8d3b0
+4.10.0b1 |checksum:8791e2006a2ddbf8dc96cfc19633de01af8cb8687703177a85aeb3f959974d5b,rubygems:> 1.3.1
+4.10.0 |checksum:b67a289ae4a3968d93dab0803d0ef5a262b6f94138ab98072e489d2aa8af4034
+4.10.1 |checksum:63d2297712eb1d6219ab1cb9207d9a239ac9ad20463c0b58ca865f0b46deb5ec
+4.11.0 |checksum:4c90ff17c492789fdd248369fa16ce65ef05576b3d9f593a49c6a0961dbcd5ee
+4.12.0 |checksum:671110574e96377a03b328bfb7f6339540443eca0b62913bf8fe38e9ebcb4470
+4.12.1 |checksum:f87cd92457a343b4e951e1f1ac3e8183f98de4640a32f6ceb44628332d21a088
+4.13.0 |checksum:47e86c22a2d7810897e3eae9669ab9afa220f5e6cea5ac1d47164650a9b857d3
+4.14.0 |checksum:99a20cc5e7b901f6b493a8ca5e13439b73b19671eaaca68a00216c4f66765edc
+4.14.1 |checksum:0fa8731445cf4a0c01570ec29aac4b50a0451ce66b1b31ad768f5035e3af7b90,ruby:~> 2.2
+4.15.0 |checksum:a5ec27d8055ad47444cfb7ce860bad8af2365772a82892f4a8a0d97e8e9e3b34,ruby:~> 2.2
+4.15.1 |checksum:9291a0f2247f50d15068ee6965b67cd7b678b0d273e18adf3c0b2ea4a890125c,ruby:< 3.1&>= 2.1
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb
new file mode 100644
index 0000000000..e45e7d9f68
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "circular-dep1::default"
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb
new file mode 100644
index 0000000000..fa1770b552
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep1-definition')
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb
new file mode 100644
index 0000000000..b20b648789
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("circular-dep1")
+
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb
new file mode 100644
index 0000000000..e990004b56
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/metadata.rb
@@ -0,0 +1,2 @@
+name "circular-dep1"
+depends "circular-dep2"
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb
new file mode 100644
index 0000000000..ee66286da4
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep1-provider')
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb b/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
new file mode 100644
index 0000000000..eaf68e9058
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('circular-dep1-resource')
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb
new file mode 100644
index 0000000000..37f396b1f9
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "circular-dep2::default"
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb
new file mode 100644
index 0000000000..a23bf8f679
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep2-definition')
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb
new file mode 100644
index 0000000000..ce0acc1231
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("circular-dep2")
+
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb
new file mode 100644
index 0000000000..3df19e03de
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/metadata.rb
@@ -0,0 +1,2 @@
+name "circular-dep2"
+depends "circular-dep1"
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb
new file mode 100644
index 0000000000..139ed59a9e
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep2-provider')
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb b/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
new file mode 100644
index 0000000000..63eb08d5b5
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('circular-dep2-resource')
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb
new file mode 100644
index 0000000000..3059494198
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "dependency1::aa_first"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb
new file mode 100644
index 0000000000..a65a3345bc
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "dependency1::default"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb b/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb
new file mode 100644
index 0000000000..94ffb30133
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "dependency1::zz_last"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb b/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb
new file mode 100644
index 0000000000..4e4344e41f
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency1-definition')
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb
new file mode 100644
index 0000000000..10dbb37a9e
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("dependency1")
+
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb b/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb
new file mode 100644
index 0000000000..f0ca0004b6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency1-provider')
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb b/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb b/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
new file mode 100644
index 0000000000..d355f7ce48
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('dependency1-resource')
diff --git a/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file b/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file
new file mode 100644
index 0000000000..60fee07cc6
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file
@@ -0,0 +1 @@
+raise "this should not be parsed by the loader"
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb b/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb
new file mode 100644
index 0000000000..8917bf9730
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "dependency2::default"
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb b/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb
new file mode 100644
index 0000000000..7839278319
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency2-definition')
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb
new file mode 100644
index 0000000000..27b3be10d1
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("dependency2")
+
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb b/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb
new file mode 100644
index 0000000000..6cc6310d54
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency2-provider')
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb b/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb b/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
new file mode 100644
index 0000000000..5ec44d4564
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('dependency2-resource')
diff --git a/knife/spec/data/run_context/cookbooks/include/recipes/default.rb b/knife/spec/data/run_context/cookbooks/include/recipes/default.rb
new file mode 100644
index 0000000000..8d22994252
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/include/recipes/default.rb
@@ -0,0 +1,24 @@
+module ::RanResources
+ def self.resources
+ @resources ||= []
+ end
+end
+class RunContextCustomResource < Chef::Resource
+ action :create do
+ ruby_block '4' do
+ block { RanResources.resources << 4 }
+ end
+ recipe_eval do
+ ruby_block '1' do
+ block { RanResources.resources << 1 }
+ end
+ include_recipe 'include::includee'
+ ruby_block '3' do
+ block { RanResources.resources << 3 }
+ end
+ end
+ ruby_block '5' do
+ block { RanResources.resources << 5 }
+ end
+ end
+end
diff --git a/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb b/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb
new file mode 100644
index 0000000000..87bb7f114e
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/include/recipes/includee.rb
@@ -0,0 +1,3 @@
+ruby_block '2' do
+ block { RanResources.resources << 2 }
+end
diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
new file mode 100644
index 0000000000..07294665b2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "no-default-attr::server"
diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
new file mode 100644
index 0000000000..cee4344a9c
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('no-default-attr-definition')
diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
new file mode 100644
index 0000000000..53b8adc934
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('no-default-attr-provider')
diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb b/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
new file mode 100644
index 0000000000..fd8fa73b5a
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('no-default-attr-resource')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb
new file mode 100644
index 0000000000..77309462b1
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "test-with-circular-deps::default"
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb
new file mode 100644
index 0000000000..f0840046b8
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-circular-deps-definition')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb
new file mode 100644
index 0000000000..76108f067c
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("test-with-circular-deps")
+
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb
new file mode 100644
index 0000000000..280c5ae7ce
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb
@@ -0,0 +1,2 @@
+name "test-with-circular-deps"
+depends "circular-dep1"
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb
new file mode 100644
index 0000000000..c25da707f2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-circular-deps-provider')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
new file mode 100644
index 0000000000..ffe7d099c9
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
@@ -0,0 +1,3 @@
+unified_mode true
+
+LibraryLoadOrder.record('test-with-circular-deps-resource')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb
new file mode 100644
index 0000000000..c4cc8151a4
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb
@@ -0,0 +1,2 @@
+normal_unless[:attr_load_order] = []
+normal[:attr_load_order] << "test-with-deps::default"
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb
new file mode 100644
index 0000000000..c481734b54
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-deps-definition')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb
new file mode 100644
index 0000000000..7dd942f8b3
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record("test-with-deps")
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb
new file mode 100644
index 0000000000..8909f11630
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/metadata.rb
@@ -0,0 +1,3 @@
+name "test-with-deps"
+depends "dependency1"
+depends "dependency2"
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb
new file mode 100644
index 0000000000..96146c68b7
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-deps-provider')
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb
diff --git a/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
new file mode 100644
index 0000000000..203efabec5
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
@@ -0,0 +1,2 @@
+unified_mode true
+LibraryLoadOrder.record('test-with-deps-resource')
diff --git a/knife/spec/data/run_context/cookbooks/test/attributes/default.rb b/knife/spec/data/run_context/cookbooks/test/attributes/default.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/attributes/default.rb
diff --git a/knife/spec/data/run_context/cookbooks/test/attributes/george.rb b/knife/spec/data/run_context/cookbooks/test/attributes/george.rb
new file mode 100644
index 0000000000..8ea4454c79
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/attributes/george.rb
@@ -0,0 +1 @@
+default[:george] = "washington"
diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb b/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb
new file mode 100644
index 0000000000..5b00553cfe
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/definitions/new_animals.rb
@@ -0,0 +1,9 @@
+define :new_dog, :is_cute => true do
+ dog "#{params[:name]}" do
+ cute params[:is_cute]
+ end
+end
+
+define :new_badger do
+ badger "#{params[:name]}"
+end
diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb b/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb
new file mode 100644
index 0000000000..a49b53348c
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/definitions/new_cat.rb
@@ -0,0 +1,5 @@
+define :new_cat, :is_pretty => true do
+ cat "#{params[:name]}" do
+ pretty_kitty params[:is_pretty]
+ end
+end
diff --git a/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb b/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb
new file mode 100644
index 0000000000..b6a2e53488
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/definitions/test_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-definition')
diff --git a/knife/spec/data/run_context/cookbooks/test/providers/provider.rb b/knife/spec/data/run_context/cookbooks/test/providers/provider.rb
new file mode 100644
index 0000000000..abf0bd24e9
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-provider')
diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/default.rb b/knife/spec/data/run_context/cookbooks/test/recipes/default.rb
new file mode 100644
index 0000000000..d769dc826d
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/recipes/default.rb
@@ -0,0 +1,5 @@
+
+cat "einstein" do
+ pretty_kitty true
+end
+
diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/one.rb b/knife/spec/data/run_context/cookbooks/test/recipes/one.rb
new file mode 100644
index 0000000000..7795cc1d4a
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/recipes/one.rb
@@ -0,0 +1,7 @@
+cat "loulou" do
+ pretty_kitty true
+end
+
+new_cat "birthday" do
+ pretty_kitty false
+end
diff --git a/knife/spec/data/run_context/cookbooks/test/recipes/two.rb b/knife/spec/data/run_context/cookbooks/test/recipes/two.rb
new file mode 100644
index 0000000000..01def1b2ac
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/recipes/two.rb
@@ -0,0 +1,7 @@
+cat "peanut" do
+ pretty_kitty true
+end
+
+new_cat "fat peanut" do
+ pretty_kitty false
+end
diff --git a/knife/spec/data/run_context/cookbooks/test/resources/resource.rb b/knife/spec/data/run_context/cookbooks/test/resources/resource.rb
new file mode 100644
index 0000000000..54c99d2fb8
--- /dev/null
+++ b/knife/spec/data/run_context/cookbooks/test/resources/resource.rb
@@ -0,0 +1,3 @@
+unified_mode true
+
+LibraryLoadOrder.record('test-resource')
diff --git a/knife/spec/data/run_context/nodes/run_context.rb b/knife/spec/data/run_context/nodes/run_context.rb
new file mode 100644
index 0000000000..076d21ab89
--- /dev/null
+++ b/knife/spec/data/run_context/nodes/run_context.rb
@@ -0,0 +1,5 @@
+##
+# Nodes should have a unique name
+##
+name "compile"
+run_list "test", "test::one", "test::two"
diff --git a/knife/spec/data/sample_msu1.xml b/knife/spec/data/sample_msu1.xml
new file mode 100644
index 0000000000..cc68dbf060
--- /dev/null
+++ b/knife/spec/data/sample_msu1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend">
+ <servicing>
+ <package action="install">
+ <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/>
+ <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" />
+ </package>
+ </servicing>
+</unattend>
+
diff --git a/knife/spec/data/sample_msu2.xml b/knife/spec/data/sample_msu2.xml
new file mode 100644
index 0000000000..6f95e04f93
--- /dev/null
+++ b/knife/spec/data/sample_msu2.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend">
+ <servicing>
+ <package action="install">
+ <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/>
+ <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" />
+ </package>
+ <package action="install">
+ <assemblyIdentity name="Package_for_abc" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/>
+ <source location="%configsetroot%\abc.CAB" />
+ </package>
+ </servicing>
+</unattend>
+
diff --git a/knife/spec/data/sample_msu3.xml b/knife/spec/data/sample_msu3.xml
new file mode 100644
index 0000000000..0ef09da206
--- /dev/null
+++ b/knife/spec/data/sample_msu3.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend">
+ <servicing>
+ <package action="install">
+ <assemblyIdentity name="Package_for_KB2859903" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/>
+ <source location="%configsetroot%\IE10-Windows6.1-KB2859903-x86.CAB" />
+ </package>
+ </servicing>
+ <servicing>
+ <package action="install">
+ <assemblyIdentity name="Package_for_abc" version="10.2.1.0" language="neutral" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35"/>
+ <source location="%configsetroot%\abc.CAB" />
+ </package>
+ </servicing>
+</unattend>
+
diff --git a/knife/spec/data/search_queries_to_transform.txt b/knife/spec/data/search_queries_to_transform.txt
new file mode 100644
index 0000000000..4a05d5405e
--- /dev/null
+++ b/knife/spec/data/search_queries_to_transform.txt
@@ -0,0 +1,98 @@
+afield:[* TO *]
+content:afield__=__*
+
+afield:[a TO *]
+content:[afield__=__a TO afield__=__\ufff0]
+
+afield:[* TO b]
+content:[afield__=__ TO afield__=__b]
+
+*:*
+*:*
+
+role:mon
+content:role__=__mon
+
+role:mon AND role:prod
+content:role__=__mon AND content:role__=__prod
+
+run_list:role\[rubberband\] AND run_list:role\[whale\]
+content:run_list__=__role\[rubberband\] AND content:run_list__=__role\[whale\]
+
+sharable_server:[* TO *]
+content:sharable_server__=__*
+
+run_list:role\[nfs_server\] AND sharable_server:[* TO *]
+content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__*
+
+run_list:role\[nfs_server\] AND sharable_server:[* TO *]
+content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__*
+
+(role:prod AND x_y:true)
+(content:role__=__prod AND content:x_y__=__true)
+
+hostname:[* TO *] AND role:prod
+content:hostname__=__* AND content:role__=__prod
+
+role:t_mem AND role:prod NOT hostname:ip-1-2-3-4
+content:role__=__t_mem AND content:role__=__prod NOT content:hostname__=__ip-1-2-3-4
+
+ohai_time:[1234.567 TO *]
+content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0]
+
+ohai_time:{1234.567 TO *}
+content:{ohai_time__=__1234.567 TO ohai_time__=__\ufff0}
+
+ohai_time:[* TO baz]
+content:[ohai_time__=__ TO ohai_time__=__baz]
+
+ohai_time:{* TO baz}
+content:{ohai_time__=__ TO ohai_time__=__baz}
+
+tags:apples*.for.eating.com
+content:tags__=__apples*.for.eating.com
+
+role:safe AND ohai_time:[1234.567 TO *] AND whiz_bang:x5
+content:role__=__safe AND content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] AND content:whiz_bang__=__x5
+
+role:safe AND ohai_time:[* TO 1234.567] AND whiz_bang:x5
+content:role__=__safe AND content:[ohai_time__=__ TO ohai_time__=__1234.567] AND content:whiz_bang__=__x5
+
+animal:[ape TO zebra]
+content:[animal__=__ape TO animal__=__zebra]
+
+animal:{ape TO zebra}
+content:{animal__=__ape TO animal__=__zebra}
+
+((value:[1 TO 3] OR nested_b1_a2_a3:B1_A2_A3-c) OR value:[5 TO *])
+((content:[value__=__1 TO value__=__3] OR content:nested_b1_a2_a3__=__B1_A2_A3-c) OR content:[value__=__5 TO value__=__\ufff0])
+
+((value:{1 TO 3} OR value:{1 TO 3}) OR run_list:recipe\[alpha\])
+((content:{value__=__1 TO value__=__3} OR content:{value__=__1 TO value__=__3}) OR content:run_list__=__recipe\[alpha\])
+
+words:"one two three"
+content:"words__=__one two three"
+
+words:"one \"two\" three"
+content:"words__=__one \"two\" three"
+
+words:"\"one two\" three"
+content:"words__=__\"one two\" three"
+
+words:"one two \"three\""
+content:"words__=__one two \"three\""
+
+words:"one two \"three\"" OR words:"\"one two\" three" AND words:"one \"two\" three"
+content:"words__=__one two \"three\"" OR content:"words__=__\"one two\" three" AND content:"words__=__one \"two\" three"
+
+words:\"*
+content:words__=__\"*
+
+-version:0.9.12
+-content:version__=__0.9.12
+
+!version:0.9.12
+NOT content:version__=__0.9.12
+
+ec2:*
+content:ec2__=__*
diff --git a/knife/spec/data/shef-config.rb b/knife/spec/data/shef-config.rb
new file mode 100644
index 0000000000..1ace5efd21
--- /dev/null
+++ b/knife/spec/data/shef-config.rb
@@ -0,0 +1,11 @@
+ohai[:disabled_plugins] << "darwin::system_profiler" << "darwin::kernel" << "darwin::ssh_host_key" << "network_listeners"
+ohai[:disabled_plugins] << "virtualization" << "darwin::virtualization"
+ohai[:disabled_plugins] << "darwin::uptime" << "darwin::filesystem" << "dmi" << "languages" << "perl" << "python" << "java"
+ohai[:disabled_plugins] << "linux::block_device" << "linux::kernel" << "linux::ssh_host_key" << "linux::virtualization"
+ohai[:disabled_plugins] << "linux::cpu" << "linux::memory" << "ec2" << "rackspace" << "eucalyptus" << "ip_scopes"
+ohai[:disabled_plugins] << "solaris2::cpu" << "solaris2::dmi" << "solaris2::filesystem" << "solaris2::kernel"
+ohai[:disabled_plugins] << "solaris2::virtualization" << "solaris2::zpools"
+ohai[:disabled_plugins] << "c" << "php" << "mono" << "groovy" << "lua" << "erlang"
+ohai[:disabled_plugins] << "kernel" << "linux::filesystem" << "ruby"
+chef_repo_path __dir__
+cookbook_path "#{chef_repo_path}/cookbooks"
diff --git a/knife/spec/data/snap_package/async_result_success.json b/knife/spec/data/snap_package/async_result_success.json
new file mode 100644
index 0000000000..09781ad5bd
--- /dev/null
+++ b/knife/spec/data/snap_package/async_result_success.json
@@ -0,0 +1,6 @@
+{
+ "type": "async",
+ "status-code": 202,
+ "status": "Accepted",
+ "change": "401"
+}
diff --git a/knife/spec/data/snap_package/change_id_result.json b/knife/spec/data/snap_package/change_id_result.json
new file mode 100644
index 0000000000..cd823d7cbc
--- /dev/null
+++ b/knife/spec/data/snap_package/change_id_result.json
@@ -0,0 +1,175 @@
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": {
+ "id": "15",
+ "kind": "install-snap",
+ "summary": "Install snap \"hello\"",
+ "status": "Done",
+ "tasks": [{
+ "id": "165",
+ "kind": "prerequisites",
+ "summary": "Ensure prerequisites for \"hello\" are available",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.22104314Z",
+ "ready-time": "2018-09-22T20:25:25.231090966Z"
+ }, {
+ "id": "166",
+ "kind": "download-snap",
+ "summary": "Download snap \"hello\" (20) from channel \"stable\"",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221070859Z",
+ "ready-time": "2018-09-22T20:25:25.24321909Z"
+ }, {
+ "id": "167",
+ "kind": "validate-snap",
+ "summary": "Fetch and check assertions for snap \"hello\" (20)",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221080163Z",
+ "ready-time": "2018-09-22T20:25:25.55308904Z"
+ }, {
+ "id": "168",
+ "kind": "mount-snap",
+ "summary": "Mount snap \"hello\" (20)",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221082984Z",
+ "ready-time": "2018-09-22T20:25:25.782452658Z"
+ }, {
+ "id": "169",
+ "kind": "copy-snap-data",
+ "summary": "Copy snap \"hello\" data",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221085677Z",
+ "ready-time": "2018-09-22T20:25:25.790911883Z"
+ }, {
+ "id": "170",
+ "kind": "setup-profiles",
+ "summary": "Setup snap \"hello\" (20) security profiles",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221088261Z",
+ "ready-time": "2018-09-22T20:25:25.972796111Z"
+ }, {
+ "id": "171",
+ "kind": "link-snap",
+ "summary": "Make snap \"hello\" (20) available to the system",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221090669Z",
+ "ready-time": "2018-09-22T20:25:25.986931331Z"
+ }, {
+ "id": "172",
+ "kind": "auto-connect",
+ "summary": "Automatically connect eligible plugs and slots of snap \"hello\"",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221093357Z",
+ "ready-time": "2018-09-22T20:25:25.996914144Z"
+ }, {
+ "id": "173",
+ "kind": "set-auto-aliases",
+ "summary": "Set automatic aliases for snap \"hello\"",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221097651Z",
+ "ready-time": "2018-09-22T20:25:26.009155888Z"
+ }, {
+ "id": "174",
+ "kind": "setup-aliases",
+ "summary": "Setup snap \"hello\" aliases",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221100379Z",
+ "ready-time": "2018-09-22T20:25:26.021062388Z"
+ }, {
+ "id": "175",
+ "kind": "run-hook",
+ "summary": "Run install hook of \"hello\" snap if present",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221103116Z",
+ "ready-time": "2018-09-22T20:25:26.031383884Z"
+ }, {
+ "id": "176",
+ "kind": "start-snap-services",
+ "summary": "Start snap \"hello\" (20) services",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221110251Z",
+ "ready-time": "2018-09-22T20:25:26.039564637Z"
+ }, {
+ "id": "177",
+ "kind": "run-hook",
+ "summary": "Run configure hook of \"hello\" snap if present",
+ "status": "Done",
+ "progress": {
+ "label": "",
+ "done": 1,
+ "total": 1
+ },
+ "spawn-time": "2018-09-22T20:25:25.221115952Z",
+ "ready-time": "2018-09-22T20:25:26.05069451Z"
+ }
+ ],
+ "ready": true,
+ "spawn-time": "2018-09-22T20:25:25.221130149Z",
+ "ready-time": "2018-09-22T20:25:26.050696298Z",
+ "data": {
+ "snap-names": ["hello"]
+ }
+ }
+}
diff --git a/knife/spec/data/snap_package/find_result_failure.json b/knife/spec/data/snap_package/find_result_failure.json
new file mode 100644
index 0000000000..ec0d82a3b8
--- /dev/null
+++ b/knife/spec/data/snap_package/find_result_failure.json
@@ -0,0 +1,10 @@
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "snap not found",
+ "kind": "snap-not-found",
+ "value": "hello2"
+ }
+}
diff --git a/knife/spec/data/snap_package/find_result_success.json b/knife/spec/data/snap_package/find_result_success.json
new file mode 100644
index 0000000000..f19f24dcef
--- /dev/null
+++ b/knife/spec/data/snap_package/find_result_success.json
@@ -0,0 +1,70 @@
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": [{
+ "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
+ "title": "hello",
+ "summary": "GNU Hello, the \"hello world\" snap",
+ "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
+ "download-size": 65536,
+ "name": "hello",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical",
+ "validation": "verified"
+ },
+ "developer": "canonical",
+ "status": "available",
+ "type": "app",
+ "version": "2.10",
+ "channel": "stable",
+ "ignore-validation": false,
+ "revision": "20",
+ "confinement": "strict",
+ "private": false,
+ "devmode": false,
+ "jailmode": false,
+ "contact": "mailto:snaps@canonical.com",
+ "license": "GPL-3.0",
+ "channels": {
+ "latest/beta": {
+ "revision": "29",
+ "confinement": "strict",
+ "version": "2.10.1",
+ "channel": "beta",
+ "epoch": "0",
+ "size": 65536
+ },
+ "latest/candidate": {
+ "revision": "20",
+ "confinement": "strict",
+ "version": "2.10",
+ "channel": "candidate",
+ "epoch": "0",
+ "size": 65536
+ },
+ "latest/edge": {
+ "revision": "34",
+ "confinement": "strict",
+ "version": "2.10.42",
+ "channel": "edge",
+ "epoch": "0",
+ "size": 65536
+ },
+ "latest/stable": {
+ "revision": "20",
+ "confinement": "strict",
+ "version": "2.10",
+ "channel": "stable",
+ "epoch": "0",
+ "size": 65536
+ }
+ },
+ "tracks": ["latest"]
+ }
+ ],
+ "sources": ["store"],
+ "suggested-currency": "USD"
+}
diff --git a/knife/spec/data/snap_package/get_by_name_result_failure.json b/knife/spec/data/snap_package/get_by_name_result_failure.json
new file mode 100644
index 0000000000..c8c1bb7342
--- /dev/null
+++ b/knife/spec/data/snap_package/get_by_name_result_failure.json
@@ -0,0 +1,10 @@
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "snap not installed",
+ "kind": "snap-not-found",
+ "value": "aws-cliasdfasdf"
+ }
+}
diff --git a/knife/spec/data/snap_package/get_by_name_result_success.json b/knife/spec/data/snap_package/get_by_name_result_success.json
new file mode 100644
index 0000000000..05517362ab
--- /dev/null
+++ b/knife/spec/data/snap_package/get_by_name_result_success.json
@@ -0,0 +1,38 @@
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": {
+ "id": "CRrJViJiSuDcCkU31G0xpNRVNaj4P960",
+ "summary": "Universal Command Line Interface for Amazon Web Services",
+ "description": "This package provides a unified command line interface to Amazon Web\nServices.\n",
+ "installed-size": 15851520,
+ "name": "aws-cli",
+ "publisher": {
+ "id": "S7iQ7mKDXBDliQqRcgefvc2TKXIH9pYk",
+ "username": "aws",
+ "display-name": "Amazon Web Services",
+ "validation": "verified"
+ },
+ "developer": "aws",
+ "status": "active",
+ "type": "app",
+ "version": "1.15.71",
+ "channel": "",
+ "tracking-channel": "stable",
+ "ignore-validation": false,
+ "revision": "135",
+ "confinement": "classic",
+ "private": false,
+ "devmode": false,
+ "jailmode": false,
+ "apps": [{
+ "snap": "aws-cli",
+ "name": "aws"
+ }
+ ],
+ "contact": "",
+ "mounted-from": "/var/lib/snapd/snaps/aws-cli_135.snap",
+ "install-date": "2018-09-17T20:39:38.516Z"
+ }
+}
diff --git a/knife/spec/data/snap_package/get_conf_success.json b/knife/spec/data/snap_package/get_conf_success.json
new file mode 100644
index 0000000000..e83ffbfbe3
--- /dev/null
+++ b/knife/spec/data/snap_package/get_conf_success.json
@@ -0,0 +1,10 @@
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": {
+ "address": "0.0.0.0",
+ "allow-privileged": true,
+ "anonymous-auth": false
+ }
+}
diff --git a/knife/spec/data/snap_package/result_failure.json b/knife/spec/data/snap_package/result_failure.json
new file mode 100644
index 0000000000..e65120ad33
--- /dev/null
+++ b/knife/spec/data/snap_package/result_failure.json
@@ -0,0 +1,9 @@
+{
+ "type": "error",
+ "status-code": 401,
+ "status": "Unauthorized",
+ "result": {
+ "message": "access denied",
+ "kind": "login-required"
+ }
+}
diff --git a/knife/spec/data/ssl/5e707473.0 b/knife/spec/data/ssl/5e707473.0
new file mode 100644
index 0000000000..5c5cf87e11
--- /dev/null
+++ b/knife/spec/data/ssl/5e707473.0
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAlGgAwIBAgIJANlevg7kzqvpMA0GCSqGSIb3DQEBBQUAMFcxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UECxMVU25ha2VvaWwg
+Q2VydGlmaWNhdGVzMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMDkxMjE5MTkxODUy
+WhcNMTAwMTE4MTkxODUyWjBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQxHjAcBgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJ
+bG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhm9En1DL3aC4H
+j5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JEhQhLXStjhDr4p/IrARnZ8shy0MA4
+wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAoM9b/4pd0FqgRW1UbhvqQDzkWmVyK
+Tz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQABo4G7MIG4MB0GA1UdDgQWBBTmAcyA
+CqQblJ1L4sOIzmkdIAtY6jCBiAYDVR0jBIGAMH6AFOYBzIAKpBuUnUviw4jOaR0g
+C1jqoVukWTBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxHjAc
+BgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJbG9jYWxob3N0
+ggkA2V6+DuTOq+kwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBe4f9R
+s0g5GCFekabzl9AHvIn4ITxenvuyaNX9f2BJbdgoD03wlGycBxjbC57RjFVfetu7
+mtUYuJSx7iojBSC+LzotGptrG9d2BxrWOKBfF2K+dyoIG8kZL5aLfS0be6Cc5O3c
+L/IPadJhBu/EfyGI2vL1l8GspXdOxaFzHprpgA==
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/ssl/binary/chef-rspec-der.cert b/knife/spec/data/ssl/binary/chef-rspec-der.cert
new file mode 100644
index 0000000000..e49df6252a
--- /dev/null
+++ b/knife/spec/data/ssl/binary/chef-rspec-der.cert
Binary files differ
diff --git a/knife/spec/data/ssl/binary/chef-rspec-der.key b/knife/spec/data/ssl/binary/chef-rspec-der.key
new file mode 100644
index 0000000000..d8adadc5c9
--- /dev/null
+++ b/knife/spec/data/ssl/binary/chef-rspec-der.key
Binary files differ
diff --git a/knife/spec/data/ssl/chef-rspec.cert b/knife/spec/data/ssl/chef-rspec.cert
new file mode 100644
index 0000000000..9215a39362
--- /dev/null
+++ b/knife/spec/data/ssl/chef-rspec.cert
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIJAOEDC5RFoEUZMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEN
+MAsGA1UEChMEQ2hlZjETMBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2Fs
+bGlzdGVjMR4wHAYJKoZIhvcNAQkBFg9kYW5Ab3BzY29kZS5jb20wHhcNMjAwODEy
+MTEyOTUxWhcNMzAwODEwMTEyOTUxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzAR
+BgNVBAsTCmRldmVsb3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3
+DQEJARYPZGFuQG9wc2NvZGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdTnEdjlGGG
+SxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bLAHEHS2if
+1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/rMFHf+yoY
+guuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8nPMiiHpoO
+pgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xFSFwGUUA9
+IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABo4H0MIHxMAwGA1UdEwQFMAMBAf8w
+HQYDVR0OBBYEFLzxnG28b4VO7VT5UUDjt9/PBK1uMIHBBgNVHSMEgbkwgbaAFLzx
+nG28b4VO7VT5UUDjt9/PBK1uoYGSpIGPMIGMMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTENMAsGA1UEChMEQ2hlZjET
+MBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2FsbGlzdGVjMR4wHAYJKoZI
+hvcNAQkBFg9kYW5Ab3BzY29kZS5jb22CCQDhAwuURaBFGTANBgkqhkiG9w0BAQsF
+AAOCAQEAoTCRzUbZOPrJdpd928fQPt/HsYODDmgWQJIPucdLKmlY5wb3zSc1B5H6
+zBUmSFylnDLKhZlO+gojBuQDhr2h9bMXn8RvE0A/Dqq9XcNsblMn0HjoJmjv/x8Q
+wFfrwIWj1wHoGHromkJaedWCRGlVW1oLZa99JmQCNee4bjcwkK2H0xRqX8STbqJV
+z+uEBf5fDc4EioULwfxa6B15XDD09k14uHtlV6JwTmahDjpdKV/ICKBi/WN0aQg1
+9k7OAkW5cnzmS6uFFjrvWuNy4ey4j1c4U5GogxEgCsattshHNO+icWRCN2gPg2Nx
+SKEXNcUA4jRWGF7PRgY/oyjULObFqw==
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/ssl/chef-rspec.key b/knife/spec/data/ssl/chef-rspec.key
new file mode 100644
index 0000000000..29aaecf2a6
--- /dev/null
+++ b/knife/spec/data/ssl/chef-rspec.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdT
+nEdjlGGGSxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bL
+AHEHS2if1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/r
+MFHf+yoYguuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8n
+PMiiHpoOpgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xF
+SFwGUUA9IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABAoIBAQC+hddKaA4se+sL
+4QaSoj+mwtypXjZHnv/+sJj8IjY+IMGbzmJmqzLX6VbB+gCMoMTySwmS54NxFTHp
+LPwUz0vFTUdzecHpzg9mDAU5HUYYA1ZNbhq2R2JvlW16j1b9NnOpse77fLbFCPgK
+b8TOqnmheot2hkjEipGN2Z7o5gYaz1/3PtolkP1ypCTG6Bh7V3ohBLBIEdjA552o
+HNGe3t6PpvoNtBqaeb/j/SAOvg+8DGF1WQtE+5Y1koSlhABYWkHzHC1fHAzRMSHH
+ZMfKOQNusRgBRNJabdVqkuTbvyRCQEb2YGQxPPYV2C+AxAlh3APeYTg90vUqAq/3
+ivNdilcBAoGBAOLELc0mcTftDbIMWVnrzAGAJOCMz3FkwGcV8nqNeA3R77e3pWL2
+5+bKadWQGjjpR3ZEYt/RxHsoGCW3NtM44icxqVCTPW/unp2xqadjuvcsKrxk+1wD
+OdvVrwcd/N+KzgXO+Hm7xbV/loFms3ueGfCRbOueQyP4dj9MyOBGlO2hAoGBANzQ
+u8IrZBG0DL8YFdmjw4YWUENIOtABPU1qHo/sugTQjI9K3/E3LA7aaGnl2P//1tao
+SR/aP/To90H6D989/JomhkEKKA+DyL1sRL1NMdtWwrKdEq32W8fUN0JEA+Q1FMsd
+Hk6Ix+KrZVg9cTb9HoGikDxeHW3pPKDWaEkWIQLLAoGAD13N4L3/JBQLPop5r487
+9soRNao1EHEMXK/vC4D0prMYNHHcYjVrB4el3lPygvLD5e7CaHpVfyb7Y+rjazLK
+mG9UEuK3YhNgaj00yuQGMmOqzbNmGRka3ZvATZIppZhJV7lruwwPXLo1n7Uu6myP
+Q28HW3wQ/qoCkU2JuzDtPKECgYBUrYcTEuixEUbCEU5vw6k7RltJMe27zn3frg5C
+Sxmatw7v9Fqkee/fUkowMgBhS47rimVgXaWhGaWYG3jytyajRpq9XlO2f2b/nQFP
+RscTwdWwASQkqhDQNMVsGAEWBnUO3v+8Rh/BANFAYW+FEtQcCmcdf0nx2DtzwkUD
+ogTOuQKBgCbEg+/ND/p8xKwY9LtjLKnrQSL5tSH/7prhLJvVVdW7FMRfKSp1t2xc
+kfJFqO1Lcf2j7hiclval3xDoWUretNQ5379T0Ob30WuIomSfeqcxJjCUtyN3fUqr
+z/QG9dk/23OOYJhRgAmttBDqpk5uB5mOQgSftdELNyw0EOyNIBfZ
+-----END RSA PRIVATE KEY-----
diff --git a/knife/spec/data/ssl/key.pem b/knife/spec/data/ssl/key.pem
new file mode 100644
index 0000000000..50b8fd83c8
--- /dev/null
+++ b/knife/spec/data/ssl/key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDhm9En1DL3aC4Hj5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JE
+hQhLXStjhDr4p/IrARnZ8shy0MA4wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAo
+M9b/4pd0FqgRW1UbhvqQDzkWmVyKTz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQAB
+AoGAGicC1tgdVFqqQ0wd3a14DXzH3SkTkjWPSdvI2pX6hLvCptQWRLUbIglZ1z5j
+y6FETEHjakVfgRe7wJhyddOQS3eeVt/aK0xBHz/JiJuIF+NzbJT9t01nPV21abYU
+lWIhWV8Ja39a5LKV6hee0TTYdAub7BVQ95kwrqMqRcDoXHECQQDxpAgq925Cmlz1
+0Q1WZq2A/o8oqPvPS1FulPK2OgyOyQSK+DdcK2xUKGWMn0m9fDLLzj/pe/H3dN1I
+b8Z/iiWrAkEA7wPlesZX3GzfqQLd6GYGBa4IdrV5dHdeoCCVRnkFr06KjcqpAhg1
+7i0T9frSC5EfRCfbGNgo4eutT9+D7HJhuQJAZeDBrNPbQetxDBbSp73sovkwhHUS
+jah0scnMtvWse7rW1nymYo7QQn8xqWMzJNerVvAjVB50ut8juLmfmAA3twJAQy9/
+NBHI5Mcd365Unlz/WF1hN60vZNOhH7XJADRIqsyTGeRbuaEAl+DH+Z71qBa1CT2C
+0usAIvFSmF8mADLu0QJAHSSh6zLNInvkhDjYAmEu3oeFQgQ4Rp7oiMaBZ6VVuOMo
+4GU9CA18iI75NaO7FOfquJPkIJ0li0xadVofUpaJcg==
+-----END RSA PRIVATE KEY-----
diff --git a/knife/spec/data/ssl/private_key.pem b/knife/spec/data/ssl/private_key.pem
new file mode 100644
index 0000000000..b6636604a8
--- /dev/null
+++ b/knife/spec/data/ssl/private_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----
diff --git a/knife/spec/data/ssl/private_key_with_whitespace.pem b/knife/spec/data/ssl/private_key_with_whitespace.pem
new file mode 100644
index 0000000000..b393a3f13d
--- /dev/null
+++ b/knife/spec/data/ssl/private_key_with_whitespace.pem
@@ -0,0 +1,32 @@
+
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----
+
+
+
diff --git a/knife/spec/data/standalone_cookbook/Gemfile b/knife/spec/data/standalone_cookbook/Gemfile
new file mode 100644
index 0000000000..9c961848d8
--- /dev/null
+++ b/knife/spec/data/standalone_cookbook/Gemfile
@@ -0,0 +1 @@
+source "https://rubygems.org/" \ No newline at end of file
diff --git a/knife/spec/data/standalone_cookbook/chefignore b/knife/spec/data/standalone_cookbook/chefignore
new file mode 100644
index 0000000000..cd18e699c1
--- /dev/null
+++ b/knife/spec/data/standalone_cookbook/chefignore
@@ -0,0 +1,9 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme.rb
+ # comments can be indented
+ignored
+vendor/bundle/*
diff --git a/knife/spec/data/standalone_cookbook/recipes/default.rb b/knife/spec/data/standalone_cookbook/recipes/default.rb
new file mode 100644
index 0000000000..c2fa53be32
--- /dev/null
+++ b/knife/spec/data/standalone_cookbook/recipes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing ot see here
+# \ No newline at end of file
diff --git a/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb b/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb
new file mode 100644
index 0000000000..3b992add1a
--- /dev/null
+++ b/knife/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb
@@ -0,0 +1 @@
+# This is a dummy ruby file \ No newline at end of file
diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt b/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt
new file mode 100644
index 0000000000..f476ccd704
--- /dev/null
+++ b/knife/spec/data/templates/chef-seattle20160930-4388-1crv7ef.txt
@@ -0,0 +1 @@
+Do do do do, do do do do, do do do do, do do do do \ No newline at end of file
diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt b/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt
new file mode 100644
index 0000000000..f476ccd704
--- /dev/null
+++ b/knife/spec/data/templates/chef-seattle20160930-4388-jjfoae.txt
@@ -0,0 +1 @@
+Do do do do, do do do do, do do do do, do do do do \ No newline at end of file
diff --git a/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt b/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt
new file mode 100644
index 0000000000..f476ccd704
--- /dev/null
+++ b/knife/spec/data/templates/chef-seattle20160930-4388-umeq2c.txt
@@ -0,0 +1 @@
+Do do do do, do do do do, do do do do, do do do do \ No newline at end of file
diff --git a/knife/spec/data/templates/failed.erb b/knife/spec/data/templates/failed.erb
new file mode 100644
index 0000000000..e077ac8684
--- /dev/null
+++ b/knife/spec/data/templates/failed.erb
@@ -0,0 +1,5 @@
+This is a template
+
+Which includes some content
+
+And will fail <%= nil[] %>
diff --git a/knife/spec/data/templates/seattle.txt b/knife/spec/data/templates/seattle.txt
new file mode 100644
index 0000000000..19f6290939
--- /dev/null
+++ b/knife/spec/data/templates/seattle.txt
@@ -0,0 +1 @@
+Seattle is a great town. Next time you visit, you should buy me a beer. \ No newline at end of file
diff --git a/knife/spec/data/trusted_certs/example.crt b/knife/spec/data/trusted_certs/example.crt
new file mode 100644
index 0000000000..832aebe2ec
--- /dev/null
+++ b/knife/spec/data/trusted_certs/example.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnoCCQDihI8kxGYTFTANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRAwDgYDVQQKEwdZb3VD
+b3JwMRMwEQYDVQQLEwpPcGVyYXRpb25zMRYwFAYDVQQDEw1leGFtcGxlLmxvY2Fs
+MR0wGwYJKoZIhvcNAQkBFg5tZUBleGFtcGxlLmNvbTAeFw0xMzEwMTcxODAxMzVa
+Fw0yMzEwMTUxODAxMzVaMIGKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO
+BgNVBAcTB1NlYXR0bGUxEDAOBgNVBAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJh
+dGlvbnMxFjAUBgNVBAMTDWV4YW1wbGUubG9jYWwxHTAbBgkqhkiG9w0BCQEWDm1l
+QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyKBo
+U+Bdni0xZK/NCzdLdi2X+TyW5eahbYMx+r1GDcVqCICvrthBCVLVFsQ8rvOHwTPi
+AxQJGxb9TLSXRgXQSlH6FLjIUceuOtpan3qYVJ1v7AxY4DgNvYBpbtJz5MQedJnT
+g2F+rXzkwaD6CWBqWHeGU0oP3r7bq1AMD6XEsK2w2/zHtG7TEnL45ARv1PsyrU5M
+ZAW/XyoMyq1k2Lpv7YR5kAvTq1+4RSt/it2RFE7R0AVbaQ0MeAnllfySiHHHlaOT
+FVd/qPSiGISxsUmmzA3Z08+0sfJwkrnJXbLscCBYndd7gMGgtczGjJtul0Ch3GFa
+/Pn5McjwF272+usJ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCzPePWifWNECsG
+nL8on1AtFMkczE1/pdRS4YUl/Tc926MpezptSja8rL31+4Bom37/wYPG7HygtAQl
+R4FHpAtuqJKPOfjUmDNsIXRFnytrnflTpctDu/Nbj4PDCy01k/sTDUQt+s+lEBL8
+M8ArmfLZ8PCfAwnXmJQ5rggDFKqegjt6z1RsSglbMiASE7+KkpBnzaqH6fET6IQz
+WgAjv6WdRfwgfJjOTSX4XMpCSet9KaWmXExKrxiVng2Uu6E+ShVAyKaGMuc1B7VA
+oxnnVaVapFv5lOWucQr4KkC7EgaUZnyt8duOc8+Yvd+y3Xd2dcHUnmegRxly4jRV
+/lXbFAUb
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/trusted_certs/example_no_cn.crt b/knife/spec/data/trusted_certs/example_no_cn.crt
new file mode 100644
index 0000000000..6b42b40d99
--- /dev/null
+++ b/knife/spec/data/trusted_certs/example_no_cn.crt
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGPzCCBCegAwIBAgIJAKwtLqBeqNzfMA0GCSqGSIb3DQEBBQUAMHIxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEQMA4GA1UEChMH
+WW91Q29ycDETMBEGA1UECxMKT3BlcmF0aW9uczEdMBsGCSqGSIb3DQEJARYObWVA
+ZXhhbXBsZS5jb20wHhcNMTYxMDMxMTkxMzQ2WhcNMjYxMDI5MTkxMzQ2WjByMQsw
+CQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxEDAOBgNV
+BAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJhdGlvbnMxHTAbBgkqhkiG9w0BCQEW
+Dm1lQGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
+s1OiWnMV3shxVccqzenDBww5rSou9Ab/VqujKisJ54dXyHukYMxh9MJwlRDsy0FB
+uKRAyNfhM43hSMYhtF7NS//D1lI/LDvIQkBaH8R834bvK102Avmsx7zKPOo/CUkd
+g7uuL2eRzRszEuAREH1E7/PpTj11CjirG9i7FlbKj7vDA1Nqvtb0kHdiQuH2Cojy
+Uf1uVFyE5UliFXtePDrxpOAfJUbcSdOLsK8olKHGCb0cfN/tCfbyEY8rHGsAUK2A
+afuHRTR7pRQwfqJ5EK3DBbbFz+GSi+9zWFOudfqTsczS/HtpMbF8HBwqBAa+mpU/
+UjmhpTYQ+rgVtWtEcttboeK6jvFBFLyQ6VRcrDi/8lmAnm1Q+RZk5g3GwZMhIMNU
+5XQZf6jsUsIFBuOaRyLn9dXkgyO7gOy8n8Yw+YdIFt29kaqZ6pu9kpS0jquxzSKj
+MVS4OrThLwgazfQu/BlOvJpQfcNPI/VP9c41yHKpeoIh6oxNDc/212/wwgwPgD83
+8YXddupaSuE++h9Z10CCZgwux8deTlMjzETIMiIo8R3KV0pJgZ11akeJ8USr+QQ2
++fO/GdpNUa5nNTgF3t4zTF3DPToqj6KDgxLhUdXopF1hLYgwr8FKOtn9KXP+I0hz
+hWzZoX9gwFLEPrUy265Gpw8TVTmNuSiiZtgJDSDKTBcCAwEAAaOB1zCB1DAdBgNV
+HQ4EFgQUr5Y6dxhyVxfhwFsEKLDIXxQ2zBswgaQGA1UdIwSBnDCBmYAUr5Y6dxhy
+VxfhwFsEKLDIXxQ2zBuhdqR0MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQ
+MA4GA1UEBxMHU2VhdHRsZTEQMA4GA1UEChMHWW91Q29ycDETMBEGA1UECxMKT3Bl
+cmF0aW9uczEdMBsGCSqGSIb3DQEJARYObWVAZXhhbXBsZS5jb22CCQCsLS6gXqjc
+3zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQBYXgqXAnocH6i8o47c
+BZPMGO9y4LCB4YNIrZPKRNFvRl2aolA5KiBxr6WJp1iczxVA4lCmXU1LGfvRPHec
+nHtVax3+Q1JCZhBSv/txQTjgn72qoJyCsPmjyWifbE1jFdRj0g74/Eu/0ku3L0vV
+jTlqzJXQIzRkQm+Y5OrZo92tXaOgO+C0qdd6gaEaIIya6bzrBpW95NtVymhXi2Qf
+7G7Z/yw8XhoQiDJaPHF6XavC3dYvi51cehnPR4E6Jok23kbJEe3Qw5Yh747JjSsS
+Sz07CKqTFcFjHI2f1sFdDjw34lj5mtOf3pEpRGGmvzkF8zm/sVQQ2rCKnqEe7zPy
+Bg9guzVpORG+g76hGFZcYnz78LLNrIYcuYoLcbbZh404wjmifVKO5OC1dRgmJTuc
+VnJe92568Y9cUAjrLztxp5gwXgYUllsXweJ2UGiHxSBqUfCCGG5vK5sYs52HR6wJ
+wRSvwk/VHifYPxJ54RRB51ebYjmD1j41tRseHdFq21qpXSvr9DFLUJBvdN9zA/6t
+xCBlXAdYxD0n0/bruUYNoXBeMhLp+WKSAQvTlVIyqoNQCo1OBBzBVNg9otl3jw5d
+1QOhodRqmS5UQAJptuXtk8WN8OZqMCCeogIfdpa5tJG+/fxFML9EvqedS4c05Wf/
+oYdVLVWSjyoA2l4Xb4LdexAgCg==
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/trusted_certs/intermediate.pem b/knife/spec/data/trusted_certs/intermediate.pem
new file mode 100644
index 0000000000..78148b0fcf
--- /dev/null
+++ b/knife/spec/data/trusted_certs/intermediate.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy
+ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh
+qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx
+8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c
+fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK
+gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl
+LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs
+Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G
+A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6
+Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js
+My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo
+dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js
+MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k
+aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf
+BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC
+AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf
+z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M
+2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24
+LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws
+slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh
+v+PMGxmcJcqnBrJT3yOyzxIZow==
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/trusted_certs/opscode.pem b/knife/spec/data/trusted_certs/opscode.pem
new file mode 100644
index 0000000000..e421a4e6e9
--- /dev/null
+++ b/knife/spec/data/trusted_certs/opscode.pem
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg
+U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83
+nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd
+KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f
+/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX
+kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0
+/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY
+aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6
+Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1
+oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD
+QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh
+xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB
+CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl
+5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA
+8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC
+2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit
+c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0
+j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
+aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN
+MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
+bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j
+MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e
+2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA
+tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB
+ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL
+TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM
+ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP
+gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d
+pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF
+oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g
+K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At
+oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG
+A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho
+dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl
+cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw
+DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ
+zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q
+5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl
+CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD
+eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt
+fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP
+ng==
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/trusted_certs/root.pem b/knife/spec/data/trusted_certs/root.pem
new file mode 100644
index 0000000000..fd4341df26
--- /dev/null
+++ b/knife/spec/data/trusted_certs/root.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/windows_certificates/base64_test.cer b/knife/spec/data/windows_certificates/base64_test.cer
new file mode 100644
index 0000000000..763216f86a
--- /dev/null
+++ b/knife/spec/data/windows_certificates/base64_test.cer
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgIQPAc5ZRAOLL1PCvdo8CoWDTANBgkqhkiG9w0BAQsFADAh
+MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNjEyMDUz
+MVoYDzI4OTkxMjMxMTgzMDAwWjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz
+dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6uZ0V5zobMQm
+JPtZxt4vYtL/As7U6sUBVe9oR9OCYvyIDmpuPcNnrJ26L+iu2W5Kd+840Dv6tHS4
+yOV07bYBU+nVHiCdEn/K7Q5ITv/8uXv39dvlSuSrIn4P+I2vhSQjIy/B94QPD/xE
+dD0WDym1ySY2zQsL4T+yKoaXc5tiBoWBwAdl6/RiXeOm2kBXhIDcW4MLlB0BXtDJ
+l7syB30mOvNsQT6UlymI1q7fpsaPBTo8V3lUWooVVmQciiYquoD34gq7XpdGQOLJ
+V7aSIch1BoQyeQJfWsKzv/R5yzAzw+2zeRf301USunBXwhoac/Sx4xrJxjRknGTs
+7tsCNQUmRQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
+BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j
+b20wHQYDVR0OBBYEFGQa7l1ZPNbhj0s64g1/nyY+xULdMA0GCSqGSIb3DQEBCwUA
+A4IBAQCS3chRs1LUvlq7Hj1kx3CtAhjTE75eEWz8wzWZ+DGppGnMUQg0vwrw7JPd
+s3ODAFor62J97Fmb1sQ9/lSGan0CwBtCMqzHr3hoKbpVR9aFKu/Kt21zE4pEvFgZ
+NVrxOFofmZ072VRdRpRK3RcnV58I02Xyb+5VR8lTbHpIsUOj+i9+y5ZuuOXoRDpI
+G+AdIAfvcBbshPkI62gSFvBUdic0fcMVPZ5rFWaDjW2XFXZ6s/e5mPHNjpGpSZy7
+2y9ku9kB6ywBQXx9U21DBoIDxfprSylQGxtUuXaeCwnRvpT0Ifto5/KaeH4IzJQq
+ZYGdPzBO7WBpk/AsO6buw3kQ9M5h
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/windows_certificates/othertest.cer b/knife/spec/data/windows_certificates/othertest.cer
new file mode 100644
index 0000000000..f4ff69eb08
--- /dev/null
+++ b/knife/spec/data/windows_certificates/othertest.cer
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgIQEavqVx6t6J9MQFWBGNLMAzANBgkqhkiG9w0BAQsFADAo
+MSYwJAYDVQQDDB13d3cuZHVtbXljaGVmdGVzdHNhbm90aGVyLmNvbTAgFw0yMDAz
+MDYxMDA2NDVaGA8yODk5MTIzMTE4MzAwMFowKDEmMCQGA1UEAwwdd3d3LmR1bW15
+Y2hlZnRlc3RzYW5vdGhlci5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDwvGtD46qB4T9Fyt0oEXX2C0zgMQuFPWSutAKWArQCXjDlSD2+a4DvFHvz
+czv8P5P9XZM52cYDIk1nsdG75ZsgzLoiFvk2MtGpH3A1J8tXxJAROZCj8mAwoVpQ
+e791otvHH6LYK4iABHUN3PvuQEfbLTqcPPDFB3jgqV7mkI3wiGxZ5txrjBJo4f8Q
+9ZcqhR33zhp5+eBUH46T7ZY524/CI2dv+1a58LX9y1neqe6Bg0K51Rw0O8Zm/8kD
+31paD75qkq1qWS0BD0OOhVXXzsO1C4OH8L53nAtrwGceTmNRKPgMD5+Rwe6zaw94
+G7m+UeMyEkFJ87Y8+HeSU7F09TcVAgMBAAGjejB4MA4GA1UdDwEB/wQEAwIFoDAd
+BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwKAYDVR0RBCEwH4Idd3d3LmR1
+bW15Y2hlZnRlc3RzYW5vdGhlci5jb20wHQYDVR0OBBYEFLzhpImP5MmDZ0s60syY
+agCrc9NoMA0GCSqGSIb3DQEBCwUAA4IBAQArFhqQQ0U3yzLr47Bcu5WwooULCarW
+caroDqJySdQV+abAMf0aqr8TTl+o9dVyl7Lzt+G4WQgl8Ay7P5dlEv3mGRl7J2PF
+o55PPPmZQkORzwv772d7Nzv9bQemMcYZF38su9+k3mcMh0vxdHoaz39TpUQt5Tz1
+3WC56dflaCbRMq8/fQhYivxfByq3BOf4ghW+BswZMjrV9wMDwKv21ebYAULmxaU3
+/Z0igXN3O4V4RKavONUwRMyRspLFlxm0EEirP+FruQS+/ABIPxuaReYLSqnD9Jqa
+03UdwrqC3bymfKI1zYYToSAqBO8qjim8+cjvdEaXGILW7YBKXNEvyJ20
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/windows_certificates/test.cer b/knife/spec/data/windows_certificates/test.cer
new file mode 100644
index 0000000000..1c358b5035
--- /dev/null
+++ b/knife/spec/data/windows_certificates/test.cer
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgIQX3zqNCJbsKlEvzCz3Z9aNDANBgkqhkiG9w0BAQsFADAh
+MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNTEwMjcw
+NVoYDzIxMjAwMzA1MTAzNzA2WjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz
+dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtuYKDb6woWIH
+HPPOrcVpgJFVxbkjgk+tsYwbIiqR9jtRaKE6nM/awOgn9/dFF4k8KB8Em0sUx7Vq
+J3YhK2N2cAacgP2Frqqf5znpNBBOg968RoZzGx0EiXFvLsqC4y8ggApWTbMXPRk4
+1a7GlpUpSqI3y5cLeEbzwGQKu8I1I+v7P2fTlnJPHarM7sBbL8bieukkFHYu78iV
+u1wpKOCCfs5DTmJu8WN+z1Mar9vyrWMBlt2wBBgNHPz5mcXUzJHTzaI/D9RGgBgF
+V0IkNqISx/IzR62jjj2g6MgTH4G/0mM6O5sxduM4yGmWZNZpVzh0yMLgH619MZlj
+SMQIN3U/SQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
+BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j
+b20wHQYDVR0OBBYEFHwS3gs03m6RcpR+66u4OqGiZdYnMA0GCSqGSIb3DQEBCwUA
+A4IBAQCFHqMjHUfBZahIsKHQIcFCbC1NFh1ZHlJKZzrRBRwRzX19OttHGMyLpDd6
+tM9Ac6LLR8S4QIWg+HF3IrkN+vfTRDZAccj+tIwBRstmdsEz/rAJ79Vb/00mXZQx
+0FPiBDR3hE7On2oo24DU8kJP3v6TrunwtIomVGqrrkwZzvxqyW+WJMB2shGNFw5J
+mKYBiiXsHl4Bi7V4zhXssrLp877sqpNLeXloXBmAlT39SwQTP9ImZaV5R6udqlvo
+Gfgm5PH/WeK6MV3n5ik0v1rS0LwR2o82WlIB6a4iSEbzY3qSLsWOwt8o5QjAVzCR
+tNdbdS3U8nrG73iA2clmF57ARQWC
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/windows_certificates/test.p7b b/knife/spec/data/windows_certificates/test.p7b
new file mode 100644
index 0000000000..bd46d5eccd
--- /dev/null
+++ b/knife/spec/data/windows_certificates/test.p7b
Binary files differ
diff --git a/knife/spec/data/windows_certificates/test.pem b/knife/spec/data/windows_certificates/test.pem
new file mode 100644
index 0000000000..1c358b5035
--- /dev/null
+++ b/knife/spec/data/windows_certificates/test.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgIQX3zqNCJbsKlEvzCz3Z9aNDANBgkqhkiG9w0BAQsFADAh
+MR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVzdHMuY29tMCAXDTIwMDMwNTEwMjcw
+NVoYDzIxMjAwMzA1MTAzNzA2WjAhMR8wHQYDVQQDDBZ3d3cuZHVtbXljaGVmdGVz
+dHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtuYKDb6woWIH
+HPPOrcVpgJFVxbkjgk+tsYwbIiqR9jtRaKE6nM/awOgn9/dFF4k8KB8Em0sUx7Vq
+J3YhK2N2cAacgP2Frqqf5znpNBBOg968RoZzGx0EiXFvLsqC4y8ggApWTbMXPRk4
+1a7GlpUpSqI3y5cLeEbzwGQKu8I1I+v7P2fTlnJPHarM7sBbL8bieukkFHYu78iV
+u1wpKOCCfs5DTmJu8WN+z1Mar9vyrWMBlt2wBBgNHPz5mcXUzJHTzaI/D9RGgBgF
+V0IkNqISx/IzR62jjj2g6MgTH4G/0mM6O5sxduM4yGmWZNZpVzh0yMLgH619MZlj
+SMQIN3U/SQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
+BQUHAwIGCCsGAQUFBwMBMCEGA1UdEQQaMBiCFnd3dy5kdW1teWNoZWZ0ZXN0cy5j
+b20wHQYDVR0OBBYEFHwS3gs03m6RcpR+66u4OqGiZdYnMA0GCSqGSIb3DQEBCwUA
+A4IBAQCFHqMjHUfBZahIsKHQIcFCbC1NFh1ZHlJKZzrRBRwRzX19OttHGMyLpDd6
+tM9Ac6LLR8S4QIWg+HF3IrkN+vfTRDZAccj+tIwBRstmdsEz/rAJ79Vb/00mXZQx
+0FPiBDR3hE7On2oo24DU8kJP3v6TrunwtIomVGqrrkwZzvxqyW+WJMB2shGNFw5J
+mKYBiiXsHl4Bi7V4zhXssrLp877sqpNLeXloXBmAlT39SwQTP9ImZaV5R6udqlvo
+Gfgm5PH/WeK6MV3n5ik0v1rS0LwR2o82WlIB6a4iSEbzY3qSLsWOwt8o5QjAVzCR
+tNdbdS3U8nrG73iA2clmF57ARQWC
+-----END CERTIFICATE-----
diff --git a/knife/spec/data/windows_certificates/test.pfx b/knife/spec/data/windows_certificates/test.pfx
new file mode 100644
index 0000000000..2c208bf7c6
--- /dev/null
+++ b/knife/spec/data/windows_certificates/test.pfx
Binary files differ
diff --git a/knife/spec/functional/configure_spec.rb b/knife/spec/functional/configure_spec.rb
new file mode 100644
index 0000000000..402e988132
--- /dev/null
+++ b/knife/spec/functional/configure_spec.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+require "chef/knife/configure"
+
+describe "knife configure" do
+ let(:ohai) do
+ OHAI_SYSTEM
+ end
+
+ it "loads the fqdn from Ohai" do
+ knife_configure = Chef::Knife::Configure.new
+ hostname_guess = ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] || "localhost"
+ expect(knife_configure.guess_servername).to eql(hostname_guess)
+ end
+end
diff --git a/knife/spec/functional/cookbook_delete_spec.rb b/knife/spec/functional/cookbook_delete_spec.rb
new file mode 100644
index 0000000000..f25999f0fc
--- /dev/null
+++ b/knife/spec/functional/cookbook_delete_spec.rb
@@ -0,0 +1,156 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tiny_server"
+
+describe Chef::Knife::CookbookDelete do
+ let(:server) { TinyServer::Manager.new }
+ let(:api) { TinyServer::API.instance }
+ let(:knife_stdout) { StringIO.new }
+ let(:knife_stderr) { StringIO.new }
+ let(:knife) do
+ knife = Chef::Knife::CookbookDelete.new
+ allow(knife.ui).to receive(:stdout).and_return(knife_stdout)
+ allow(knife.ui).to receive(:stderr).and_return(knife_stderr)
+ knife
+ end
+
+ before(:each) do
+ server.start
+ api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = "http://localhost:9000"
+ end
+
+ after(:each) do
+ server.stop
+ end
+
+ context "when the cookbook doesn't exist" do
+ before do
+ knife.name_args = %w{no-such-cookbook}
+ api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({ "error" => "dear Tim, no. -Sent from my iPad" }))
+ end
+
+ it "logs an error and exits" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(knife_stderr.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/)
+ end
+
+ end
+
+ context "when there is only one version of a cookbook" do
+ before do
+ knife.name_args = %w{obsolete-cookbook}
+ @cookbook_list = { "obsolete-cookbook" => { "versions" => ["version" => "1.0.0"] } }
+ api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list))
+ end
+
+ it "asks for confirmation, then deletes the cookbook" do
+ stdin, stdout = StringIO.new("y\n"), StringIO.new
+ allow(knife.ui).to receive(:stdin).and_return(stdin)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+
+ cb100_deleted = false
+ api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+
+ knife.run
+
+ expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+ expect(cb100_deleted).to be_truthy
+ end
+
+ it "asks for confirmation before purging" do
+ knife.config[:purge] = true
+
+ stdin, stdout = StringIO.new("y\ny\n"), StringIO.new
+ allow(knife.ui).to receive(:stdin).and_return(stdin)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+
+ cb100_deleted = false
+ api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" }
+
+ knife.run
+
+ expect(stdout.string).to match(/#{Regexp.escape('Are you sure you want to purge files')}/)
+ expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+ expect(cb100_deleted).to be_truthy
+
+ end
+
+ end
+
+ context "when there are several versions of a cookbook" do
+ before do
+ knife.name_args = %w{obsolete-cookbook}
+ versions = ["1.0.0", "1.1.0", "1.2.0"]
+ with_version = lambda { |version| { "version" => version } }
+ @cookbook_list = { "obsolete-cookbook" => { "versions" => versions.map(&with_version) } }
+ api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list))
+ end
+
+ it "deletes all versions of a cookbook when given the '-a' flag" do
+ knife.config[:all] = true
+ knife.config[:yes] = true
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+ api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+ knife.run
+
+ expect(cb100_deleted).to be_truthy
+ expect(cb110_deleted).to be_truthy
+ expect(cb120_deleted).to be_truthy
+ end
+
+ it "asks which version to delete and deletes that when not given the -a flag" do
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ stdin, stdout = StringIO.new, StringIO.new
+ allow(knife.ui).to receive(:stdin).and_return(stdin)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ stdin << "1\n"
+ stdin.rewind
+ knife.run
+ expect(cb100_deleted).to be_truthy
+ expect(stdout.string).to match(/Which version\(s\) do you want to delete\?/)
+ end
+
+ it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+ api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+
+ stdin, stdout = StringIO.new("4\n"), StringIO.new
+ allow(knife.ui).to receive(:stdin).and_return(stdin)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+
+ knife.run
+
+ expect(cb100_deleted).to be_truthy
+ expect(cb110_deleted).to be_truthy
+ expect(cb120_deleted).to be_truthy
+ end
+
+ end
+
+end
diff --git a/knife/spec/functional/exec_spec.rb b/knife/spec/functional/exec_spec.rb
new file mode 100644
index 0000000000..267fe8492e
--- /dev/null
+++ b/knife/spec/functional/exec_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tiny_server"
+
+describe Chef::Knife::Exec do
+ before(:each) do
+ @server = TinyServer::Manager.new # (:debug => true)
+ @server.start
+ end
+
+ after(:each) do
+ @server.stop
+ end
+
+ before(:each) do
+ @knife = Chef::Knife::Exec.new
+ @api = TinyServer::API.instance
+ @api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = "http://localhost:9000"
+
+ $output = StringIO.new
+ end
+
+ it "executes a script in the context of the chef-shell main context" do
+ @node = Chef::Node.new
+ @node.name("ohai-world")
+ response = { "rows" => [@node], "start" => 0, "total" => 1 }
+ @api.get(%r{^/search/node}, 200, Chef::JSONCompat.to_json(response))
+ code = "$output.puts nodes.all"
+ @knife.config[:exec] = code
+ @knife.run
+ expect($output.string).to match(/node\[ohai-world\]/)
+ end
+
+end
diff --git a/knife/spec/functional/rehash_spec.rb b/knife/spec/functional/rehash_spec.rb
new file mode 100644
index 0000000000..a4b7e5507c
--- /dev/null
+++ b/knife/spec/functional/rehash_spec.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+require "chef/knife/rehash"
+require "chef/knife/core/subcommand_loader"
+
+describe "knife rehash" do
+ before do
+ allow(Chef::Knife::SubcommandLoader).to receive(:load_commands)
+ end
+
+ after do
+ # We need to clean up the generated manifest or else is messes with later tests
+ FileUtils.rm_f(Chef::Knife::SubcommandLoader.plugin_manifest_path)
+ end
+
+ it "writes the loaded plugins to disc" do
+ knife_rehash = Chef::Knife::Rehash.new
+ knife_rehash.run
+ expect(File.read(Chef::Knife::SubcommandLoader.plugin_manifest_path)).to match(/node_list.rb/)
+ end
+end
diff --git a/spec/functional/knife/smoke_test.rb b/knife/spec/functional/smoke_test.rb
index 99b61f42f2..99b61f42f2 100644
--- a/spec/functional/knife/smoke_test.rb
+++ b/knife/spec/functional/smoke_test.rb
diff --git a/knife/spec/functional/ssh_spec.rb b/knife/spec/functional/ssh_spec.rb
new file mode 100644
index 0000000000..5a29f995f8
--- /dev/null
+++ b/knife/spec/functional/ssh_spec.rb
@@ -0,0 +1,352 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tiny_server"
+
+describe Chef::Knife::Ssh do
+
+ before(:each) do
+ Chef::Knife::Ssh.load_deps
+ @server = TinyServer::Manager.new
+ @server.start
+ end
+
+ after(:each) do
+ @server.stop
+ end
+
+ let(:ssh_config) { {} }
+ before do
+ allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config)
+ end
+
+ describe "identity file" do
+ context "when knife[:ssh_identity_file] is set" do
+ before do
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_identity_file" do
+ @knife.run
+ expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
+ end
+ end
+
+ context "when knife[:ssh_identity_file] is set and frozen" do
+ before do
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_identity_file" do
+ @knife.run
+ expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
+ end
+ end
+
+ context "when -i is provided" do
+ before do
+ Chef::Config[:knife][:ssh_identity_file] = nil
+ setup_knife(["-i ~/.ssh/aws.rsa", "*:*", "uptime"])
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
+ end
+
+ it "should override what is set in knife.rb" do
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa"
+ @knife.merge_configs
+ @knife.run
+ expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
+ end
+ end
+
+ context "when knife[:ssh_identity_file] is not provided]" do
+ before do
+ Chef::Config[:knife][:ssh_identity_file] = nil
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the default" do
+ @knife.run
+ expect(@knife.config[:ssh_identity_file]).to eq(nil)
+ end
+ end
+ end
+
+ describe "port" do
+ context "when -p 31337 is provided" do
+ before do
+ setup_knife(["-p 31337", "*:*", "uptime"])
+ end
+
+ it "uses the ssh_port" do
+ @knife.run
+ expect(@knife.config[:ssh_port]).to eq("31337")
+ end
+ end
+ end
+
+ describe "user" do
+ context "when knife[:ssh_user] is set" do
+ before do
+ Chef::Config[:knife][:ssh_user] = "ubuntu"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_user" do
+ @knife.run
+ expect(@knife.config[:ssh_user]).to eq("ubuntu")
+ end
+ end
+
+ context "when knife[:ssh_user] is set and frozen" do
+ before do
+ Chef::Config[:knife][:ssh_user] = "ubuntu".freeze
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_user" do
+ @knife.run
+ expect(@knife.config[:ssh_user]).to eq("ubuntu")
+ end
+ end
+
+ context "when -x is provided" do
+ before do
+ Chef::Config[:knife][:ssh_user] = nil
+ setup_knife(["-x ubuntu", "*:*", "uptime"])
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ expect(@knife.config[:ssh_user]).to eq("ubuntu")
+ end
+
+ it "should override what is set in knife.rb" do
+ Chef::Config[:knife][:ssh_user] = "root"
+ @knife.merge_configs
+ @knife.run
+ expect(@knife.config[:ssh_user]).to eq("ubuntu")
+ end
+ end
+
+ context "when knife[:ssh_user] is not provided]" do
+ before do
+ Chef::Config[:knife][:ssh_user] = nil
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the default (current user)" do
+ @knife.run
+ expect(@knife.config[:ssh_user]).to eq(nil)
+ end
+ end
+ end
+
+ describe "attribute" do
+ context "when knife[:ssh_attribute] is set" do
+ before do
+ Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_attribute" do
+ @knife.run
+ expect(@knife.get_ssh_attribute({ "target" => "ec2.public_hostname" })).to eq("ec2.public_hostname")
+ end
+ end
+
+ context "when knife[:ssh_attribute] is not provided" do
+ before do
+ Chef::Config[:knife][:ssh_attribute] = nil
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the default" do
+ @knife.run
+ expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn")
+ end
+ end
+
+ context "when -a ec2.public_public_hostname is provided" do
+ before do
+ Chef::Config[:knife][:ssh_attribute] = nil
+ setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"])
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname")
+ end
+
+ it "should override what is set in knife.rb" do
+ # This is the setting imported from knife.rb
+ Chef::Config[:knife][:ssh_attribute] = "fqdn"
+ @knife.merge_configs
+ # Then we run knife with the -a flag, which sets the above variable
+ setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"])
+ @knife.run
+ expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname")
+ end
+ end
+ end
+
+ describe "prefix" do
+ context "when knife[:prefix_attribute] is set" do
+ before do
+ Chef::Config[:knife][:prefix_attribute] = "name"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the prefix_attribute" do
+ @knife.run
+ expect(@knife.get_prefix_attribute({ "prefix" => "name" })).to eq("name")
+ end
+ end
+
+ context "when knife[:prefix_attribute] is not provided" do
+ before do
+ Chef::Config[:knife][:prefix_attribute] = nil
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "falls back to nil" do
+ @knife.run
+ expect(@knife.get_prefix_attribute({})).to eq(nil)
+ end
+ end
+
+ context "when --prefix-attribute ec2.public_public_hostname is provided" do
+ before do
+ Chef::Config[:knife][:prefix_attribute] = nil
+ setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"])
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname")
+ end
+
+ it "should override what is set in knife.rb" do
+ # This is the setting imported from knife.rb
+ Chef::Config[:knife][:prefix_attribute] = "fqdn"
+ @knife.merge_configs
+ # Then we run knife with the -b flag, which sets the above variable
+ setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"])
+ @knife.run
+ expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname")
+ end
+ end
+ end
+
+ describe "gateway" do
+ context "when knife[:ssh_gateway] is set" do
+ before do
+ Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_gateway" do
+ expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true })
+ @knife.run
+ expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname")
+ end
+ end
+
+ context "when -G user@ec2.public_hostname is provided" do
+ before do
+ Chef::Config[:knife][:ssh_gateway] = nil
+ setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"])
+ end
+
+ it "uses the ssh_gateway" do
+ expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true })
+ @knife.run
+ expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname")
+ end
+ end
+
+ context "when knife[:ssh_gateway_identity] is set" do
+ before do
+ Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
+ Chef::Config[:knife][:ssh_gateway_identity] = "~/.ssh/aws-gateway.rsa"
+ setup_knife(["*:*", "uptime"])
+ end
+
+ it "uses the ssh_gateway_identity file" do
+ expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true })
+ @knife.run
+ expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa")
+ end
+ end
+
+ context "when -ssh-gateway-identity is provided and knife[:ssh_gateway] is set" do
+ before do
+ Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
+ Chef::Config[:knife][:ssh_gateway_identity] = nil
+ setup_knife(["--ssh-gateway-identity", "~/.ssh/aws-gateway.rsa", "*:*", "uptime"])
+ end
+
+ it "uses the ssh_gateway_identity file" do
+ expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true })
+ @knife.run
+ expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa")
+ end
+ end
+
+ context "when the gateway requires a password" do
+ before do
+ Chef::Config[:knife][:ssh_gateway] = nil
+ setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"])
+ allow(@knife.session).to receive(:via) do |host, user, options|
+ raise Net::SSH::AuthenticationFailed unless options[:password]
+ end
+ end
+
+ it "should prompt the user for a password" do
+ expect(@knife.ui).to receive(:ask).with("Enter the password for user@ec2.public_hostname: ", echo: false).and_return("password")
+ @knife.run
+ end
+ end
+ end
+
+ def setup_knife(params = [])
+ @knife = Chef::Knife::Ssh.new(params)
+ # We explicitly avoid running #configure_chef, which would read a knife.rb
+ # if available, but #merge_configs (which is called by #configure_chef) is
+ # necessary to have default options merged in.
+ @knife.merge_configs
+ allow(@knife).to receive(:ssh_command) { 0 }
+ @api = TinyServer::API.instance
+ @api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = "http://localhost:9000"
+
+ @api.post("/search/node?q=*:*&start=0&rows=1000", 200) do
+ %({"total":1, "start":0, "rows":[{"data": {"fqdn":"the.fqdn", "target": "the_public_hostname"}}]})
+ end
+ end
+
+end
diff --git a/knife/spec/functional/version_spec.rb b/knife/spec/functional/version_spec.rb
new file mode 100644
index 0000000000..b024cc1cda
--- /dev/null
+++ b/knife/spec/functional/version_spec.rb
@@ -0,0 +1,26 @@
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/mixin/shell_out"
+
+describe "Knife Version", :executables do
+ include Chef::Mixin::ShellOut
+ let(:knife_dir) { File.join(__dir__, "..", "..", "..", "knife") }
+ xit "should be sane" do
+ expect(shell_out!("bundle exec knife -v", cwd: knife_dir).stdout.chomp).to match(/.*: #{Chef::Knife::VERSION}/)
+ end
+end
+
diff --git a/knife/spec/integration/chef_fs_data_store_spec.rb b/knife/spec/integration/chef_fs_data_store_spec.rb
new file mode 100644
index 0000000000..fda06164a4
--- /dev/null
+++ b/knife/spec/integration/chef_fs_data_store_spec.rb
@@ -0,0 +1,557 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/list"
+require "chef/knife/delete"
+require "chef/knife/show"
+require "chef/knife/raw"
+require "chef/knife/cookbook_upload"
+
+describe "ChefFSDataStore tests", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ let(:cookbook_x_100_metadata_rb) { cb_metadata("x", "1.0.0") }
+ let(:cookbook_z_100_metadata_rb) { cb_metadata("z", "1.0.0") }
+
+ describe "with repo mode 'hosted_everything' (default)" do
+ before do
+ Chef::Config.chef_zero.osc_compat = false
+ end
+
+ when_the_repository "has one of each thing" do
+ before do
+ file "clients/x.json", {}
+ file "cookbook_artifacts/x-111/metadata.rb", cookbook_x_100_metadata_rb
+ file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb
+ file "data_bags/x/y.json", {}
+ file "environments/x.json", {}
+ file "nodes/x.json", {}
+ file "roles/x.json", {}
+ # file "users/x.json", {}
+ file "containers/x.json", {}
+ file "groups/x.json", {}
+ file "containers/x.json", {}
+ file "groups/x.json", {}
+ file "policies/x-111.json", {}
+ file "policy_groups/x.json", {}
+ end
+
+ context "GET /TYPE" do
+ it "knife list -z -R returns everything" do
+ knife("list -z -Rfp /").should_succeed <<~EOM
+ /acls/
+ /acls/clients/
+ /acls/clients/x.json
+ /acls/containers/
+ /acls/containers/x.json
+ /acls/cookbook_artifacts/
+ /acls/cookbook_artifacts/x.json
+ /acls/cookbooks/
+ /acls/cookbooks/x.json
+ /acls/data_bags/
+ /acls/data_bags/x.json
+ /acls/environments/
+ /acls/environments/x.json
+ /acls/groups/
+ /acls/groups/x.json
+ /acls/nodes/
+ /acls/nodes/x.json
+ /acls/organization.json
+ /acls/policies/
+ /acls/policies/x.json
+ /acls/policy_groups/
+ /acls/policy_groups/x.json
+ /acls/roles/
+ /acls/roles/x.json
+ /clients/
+ /clients/x.json
+ /containers/
+ /containers/x.json
+ /cookbook_artifacts/
+ /cookbook_artifacts/x-111/
+ /cookbook_artifacts/x-111/metadata.rb
+ /cookbooks/
+ /cookbooks/x/
+ /cookbooks/x/metadata.rb
+ /data_bags/
+ /data_bags/x/
+ /data_bags/x/y.json
+ /environments/
+ /environments/x.json
+ /groups/
+ /groups/x.json
+ /invitations.json
+ /members.json
+ /nodes/
+ /nodes/x.json
+ /org.json
+ /policies/
+ /policies/x-111.json
+ /policy_groups/
+ /policy_groups/x.json
+ /roles/
+ /roles/x.json
+ EOM
+ end
+ end
+
+ context "DELETE /TYPE/NAME" do
+ it "knife delete -z /clients/x.json works" do
+ knife("delete -z /clients/x.json").should_succeed "Deleted /clients/x.json\n"
+ knife("list -z -Rfp /clients").should_succeed ""
+ end
+
+ it "knife delete -z -r /cookbooks/x works" do
+ knife("delete -z -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -z -Rfp /cookbooks").should_succeed ""
+ end
+
+ it "knife delete -z -r /data_bags/x works" do
+ knife("delete -z -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
+ knife("list -z -Rfp /data_bags").should_succeed ""
+ end
+
+ it "knife delete -z /data_bags/x/y.json works" do
+ knife("delete -z /data_bags/x/y.json").should_succeed "Deleted /data_bags/x/y.json\n"
+ knife("list -z -Rfp /data_bags").should_succeed "/data_bags/x/\n"
+ end
+
+ it "knife delete -z /environments/x.json works" do
+ knife("delete -z /environments/x.json").should_succeed "Deleted /environments/x.json\n"
+ knife("list -z -Rfp /environments").should_succeed ""
+ end
+
+ it "knife delete -z /nodes/x.json works" do
+ knife("delete -z /nodes/x.json").should_succeed "Deleted /nodes/x.json\n"
+ knife("list -z -Rfp /nodes").should_succeed ""
+ end
+
+ it "knife delete -z /roles/x.json works" do
+ knife("delete -z /roles/x.json").should_succeed "Deleted /roles/x.json\n"
+ knife("list -z -Rfp /roles").should_succeed ""
+ end
+
+ end
+
+ context "GET /TYPE/NAME" do
+ it "knife show -z /clients/x.json works" do
+ knife("show -z /clients/x.json").should_succeed( /"x"/ )
+ end
+
+ it "knife show -z /cookbooks/x/metadata.rb works" do
+ knife("show -z /cookbooks/x/metadata.rb").should_succeed "/cookbooks/x/metadata.rb:\n#{cookbook_x_100_metadata_rb}\n"
+ end
+
+ it "knife show -z /data_bags/x/y.json works" do
+ knife("show -z /data_bags/x/y.json").should_succeed( /"y"/ )
+ end
+
+ it "knife show -z /environments/x.json works" do
+ knife("show -z /environments/x.json").should_succeed( /"x"/ )
+ end
+
+ it "knife show -z /nodes/x.json works" do
+ knife("show -z /nodes/x.json").should_succeed( /"x"/ )
+ end
+
+ it "knife show -z /roles/x.json works" do
+ knife("show -z /roles/x.json").should_succeed( /"x"/ )
+ end
+
+ end
+
+ context "PUT /TYPE/NAME" do
+ before do
+ file "empty.json", {}
+ file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
+ file "rolestuff.json", '{"description":"hi there","name":"x"}'
+ file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb
+ end
+
+ it "knife raw -z -i empty.json -m PUT /clients/x" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_succeed( /"x"/ )
+ knife("list --local /clients").should_succeed "/clients/x.json\n"
+ end
+
+ it "knife cookbook upload works" do
+ knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} x").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploaded 1 cookbook.
+ EOM
+ knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/x/\n/cookbooks/x/metadata.json\n/cookbooks/x/metadata.rb\n"
+ end
+
+ it "knife raw -z -i empty.json -m PUT /data/x/y" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_succeed( /"y"/ )
+ knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/y.json\n"
+ end
+
+ it "knife raw -z -i empty.json -m PUT /environments/x" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_succeed( /"x"/ )
+ knife("list --local /environments").should_succeed "/environments/x.json\n"
+ end
+
+ it "knife raw -z -i dummynode.json -m PUT /nodes/x" do
+ knife("raw -z -i #{path_to("dummynode.json")} -m PUT /nodes/x").should_succeed( /"x"/ )
+ knife("list --local /nodes").should_succeed "/nodes/x.json\n"
+ knife("show -z /nodes/x.json --verbose").should_succeed(/"bar"/)
+ end
+
+ it "knife raw -z -i empty.json -m PUT /roles/x" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_succeed( /"x"/ )
+ knife("list --local /roles").should_succeed "/roles/x.json\n"
+ end
+
+ it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do
+ knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ )
+ expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
+ {
+ "name": "x",
+ "description": "hi there"
+ }
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ context "POST /TYPE/NAME" do
+ before do
+ file "empty.json", { "name" => "z" }
+ file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
+ file "empty_x.json", { "name" => "x" }
+ file "empty_id.json", { "id" => "z" }
+ file "rolestuff.json", '{"description":"hi there","name":"x"}'
+ file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb
+ end
+
+ it "knife raw -z -i empty.json -m POST /clients" do
+ knife("raw -z -i #{path_to("empty.json")} -m POST /clients").should_succeed( /uri/ )
+ knife("list --local /clients").should_succeed "/clients/z.json\n"
+ end
+
+ it "knife cookbook upload works" do
+ knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} z").should_succeed stderr: <<~EOM
+ Uploading z [1.0.0]
+ Uploaded 1 cookbook.
+ EOM
+ knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/z/\n/cookbooks/z/metadata.json\n/cookbooks/z/metadata.rb\n"
+ end
+
+ it "knife raw -z -i empty.json -m POST /data" do
+ knife("raw -z -i #{path_to("empty.json")} -m POST /data").should_succeed( /uri/ )
+ knife("list --local -Rfp /data_bags").should_succeed "/data_bags/z/\n"
+ end
+
+ it "knife raw -z -i empty.json -m POST /data/x" do
+ knife("raw -z -i #{path_to("empty_x.json")} -m POST /data").should_succeed( /uri/ )
+ knife("raw -z -i #{path_to("empty_id.json")} -m POST /data/x").should_succeed( /"z"/ )
+ knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/z.json\n"
+ end
+
+ it "knife raw -z -i empty.json -m POST /environments" do
+ knife("raw -z -i #{path_to("empty.json")} -m POST /environments").should_succeed( /uri/ )
+ knife("list --local /environments").should_succeed "/environments/z.json\n"
+ end
+
+ it "knife raw -z -i dummynode.json -m POST /nodes" do
+ knife("raw -z -i #{path_to("dummynode.json")} -m POST /nodes").should_succeed( /uri/ )
+ knife("list --local /nodes").should_succeed "/nodes/z.json\n"
+ knife("show -z /nodes/z.json").should_succeed(/"bar"/)
+ end
+
+ it "knife raw -z -i empty.json -m POST /roles" do
+ knife("raw -z -i #{path_to("empty.json")} -m POST /roles").should_succeed( /uri/ )
+ knife("list --local /roles").should_succeed "/roles/z.json\n"
+ end
+
+ it "After knife raw -z -i rolestuff.json -m POST /roles, the output is pretty" do
+ knife("raw -z -i #{path_to("rolestuff.json")} -m POST /roles").should_succeed( /uri/ )
+ expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
+ {
+ "name": "x",
+ "description": "hi there"
+ }
+ EOM
+ end
+ end
+
+ it "knife list -z -R returns nothing" do
+ knife("list -z -Rfp /").should_succeed <<~EOM
+ /acls/
+ /acls/clients/
+ /acls/containers/
+ /acls/cookbook_artifacts/
+ /acls/cookbooks/
+ /acls/data_bags/
+ /acls/environments/
+ /acls/groups/
+ /acls/nodes/
+ /acls/organization.json
+ /acls/policies/
+ /acls/policy_groups/
+ /acls/roles/
+ /clients/
+ /containers/
+ /cookbook_artifacts/
+ /cookbooks/
+ /data_bags/
+ /environments/
+ /groups/
+ /invitations.json
+ /members.json
+ /nodes/
+ /org.json
+ /policies/
+ /policy_groups/
+ /roles/
+ EOM
+ end
+
+ context "DELETE /TYPE/NAME" do
+ it "knife delete -z /clients/x.json fails with an error" do
+ knife("delete -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n"
+ end
+
+ it "knife delete -z -r /cookbooks/x fails with an error" do
+ knife("delete -z -r /cookbooks/x").should_fail "ERROR: /cookbooks/x: No such file or directory\n"
+ end
+
+ it "knife delete -z -r /data_bags/x fails with an error" do
+ knife("delete -z -r /data_bags/x").should_fail "ERROR: /data_bags/x: No such file or directory\n"
+ end
+
+ it "knife delete -z /data_bags/x/y.json fails with an error" do
+ knife("delete -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
+ end
+
+ it "knife delete -z /environments/x.json fails with an error" do
+ knife("delete -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n"
+ end
+
+ it "knife delete -z /nodes/x.json fails with an error" do
+ knife("delete -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n"
+ end
+
+ it "knife delete -z /roles/x.json fails with an error" do
+ knife("delete -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
+ end
+
+ end
+
+ context "GET /TYPE/NAME" do
+ it "knife show -z /clients/x.json fails with an error" do
+ knife("show -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n"
+ end
+
+ it "knife show -z /cookbooks/x/metadata.rb fails with an error" do
+ knife("show -z /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb: No such file or directory\n"
+ end
+
+ it "knife show -z /data_bags/x/y.json fails with an error" do
+ knife("show -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
+ end
+
+ it "knife show -z /environments/x.json fails with an error" do
+ knife("show -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n"
+ end
+
+ it "knife show -z /nodes/x.json fails with an error" do
+ knife("show -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n"
+ end
+
+ it "knife show -z /roles/x.json fails with an error" do
+ knife("show -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
+ end
+
+ end
+
+ context "PUT /TYPE/NAME" do
+ before do
+ file "empty.json", {}
+ end
+
+ it "knife raw -z -i empty.json -m PUT /clients/x fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_fail( /404/ )
+ end
+
+ it "knife raw -z -i empty.json -m PUT /data/x/y fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_fail( /404/ )
+ end
+
+ it "knife raw -z -i empty.json -m PUT /environments/x fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_fail( /404/ )
+ end
+
+ it "knife raw -z -i empty.json -m PUT /nodes/x fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /nodes/x").should_fail( /404/ )
+ end
+
+ it "knife raw -z -i empty.json -m PUT /roles/x fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_fail( /404/ )
+ end
+
+ end
+ end
+ end
+
+ # We have to configure Zero for Chef 11 mode in order to test users because:
+ # 1. local mode overrides your `chef_server_url` to something like "http://localhost:PORT"
+ # 2. single org mode maps requests like "https://localhost:PORT/users" so
+ # they're functionally equivalent to "https://localhost:PORT/organizations/DEFAULT/users"
+ # 3. Users are global objects in Chef 12, and should be accessed at URLs like
+ # "https://localhost:PORT/users" (there is an org-specific users endpoint,
+ # but it's for listing users in an org, not for managing users).
+ # 4. Therefore you can't hit the _real_ users endpoint in local mode when
+ # configured for Chef Server 12 mode.
+ #
+ # Because of this, we have to configure Zero for Chef 11 OSC mode in order to
+ # test the users part of the data store with local mode.
+ describe "with repo mode 'everything'" do
+ before do
+ Chef::Config.repo_mode = "everything"
+ Chef::Config.chef_zero.osc_compat = true
+ end
+
+ when_the_repository "has one of each thing" do
+ before do
+ file "clients/x.json", {}
+ file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb
+ file "data_bags/x/y.json", {}
+ file "environments/x.json", {}
+ file "nodes/x.json", {}
+ file "roles/x.json", {}
+ file "users/x.json", {}
+ end
+
+ context "GET /TYPE" do
+ it "knife list -z -R returns everything" do
+ knife("list -z -Rfp /").should_succeed <<~EOM
+ /clients/
+ /clients/x.json
+ /cookbooks/
+ /cookbooks/x/
+ /cookbooks/x/metadata.rb
+ /data_bags/
+ /data_bags/x/
+ /data_bags/x/y.json
+ /environments/
+ /environments/x.json
+ /nodes/
+ /nodes/x.json
+ /roles/
+ /roles/x.json
+ /users/
+ /users/x.json
+ EOM
+ end
+ end
+
+ context "DELETE /TYPE/NAME" do
+ it "knife delete -z /users/x.json works" do
+ knife("delete -z /users/x.json").should_succeed "Deleted /users/x.json\n"
+ knife("list -z -Rfp /users").should_succeed ""
+ end
+ end
+
+ context "GET /TYPE/NAME" do
+ it "knife show -z /users/x.json works" do
+ knife("show -z /users/x.json").should_succeed( /"x"/ )
+ end
+ end
+
+ context "PUT /TYPE/NAME" do
+ before do
+ file "empty.json", {}
+ file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
+ file "rolestuff.json", '{"description":"hi there","name":"x"}'
+ file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb
+ end
+
+ it "knife raw -z -i empty.json -m PUT /users/x" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_succeed( /"x"/ )
+ knife("list --local /users").should_succeed "/users/x.json\n"
+ end
+
+ it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do
+ knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ )
+ expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
+ {
+ "name": "x",
+ "description": "hi there"
+ }
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ context "POST /TYPE/NAME" do
+ before do
+ file "empty.json", { "name" => "z" }
+ file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
+ file "empty_x.json", { "name" => "x" }
+ file "empty_id.json", { "id" => "z" }
+ file "rolestuff.json", '{"description":"hi there","name":"x"}'
+ file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb
+ end
+
+ it "knife raw -z -i empty.json -m POST /users" do
+ knife("raw -z -i #{path_to("empty.json")} -m POST /users").should_succeed( /uri/ )
+ knife("list --local /users").should_succeed "/users/z.json\n"
+ end
+ end
+
+ it "knife list -z -R returns nothing" do
+ knife("list -z -Rfp /").should_succeed <<~EOM
+ /clients/
+ /cookbooks/
+ /data_bags/
+ /environments/
+ /nodes/
+ /roles/
+ /users/
+ EOM
+ end
+
+ context "DELETE /TYPE/NAME" do
+ it "knife delete -z /users/x.json fails with an error" do
+ knife("delete -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n"
+ end
+ end
+
+ context "GET /TYPE/NAME" do
+ it "knife show -z /users/x.json fails with an error" do
+ knife("show -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n"
+ end
+ end
+
+ context "PUT /TYPE/NAME" do
+ before do
+ file "empty.json", {}
+ end
+
+ it "knife raw -z -i empty.json -m PUT /users/x fails with 404" do
+ knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_fail( /404/ )
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/chef_repo_path_spec.rb b/knife/spec/integration/chef_repo_path_spec.rb
new file mode 100644
index 0000000000..27b45ac428
--- /dev/null
+++ b/knife/spec/integration/chef_repo_path_spec.rb
@@ -0,0 +1,962 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/list"
+require "chef/knife/show"
+
+describe "chef_repo_path tests", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ }
+
+ # TODO alternate repo_path / *_path
+ context "alternate *_path" do
+ when_the_repository "has clients and clients2, cookbooks and cookbooks2, etc." do
+ before do
+ file "clients/client1.json", {}
+ file "cookbooks/cookbook1/metadata.rb", ""
+ file "data_bags/bag/item.json", {}
+ file "environments/env1.json", {}
+ file "nodes/node1.json", {}
+ file "roles/role1.json", {}
+ file "users/user1.json", {}
+
+ file "clients2/client2.json", {}
+ file "cookbooks2/cookbook2/metadata.rb", ""
+ file "data_bags2/bag2/item2.json", {}
+ file "environments2/env2.json", {}
+ file "nodes2/node2.json", {}
+ file "roles2/role2.json", {}
+ file "users2/user2.json", {}
+
+ directory "chef_repo2" do
+ file "clients/client3.json", {}
+ file "cookbooks/cookbook3/metadata.rb", "name 'cookbook3'"
+ file "data_bags/bag3/item3.json", {}
+ file "environments/env3.json", {}
+ file "nodes/node3.json", {}
+ file "roles/role3.json", {}
+ file "users/user3.json", {}
+ end
+ end
+
+ it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do
+ Chef::Config.delete(:chef_repo_path)
+ knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM
+ /clients/
+ /clients/client3.json
+ /cookbooks/
+ /cookbooks/cookbook3/
+ /cookbooks/cookbook3/metadata.rb
+ /data_bags/
+ /data_bags/bag3/
+ /data_bags/bag3/item3.json
+ /environments/
+ /environments/env3.json
+ /nodes/
+ /nodes/node3.json
+ /roles/
+ /roles/role3.json
+ /users/
+ /users/user3.json
+ EOM
+ end
+
+ # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default"
+ it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do
+ Chef::Config.delete(:chef_repo_path)
+ knife("list --local -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM
+ /clients/
+ /clients/client3.json
+ /cookbooks/
+ /cookbooks/cookbook3/
+ /cookbooks/cookbook3/metadata.rb
+ /data_bags/
+ /data_bags/bag3/
+ /data_bags/bag3/item3.json
+ /environments/
+ /environments/env3.json
+ /nodes/
+ /nodes/node3.json
+ /roles/
+ /roles/role3.json
+ /users/
+ /users/user3.json
+ EOM
+ end
+
+ # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default"
+ it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do
+ Chef::Config.delete(:chef_repo_path)
+ knife("list -z -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM
+ /acls/
+ /acls/clients/
+ /acls/clients/client3.json
+ /acls/containers/
+ /acls/cookbook_artifacts/
+ /acls/cookbooks/
+ /acls/cookbooks/cookbook3.json
+ /acls/data_bags/
+ /acls/data_bags/bag3.json
+ /acls/environments/
+ /acls/environments/env3.json
+ /acls/groups/
+ /acls/nodes/
+ /acls/nodes/node3.json
+ /acls/organization.json
+ /acls/policies/
+ /acls/policy_groups/
+ /acls/roles/
+ /acls/roles/role3.json
+ /clients/
+ /clients/client3.json
+ /containers/
+ /cookbook_artifacts/
+ /cookbooks/
+ /cookbooks/cookbook3/
+ /cookbooks/cookbook3/metadata.rb
+ /data_bags/
+ /data_bags/bag3/
+ /data_bags/bag3/item3.json
+ /environments/
+ /environments/env3.json
+ /groups/
+ /invitations.json
+ /members.json
+ /nodes/
+ /nodes/node3.json
+ /org.json
+ /policies/
+ /policy_groups/
+ /roles/
+ /roles/role3.json
+ EOM
+ end
+
+ context "when all _paths are set to alternates" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
+ end
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
+ end
+
+ it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do
+ knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM
+ /clients/
+ /clients/client3.json
+ /cookbooks/
+ /cookbooks/cookbook3/
+ /cookbooks/cookbook3/metadata.rb
+ /data_bags/
+ /data_bags/bag3/
+ /data_bags/bag3/item3.json
+ /environments/
+ /environments/env3.json
+ /nodes/
+ /nodes/node3.json
+ /roles/
+ /roles/role3.json
+ /users/
+ /users/user3.json
+ EOM
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client2.json
+ cookbooks/
+ cookbooks/cookbook2/
+ cookbooks/cookbook2/metadata.rb
+ data_bags/
+ data_bags/bag2/
+ data_bags/bag2/item2.json
+ environments/
+ environments/env2.json
+ nodes/
+ nodes/node2.json
+ roles/
+ roles/role2.json
+ users/
+ users/user2.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside data_bags2" do
+ before { cwd "data_bags2" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag2/
+ bag2/item2.json
+ EOM
+ end
+ it "knife list --local -Rfp ../roles lists roles" do
+ knife("list --local -Rfp ../roles").should_succeed "/roles/role2.json\n"
+ end
+ end
+ end
+
+ context "when all _paths except chef_repo_path are set to alternates" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
+ end
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client2.json
+ cookbooks/
+ cookbooks/cookbook2/
+ cookbooks/cookbook2/metadata.rb
+ data_bags/
+ data_bags/bag2/
+ data_bags/bag2/item2.json
+ environments/
+ environments/env2.json
+ nodes/
+ nodes/node2.json
+ roles/
+ roles/role2.json
+ users/
+ users/user2.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside data_bags2" do
+ before { cwd "data_bags2" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag2/
+ bag2/item2.json
+ EOM
+ end
+ end
+ end
+
+ context "when only chef_repo_path is set to its alternate" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env3.json
+ nodes/
+ nodes/node3.json
+ roles/
+ roles/role3.json
+ users/
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2/data_bags" do
+ before { cwd "chef_repo2/data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+ end
+
+ context "when paths are set to point to both versions of each" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config["#{object_name}_path".to_sym] = [
+ File.join(Chef::Config.chef_repo_path, "#{object_name}s"),
+ File.join(Chef::Config.chef_repo_path, "#{object_name}s2"),
+ ]
+ end
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
+ end
+
+ context "when there is a directory in clients1 and file in clients2 with the same name" do
+ before do
+ directory "clients/blah.json"
+ file "clients2/blah.json", {}
+ end
+ it "knife show /clients/blah.json succeeds" do
+ knife("show --local /clients/blah.json").should_succeed <<~EOM
+ /clients/blah.json:
+ {
+
+ }
+ EOM
+ end
+ end
+
+ context "when there is a file in cookbooks1 and directory in cookbooks2 with the same name" do
+ before do
+ file "cookbooks/blah", ""
+ file "cookbooks2/blah/metadata.rb", ""
+ end
+ it "knife list -Rfp cookbooks shows files in blah" do
+ knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
+ /cookbooks/blah/
+ /cookbooks/blah/metadata.rb
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ EOM
+ end
+ end
+
+ context "when there is an empty directory in cookbooks1 and a real cookbook in cookbooks2 with the same name" do
+ before do
+ directory "cookbooks/blah"
+ file "cookbooks2/blah/metadata.rb", ""
+ end
+ it "knife list -Rfp cookbooks shows files in blah" do
+ knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'blah' is empty or entirely chefignored at #{Chef::Config.cookbook_path[0]}/blah\n")
+ /cookbooks/blah/
+ /cookbooks/blah/metadata.rb
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ EOM
+ end
+ end
+
+ context "when there is a cookbook in cookbooks1 and a cookbook in cookbooks2 with the same name" do
+ before do
+ file "cookbooks/blah/metadata.json", {}
+ file "cookbooks2/blah/metadata.rb", ""
+ end
+ it "knife list -Rfp cookbooks shows files in the first cookbook and not the second" do
+ knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.cookbook_path[0]}/blah and #{Chef::Config.cookbook_path[1]}/blah\n")
+ /cookbooks/blah/
+ /cookbooks/blah/metadata.json
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ EOM
+ end
+ end
+
+ context "when there is a file in data_bags1 and a directory in data_bags2 with the same name" do
+ before do
+ file "data_bags/blah", ""
+ file "data_bags2/blah/item.json", ""
+ end
+ it "knife list -Rfp data_bags shows files in blah" do
+ knife("list --local -Rfp /data_bags").should_succeed <<~EOM
+ /data_bags/bag/
+ /data_bags/bag/item.json
+ /data_bags/bag2/
+ /data_bags/bag2/item2.json
+ /data_bags/blah/
+ /data_bags/blah/item.json
+ EOM
+ end
+ end
+
+ context "when there is a data bag in data_bags1 and a data bag in data_bags2 with the same name" do
+ before do
+ file "data_bags/blah/item1.json", ""
+ file "data_bags2/blah/item2.json", ""
+ end
+ it "knife list -Rfp data_bags shows only items in data_bags1" do
+ knife("list --local -Rfp /data_bags").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.data_bag_path[0]}/blah and #{Chef::Config.data_bag_path[1]}/blah\n")
+ /data_bags/bag/
+ /data_bags/bag/item.json
+ /data_bags/bag2/
+ /data_bags/bag2/item2.json
+ /data_bags/blah/
+ /data_bags/blah/item1.json
+ EOM
+ end
+ end
+
+ context "when there is a directory in environments1 and file in environments2 with the same name" do
+ before do
+ directory "environments/blah.json"
+ file "environments2/blah.json", {}
+ end
+ it "knife show /environments/blah.json succeeds" do
+ knife("show --local /environments/blah.json").should_succeed <<~EOM
+ /environments/blah.json:
+ {
+
+ }
+ EOM
+ end
+ end
+
+ context "when there is a directory in nodes1 and file in nodes2 with the same name" do
+ before do
+ directory "nodes/blah.json"
+ file "nodes2/blah.json", {}
+ end
+ it "knife show /nodes/blah.json succeeds" do
+ knife("show --local /nodes/blah.json").should_succeed <<~EOM
+ /nodes/blah.json:
+ {
+
+ }
+ EOM
+ end
+ end
+
+ context "when there is a directory in roles1 and file in roles2 with the same name" do
+ before do
+ directory "roles/blah.json"
+ file "roles2/blah.json", {}
+ end
+ it "knife show /roles/blah.json succeeds" do
+ knife("show --local /roles/blah.json").should_succeed <<~EOM
+ /roles/blah.json:
+ {
+
+ }
+ EOM
+ end
+ end
+
+ context "when there is a directory in users1 and file in users2 with the same name" do
+ before do
+ directory "users/blah.json"
+ file "users2/blah.json", {}
+ end
+ it "knife show /users/blah.json succeeds" do
+ knife("show --local /users/blah.json").should_succeed <<~EOM
+ /users/blah.json:
+ {
+
+ }
+ EOM
+ end
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag2/
+ bag2/item2.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client1.json
+ clients/client2.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook2/
+ cookbooks/cookbook2/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ data_bags/bag2/
+ data_bags/bag2/item2.json
+ environments/
+ environments/env1.json
+ environments/env2.json
+ nodes/
+ nodes/node1.json
+ nodes/node2.json
+ roles/
+ roles/role1.json
+ roles/role2.json
+ users/
+ users/user1.json
+ users/user2.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside data_bags2" do
+ before { cwd "data_bags2" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag2/
+ bag2/item2.json
+ EOM
+ end
+ end
+ end
+
+ context "when when chef_repo_path is set to both places and no other _path is set" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.chef_repo_path = [
+ Chef::Config.chef_repo_path,
+ File.join(Chef::Config.chef_repo_path, "chef_repo2"),
+ ]
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client1.json
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env1.json
+ environments/env3.json
+ nodes/
+ nodes/node1.json
+ nodes/node3.json
+ roles/
+ roles/role1.json
+ roles/role3.json
+ users/
+ users/user1.json
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client1.json
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env1.json
+ environments/env3.json
+ nodes/
+ nodes/node1.json
+ nodes/node3.json
+ roles/
+ roles/role1.json
+ roles/role3.json
+ users/
+ users/user1.json
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2/data_bags" do
+ before { cwd "chef_repo2/data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+ end
+
+ context "when cookbook_path is set and nothing else" do
+ before :each do
+ %w{client data_bag environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Config.cookbook_path = File.join(@repository_dir, "chef_repo2", "cookbooks")
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env3.json
+ nodes/
+ nodes/node3.json
+ roles/
+ roles/role3.json
+ users/
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2/data_bags" do
+ before { cwd "chef_repo2/data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+ end
+
+ context "when cookbook_path is set to multiple places and nothing else is set" do
+ before :each do
+ %w{client data_bag environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Config.cookbook_path = [
+ File.join(@repository_dir, "cookbooks"),
+ File.join(@repository_dir, "chef_repo2", "cookbooks"),
+ ]
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client1.json
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env1.json
+ environments/env3.json
+ nodes/
+ nodes/node1.json
+ nodes/node3.json
+ roles/
+ roles/role1.json
+ roles/role3.json
+ users/
+ users/user1.json
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client1.json
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ data_bags/bag3/
+ data_bags/bag3/item3.json
+ environments/
+ environments/env1.json
+ environments/env3.json
+ nodes/
+ nodes/node1.json
+ nodes/node3.json
+ roles/
+ roles/role1.json
+ roles/role3.json
+ users/
+ users/user1.json
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2/data_bags" do
+ before { cwd "chef_repo2/data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ bag3/
+ bag3/item3.json
+ EOM
+ end
+ end
+ end
+
+ context "when data_bag_path and chef_repo_path are set, and nothing else" do
+ before :each do
+ %w{client cookbook environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.data_bag_path = File.join(Chef::Config.chef_repo_path, "data_bags")
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
+ end
+
+ context "when cwd is at the top level" do
+ before { cwd "." }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2" do
+ before { cwd "chef_repo2" }
+ it "knife list --local -Rfp lists everything" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ clients/
+ clients/client3.json
+ cookbooks/
+ cookbooks/cookbook3/
+ cookbooks/cookbook3/metadata.rb
+ data_bags/
+ data_bags/bag/
+ data_bags/bag/item.json
+ environments/
+ environments/env3.json
+ nodes/
+ nodes/node3.json
+ roles/
+ roles/role3.json
+ users/
+ users/user3.json
+ EOM
+ end
+ end
+
+ context "when cwd is inside chef_repo2/data_bags" do
+ before { cwd "chef_repo2/data_bags" }
+ it "knife list --local -Rfp fails" do
+ knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
+ end
+ end
+ end
+
+ context "when data_bag_path is set and nothing else" do
+ include_context "default config options"
+
+ before :each do
+ %w{client cookbook environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Config.data_bag_path = File.join(@repository_dir, "data_bags")
+ end
+
+ it "knife list --local -Rfp / lists data bags" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /data_bags/
+ /data_bags/bag/
+ /data_bags/bag/item.json
+ EOM
+ end
+
+ it "knife list --local -Rfp /data_bags lists data bags" do
+ knife("list --local -Rfp /data_bags").should_succeed <<~EOM
+ /data_bags/bag/
+ /data_bags/bag/item.json
+ EOM
+ end
+
+ context "when cwd is inside the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife list --local -Rfp lists data bags" do
+ knife("list --local -Rfp").should_succeed <<~EOM
+ bag/
+ bag/item.json
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ context "when the repository _paths point to places that do not exist" do
+ before :each do
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "nowhere", object_name)
+ end
+ Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "nowhere")
+ end
+
+ it "knife list --local -Rfp / fails" do
+ knife("list --local -Rfp /").should_succeed ""
+ end
+
+ it "knife list --local -Rfp /data_bags fails" do
+ knife("list --local -Rfp /data_bags").should_fail("ERROR: /data_bags: No such file or directory\n")
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/chef_repository_file_system_spec.rb b/knife/spec/integration/chef_repository_file_system_spec.rb
new file mode 100644
index 0000000000..9a129dcb98
--- /dev/null
+++ b/knife/spec/integration/chef_repository_file_system_spec.rb
@@ -0,0 +1,200 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/list"
+require "chef/knife/show"
+
+describe "General chef_repo file system checks", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ context "directories and files that should/should not be ignored" do
+ when_the_repository "has empty roles, environments and data bag item directories" do
+ before do
+ directory "roles"
+ directory "environments"
+ directory "data_bags/bag1"
+ end
+
+ it "knife list --local -Rfp / returns them" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /data_bags/
+ /data_bags/bag1/
+ /environments/
+ /roles/
+ EOM
+ end
+ end
+
+ when_the_repository "has an empty data_bags directory" do
+ before { directory "data_bags" }
+
+ it "knife list --local / returns it" do
+ knife("list --local /").should_succeed "/data_bags\n"
+ end
+ end
+
+ when_the_repository "has an empty cookbook directory" do
+ before { directory "cookbooks/cookbook1" }
+
+ it "knife list --local -Rfp / does not return it" do
+ knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+ /cookbooks/
+ EOM
+ end
+ end
+
+ when_the_repository "has only empty cookbook subdirectories" do
+ before { directory "cookbooks/cookbook1/recipes" }
+
+ it "knife list --local -Rfp / does not return it" do
+ knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+ /cookbooks/
+ EOM
+ end
+ end
+
+ when_the_repository "has empty and non-empty cookbook subdirectories" do
+ before do
+ directory "cookbooks/cookbook1/recipes"
+ file "cookbooks/cookbook1/templates/default/x.txt", ""
+ end
+
+ it "knife list --local -Rfp / does not return the empty ones" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/templates/
+ /cookbooks/cookbook1/templates/default/
+ /cookbooks/cookbook1/templates/default/x.txt
+ EOM
+ end
+ end
+
+ when_the_repository "has only empty cookbook sub-sub-directories" do
+ before { directory "cookbooks/cookbook1/templates/default" }
+
+ it "knife list --local -Rfp / does not return it" do
+ knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+ /cookbooks/
+ EOM
+ end
+ end
+
+ when_the_repository "has empty cookbook sub-sub-directories alongside non-empty ones" do
+ before do
+ file "cookbooks/cookbook1/templates/default/x.txt", ""
+ directory "cookbooks/cookbook1/templates/rhel"
+ directory "cookbooks/cookbook1/files/default"
+ end
+
+ it "knife list --local -Rfp / does not return the empty ones" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/templates/
+ /cookbooks/cookbook1/templates/default/
+ /cookbooks/cookbook1/templates/default/x.txt
+ EOM
+ end
+ end
+
+ when_the_repository "has an extra schmenvironments directory" do
+ before do
+ directory "schmenvironments" do
+ file "_default.json", {}
+ end
+ end
+
+ it "knife list --local -Rfp / should NOT return it" do
+ knife("list --local -Rfp /").should_succeed ""
+ end
+ end
+
+ when_the_repository "has extra subdirectories and files under data bag items, roles, and environments" do
+ before do
+ directory "data_bags/bag1" do
+ file "item1.json", {}
+ file "item2.xml", ""
+ file "another_subdir/item.json", {}
+ end
+ directory "roles" do
+ file "role1.json", {}
+ file "role2.xml", ""
+ file "subdir/role.json", {}
+ end
+ directory "environments" do
+ file "environment1.json", {}
+ file "environment2.xml", ""
+ file "subdir/environment.json", {}
+ end
+ end
+
+ it "knife list --local -Rfp / should NOT return them" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /environments/
+ /environments/environment1.json
+ /roles/
+ /roles/role1.json
+ EOM
+ end
+ end
+
+ when_the_repository "has a file in cookbooks/" do
+ before { file "cookbooks/file", "" }
+ it "does not show up in list -Rfp" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ EOM
+ end
+ end
+
+ when_the_repository "has a file in data_bags/" do
+ before { file "data_bags/file", "" }
+ it "does not show up in list -Rfp" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /data_bags/
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook starting with ." do
+ before do
+ file "cookbooks/.svn/metadata.rb", ""
+ file "cookbooks/a.b/metadata.rb", ""
+ end
+ it "knife list does not show it" do
+ knife("list --local -fp /cookbooks").should_succeed "/cookbooks/a.b/\n"
+ end
+ end
+
+ when_the_repository "has a data bag starting with ." do
+ before do
+ file "data_bags/.svn/x.json", {}
+ file "data_bags/a.b/x.json", {}
+ end
+ it "knife list does not show it" do
+ knife("list --local -fp /data_bags").should_succeed "/data_bags/a.b/\n"
+ end
+ end
+end
diff --git a/knife/spec/integration/chefignore_spec.rb b/knife/spec/integration/chefignore_spec.rb
new file mode 100644
index 0000000000..f111cd56e1
--- /dev/null
+++ b/knife/spec/integration/chefignore_spec.rb
@@ -0,0 +1,301 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/list"
+require "chef/knife/show"
+
+describe "chefignore tests", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ when_the_repository "has lots of stuff in it" do
+ before do
+ file "roles/x.json", {}
+ file "environments/x.json", {}
+ file "data_bags/bag1/x.json", {}
+ file "cookbooks/cookbook1/x.json", {}
+ end
+
+ context "and has a chefignore everywhere except cookbooks" do
+ before do
+ chefignore = "x.json\nroles/x.json\nenvironments/x.json\ndata_bags/bag1/x.json\nbag1/x.json\ncookbooks/cookbook1/x.json\ncookbook1/x.json\n"
+ file "chefignore", chefignore
+ file "roles/chefignore", chefignore
+ file "environments/chefignore", chefignore
+ file "data_bags/chefignore", chefignore
+ file "data_bags/bag1/chefignore", chefignore
+ file "cookbooks/cookbook1/chefignore", chefignore
+ end
+
+ it "matching files and directories get ignored" do
+ # NOTE: many of the "chefignore" files should probably not show up
+ # themselves, but we have other tests that talk about that
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/chefignore
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/x.json
+ /environments/
+ /environments/x.json
+ /roles/
+ /roles/x.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook with only chefignored files" do
+ before do
+ file "cookbooks/cookbook1/templates/default/x.rb", ""
+ file "cookbooks/cookbook1/libraries/x.rb", ""
+ file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n"
+ end
+
+ it "the cookbook is not listed" do
+ knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+ /cookbooks/
+ EOM
+ end
+ end
+
+ when_the_repository "has multiple cookbooks" do
+ before do
+ file "cookbooks/cookbook1/x.json", {}
+ file "cookbooks/cookbook1/y.json", {}
+ file "cookbooks/cookbook2/x.json", {}
+ file "cookbooks/cookbook2/y.json", {}
+ end
+
+ context "and has a chefignore with filenames" do
+ before { file "cookbooks/chefignore", "x.json\n" }
+
+ it "matching files and directories get ignored in all cookbooks" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has a chefignore with wildcards" do
+ before do
+ file "cookbooks/chefignore", "x.*\n"
+ file "cookbooks/cookbook1/x.rb", ""
+ end
+
+ it "matching files and directories get ignored in all cookbooks" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has a chefignore with relative paths" do
+ before do
+ file "cookbooks/cookbook1/recipes/x.rb", ""
+ file "cookbooks/cookbook2/recipes/y.rb", ""
+ file "cookbooks/chefignore", "recipes/x.rb\n"
+ end
+
+ it "matching directories get ignored" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/x.json
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/recipes/
+ /cookbooks/cookbook2/recipes/y.rb
+ /cookbooks/cookbook2/x.json
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has a chefignore with subdirectories" do
+ before do
+ file "cookbooks/cookbook1/recipes/y.rb", ""
+ file "cookbooks/chefignore", "recipes\nrecipes/\n"
+ end
+
+ it "matching directories do NOT get ignored" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/recipes/
+ /cookbooks/cookbook1/recipes/y.rb
+ /cookbooks/cookbook1/x.json
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/x.json
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has a chefignore that ignores all files in a subdirectory" do
+ before do
+ file "cookbooks/cookbook1/templates/default/x.rb", ""
+ file "cookbooks/cookbook1/libraries/x.rb", ""
+ file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n"
+ end
+
+ it "ignores the subdirectory entirely" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/x.json
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/x.json
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has an empty chefignore" do
+ before do
+ file "cookbooks/chefignore", "\n"
+ end
+
+ it "nothing is ignored" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/x.json
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/x.json
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+
+ context "and has a chefignore with comments and empty lines" do
+ before do
+ file "cookbooks/chefignore", "\n\n # blah\n#\nx.json\n\n"
+ end
+
+ it "matching files and directories get ignored in all cookbooks" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/y.json
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/y.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has multiple cookbook paths" do
+ before :each do
+ Chef::Config.cookbook_path = [
+ File.join(Chef::Config.chef_repo_path, "cookbooks1"),
+ File.join(Chef::Config.chef_repo_path, "cookbooks2"),
+ ]
+ end
+
+ before do
+ file "cookbooks1/mycookbook/metadata.rb", ""
+ file "cookbooks1/mycookbook/x.json", {}
+ file "cookbooks2/yourcookbook/metadata.rb", ""
+ file "cookbooks2/yourcookbook/x.json", ""
+ end
+
+ context "and multiple chefignores" do
+ before do
+ file "cookbooks1/chefignore", "metadata.rb\n"
+ file "cookbooks2/chefignore", "x.json\n"
+ end
+ it "chefignores apply only to the directories they are in" do
+ knife("list --local -Rfp /").should_succeed <<~EOM
+ /cookbooks/
+ /cookbooks/mycookbook/
+ /cookbooks/mycookbook/x.json
+ /cookbooks/yourcookbook/
+ /cookbooks/yourcookbook/metadata.rb
+ EOM
+ end
+
+ context "and conflicting cookbooks" do
+ before do
+ file "cookbooks1/yourcookbook/metadata.rb", ""
+ file "cookbooks1/yourcookbook/x.json", ""
+ file "cookbooks1/yourcookbook/onlyincookbooks1.rb", ""
+ file "cookbooks2/yourcookbook/onlyincookbooks2.rb", ""
+ end
+
+ it "chefignores apply only to the winning cookbook" do
+ knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook\n")
+ /cookbooks/
+ /cookbooks/mycookbook/
+ /cookbooks/mycookbook/x.json
+ /cookbooks/yourcookbook/
+ /cookbooks/yourcookbook/onlyincookbooks1.rb
+ /cookbooks/yourcookbook/x.json
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook named chefignore" do
+ before do
+ file "cookbooks/chefignore/metadata.rb", {}
+ end
+ it "knife list -Rfp /cookbooks shows it" do
+ knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
+ /cookbooks/chefignore/
+ /cookbooks/chefignore/metadata.rb
+ EOM
+ end
+ end
+
+ when_the_repository "has multiple cookbook paths, one with a chefignore file and the other with a cookbook named chefignore" do
+ before do
+ file "cookbooks1/chefignore", ""
+ file "cookbooks1/blah/metadata.rb", ""
+ file "cookbooks2/chefignore/metadata.rb", ""
+ end
+ before :each do
+ Chef::Config.cookbook_path = [
+ File.join(Chef::Config.chef_repo_path, "cookbooks1"),
+ File.join(Chef::Config.chef_repo_path, "cookbooks2"),
+ ]
+ end
+ it "knife list -Rfp /cookbooks shows the chefignore cookbook" do
+ knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
+ /cookbooks/blah/
+ /cookbooks/blah/metadata.rb
+ /cookbooks/chefignore/
+ /cookbooks/chefignore/metadata.rb
+ EOM
+ end
+ end
+end
diff --git a/knife/spec/integration/client_bulk_delete_spec.rb b/knife/spec/integration/client_bulk_delete_spec.rb
new file mode 100644
index 0000000000..b7733f638d
--- /dev/null
+++ b/knife/spec/integration/client_bulk_delete_spec.rb
@@ -0,0 +1,131 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife client bulk delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some clients" do
+ before do
+ client "concat", {}
+ client "cons", {}
+ client "car", {}
+ client "cdr", {}
+ client "cat", {}
+ end
+
+ it "deletes all matching clients" do
+ knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
+ The following clients will be deleted:
+
+ car cat
+
+ Are you sure you want to delete these clients? (Y/N) Deleted client car
+ Deleted client cat
+ EOM
+
+ knife("client list").should_succeed <<~EOM
+ cdr
+ chef-validator
+ chef-webui
+ concat
+ cons
+ EOM
+ end
+
+ it "deletes all matching clients when unanchored" do
+ knife("client bulk delete ca.*", input: "Y").should_succeed <<~EOM
+ The following clients will be deleted:
+
+ car cat concat
+
+ Are you sure you want to delete these clients? (Y/N) Deleted client car
+ Deleted client cat
+ Deleted client concat
+ EOM
+
+ knife("client list").should_succeed <<~EOM
+ cdr
+ chef-validator
+ chef-webui
+ cons
+ EOM
+ end
+ end
+
+ when_the_chef_server "has a validator client" do
+ before do
+ client "cons", {}
+ client "car", {}
+ client "car-validator", { validator: true }
+ client "cdr", {}
+ client "cat", {}
+ end
+
+ it "refuses to delete a validator normally" do
+ knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
+ The following clients are validators and will not be deleted:
+
+ car-validator
+
+ You must specify --delete-validators to delete the validator clients
+ The following clients will be deleted:
+
+ car cat
+
+ Are you sure you want to delete these clients? (Y/N) Deleted client car
+ Deleted client cat
+ EOM
+
+ knife("client list").should_succeed <<~EOM
+ car-validator
+ cdr
+ chef-validator
+ chef-webui
+ cons
+ EOM
+ end
+
+ it "deletes a validator when told to" do
+ knife("client bulk delete ^ca.* -D", input: "Y\nY").should_succeed <<~EOM
+ The following validators will be deleted:
+
+ car-validator
+
+ Are you sure you want to delete these validators? (Y/N) Deleted client car-validator
+ The following clients will be deleted:
+
+ car cat
+
+ Are you sure you want to delete these clients? (Y/N) Deleted client car
+ Deleted client cat
+ EOM
+
+ knife("client list").should_succeed <<~EOM
+ cdr
+ chef-validator
+ chef-webui
+ cons
+ EOM
+ end
+ end
+end
diff --git a/knife/spec/integration/client_create_spec.rb b/knife/spec/integration/client_create_spec.rb
new file mode 100644
index 0000000000..3898ff9d24
--- /dev/null
+++ b/knife/spec/integration/client_create_spec.rb
@@ -0,0 +1,70 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "openssl"
+
+describe "knife client create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Created client[bah]\n" }
+
+ when_the_chef_server "is empty" do
+ it "creates a new client" do
+ knife("client create -k bah").should_succeed stderr: out
+ end
+
+ it "creates a new validator client" do
+ knife("client create -k --validator bah").should_succeed stderr: out
+ knife("client show bah").should_succeed <<~EOM
+ admin: false
+ chef_type: client
+ name: bah
+ validator: true
+ EOM
+ end
+
+ it "refuses to add an existing client" do
+ pending "Knife client create must not blindly overwrite an existing client"
+ knife("client create -k bah").should_succeed stderr: out
+ expect { knife("client create -k bah") }.to raise_error(Net::HTTPClientException)
+ end
+
+ it "saves the private key to a file" do
+ Dir.mktmpdir do |tgt|
+ knife("client create -f #{tgt}/bah.pem bah").should_succeed stderr: out
+ expect(File).to exist("#{tgt}/bah.pem")
+ end
+ end
+
+ it "reads the public key from a file" do
+ Dir.mktmpdir do |tgt|
+ key = OpenSSL::PKey::RSA.generate(1024)
+ File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) }
+ knife("client create -p #{tgt}/public.pem bah").should_succeed stderr: out
+ end
+ end
+
+ it "refuses to run if conflicting options are passed" do
+ knife("client create -p public.pem --prevent-keygen blah").should_fail stderr: "FATAL: You cannot pass --public-key and --prevent-keygen\n", stdout: /^USAGE.*/
+ end
+ end
+end
diff --git a/knife/spec/integration/client_delete_spec.rb b/knife/spec/integration/client_delete_spec.rb
new file mode 100644
index 0000000000..057561eaea
--- /dev/null
+++ b/knife/spec/integration/client_delete_spec.rb
@@ -0,0 +1,64 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife client delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some clients" do
+ before do
+ client "cons", {}
+ client "car", {}
+ client "car-validator", { validator: true }
+ client "cdr", {}
+ client "cat", {}
+ end
+
+ it "deletes a client" do
+ knife("client delete car", input: "Y").should_succeed <<~EOM
+ Do you really want to delete car? (Y/N) Deleted client[car]
+ EOM
+
+ knife("client list").should_succeed <<~EOM
+ car-validator
+ cat
+ cdr
+ chef-validator
+ chef-webui
+ cons
+ EOM
+ end
+
+ it "refuses to delete a validator normally" do
+ knife("client delete car-validator", input: "Y").should_fail exit_code: 2, stdout: "Do you really want to delete car-validator? (Y/N) ", stderr: <<~EOM
+ FATAL: You must specify --delete-validators to delete the validator client car-validator
+ EOM
+ end
+
+ it "deletes a validator correctly" do
+ knife("client delete car-validator -D", input: "Y").should_succeed <<~EOM
+ Do you really want to delete car-validator? (Y/N) Deleted client[car-validator]
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_key_create_spec.rb b/knife/spec/integration/client_key_create_spec.rb
new file mode 100644
index 0000000000..29b960111c
--- /dev/null
+++ b/knife/spec/integration/client_key_create_spec.rb
@@ -0,0 +1,66 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "openssl"
+
+describe "knife client key create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Created key: new" }
+
+ when_the_chef_server "has a client" do
+ before do
+ client "bah", {}
+ end
+
+ it "creates a new client key" do
+ knife("client key create -k new bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/
+ end
+
+ it "creates a new client key with an expiration date" do
+ date = "2017-12-31T23:59:59Z"
+ knife("client key create -k new -e #{date} bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/
+ knife("client key show bah new").should_succeed(/expiration_date:.*#{date}/)
+ end
+
+ it "refuses to add an already existing key" do
+ knife("client key create -k new bah")
+ expect { knife("client key create -k new bah") }.to raise_error(Net::HTTPClientException)
+ end
+
+ it "saves the private key to a file" do
+ Dir.mktmpdir do |tgt|
+ knife("client key create -f #{tgt}/bah.pem -k new bah").should_succeed stderr: /^#{out}/
+ expect(File).to exist("#{tgt}/bah.pem")
+ end
+ end
+
+ it "reads the public key from a file" do
+ Dir.mktmpdir do |tgt|
+ key = OpenSSL::PKey::RSA.generate(1024)
+ File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) }
+ knife("client key create -p #{tgt}/public.pem -k new bah").should_succeed stderr: /^#{out}/
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_key_delete_spec.rb b/knife/spec/integration/client_key_delete_spec.rb
new file mode 100644
index 0000000000..8c15377986
--- /dev/null
+++ b/knife/spec/integration/client_key_delete_spec.rb
@@ -0,0 +1,43 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife client key delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a client" do
+ before do
+ client "car", {}
+ end
+
+ it "deletes a client" do
+ out = "Do you really want to delete the key named new for the client named car? (Y/N) "
+ knife("client key create -k new car")
+ knife("client key delete car new", input: "Y").should_succeed stdout: out, stderr: <<~EOM
+ Deleted key named new for the client named car
+ EOM
+
+ knife("client key list car").should_succeed ""
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_key_list_spec.rb b/knife/spec/integration/client_key_list_spec.rb
new file mode 100644
index 0000000000..01e5b78585
--- /dev/null
+++ b/knife/spec/integration/client_key_list_spec.rb
@@ -0,0 +1,61 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "date"
+
+describe "knife client key list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:now) { DateTime.now }
+ let(:last_month) { (now << 1).strftime("%FT%TZ") }
+ let(:next_month) { (now >> 1).strftime("%FT%TZ") }
+
+ when_the_chef_server "has a client" do
+ before do
+ client "cons", {}
+ knife("client key create cons -k new")
+ knife("client key create cons -k next_month -e #{next_month}")
+ knife("client key create cons -k expired -e #{last_month}")
+ end
+
+ it "lists the keys for a client" do
+ knife("client key list cons").should_succeed "expired\nnew\nnext_month\n"
+ end
+
+ it "shows detailed output" do
+ knife("client key list -w cons").should_succeed <<~EOM
+ expired: http://127.0.0.1:8900/clients/cons/keys/expired (expired)
+ new: http://127.0.0.1:8900/clients/cons/keys/new
+ next_month: http://127.0.0.1:8900/clients/cons/keys/next_month
+ EOM
+ end
+
+ it "lists the expired keys for a client" do
+ knife("client key list -e cons").should_succeed "expired\n"
+ end
+
+ it "lists the unexpired keys for a client" do
+ knife("client key list -n cons").should_succeed "new\nnext_month\n"
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_key_show_spec.rb b/knife/spec/integration/client_key_show_spec.rb
new file mode 100644
index 0000000000..05024d40b2
--- /dev/null
+++ b/knife/spec/integration/client_key_show_spec.rb
@@ -0,0 +1,45 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "date"
+
+describe "knife client key show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:now) { DateTime.now }
+ let(:last_month) { (now << 1).strftime("%FT%TZ") }
+ let(:next_month) { (now >> 1).strftime("%FT%TZ") }
+
+ when_the_chef_server "has a client" do
+ before do
+ client "cons", {}
+ knife("client key create cons -k new")
+ knife("client key create cons -k next_month -e #{next_month}")
+ knife("client key create cons -k expired -e #{last_month}")
+ end
+
+ it "shows a key for a client" do
+ knife("client key show cons new").should_succeed stdout: /.*name:.*new/
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_list_spec.rb b/knife/spec/integration/client_list_spec.rb
new file mode 100644
index 0000000000..7668b9e455
--- /dev/null
+++ b/knife/spec/integration/client_list_spec.rb
@@ -0,0 +1,49 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife client list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some clients" do
+ before do
+ client "cons", {}
+ client "car", {}
+ client "car-validator", { validator: true }
+ client "cdr", {}
+ client "cat", {}
+ end
+
+ it "lists the clients" do
+ knife("client list").should_succeed <<~EOM
+ car
+ car-validator
+ cat
+ cdr
+ chef-validator
+ chef-webui
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/client_show_spec.rb b/knife/spec/integration/client_show_spec.rb
new file mode 100644
index 0000000000..39a107e37f
--- /dev/null
+++ b/knife/spec/integration/client_show_spec.rb
@@ -0,0 +1,37 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife client show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a client" do
+ before do
+ client "cons", {}
+ end
+
+ it "shows a client" do
+ knife("client show cons").should_succeed stdout: /.*name:.*cons/
+ end
+
+ end
+end
diff --git a/knife/spec/integration/commands_spec.rb b/knife/spec/integration/commands_spec.rb
new file mode 100644
index 0000000000..a9626c38fc
--- /dev/null
+++ b/knife/spec/integration/commands_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Marc Pardise (<marc@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/knife"
+
+Chef::Knife.subcommand_loader.load_commands
+commands = Chef::Knife::SubcommandLoader.generate_hash["_autogenerated_command_paths"]["plugins_paths"].keys
+
+# Directly execute each support knife command
+context "Command Sanity Check: executing ", :workstation do
+ describe "bundle exec knife" do
+ commands.each do |command|
+ command_name = command.gsub("_", " ")
+ modified_command, expected_result = case command_name
+ when /knife/
+ # because rspec is the actual executable running, the option parser error message
+ # is invalid from within the test.
+ next
+ when /config (use|get|list) profile.*/
+ # hyphenated special cases
+ [command_name, /^USAGE: knife config #{$1}-profile.*/]
+ when /(role|node|env) (env )?run list(.*)/
+ # underscored special cases...
+ env_part = $2.nil? ? "" : "env_"
+ ["#{$1} #{$2}run_list#{$3}", /^USAGE: knife #{$1} #{env_part}run_list#{$3}.*/]
+ else
+ [ command_name, /^USAGE: knife #{command_name}.*/]
+ end
+
+ # By using bundle exec knife instead of directly loading the command class or using the knife() helper,
+ # we ensure that this is a valid end-to-end test. This operates on the assumption
+ # that we continue to require the command class to be fully loaded so that it can handle the parsing of
+ # its own options.
+ full_command = "#{modified_command} --invalid-option outputs usage for '#{modified_command}' to stdout"
+ it full_command do
+ result = `bundle exec knife #{full_command}`
+ expect(result).to match(expected_result)
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/common_options_spec.rb b/knife/spec/integration/common_options_spec.rb
new file mode 100644
index 0000000000..7796bf9923
--- /dev/null
+++ b/knife/spec/integration/common_options_spec.rb
@@ -0,0 +1,174 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/raw"
+
+describe "knife common options", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ before do
+ # Allow this for testing the various port binding stuffs. Remove when
+ # we kill off --listen.
+ Chef::Config.treat_deprecation_warnings_as_errors(false)
+ end
+
+ let(:local_listen_warning) { /\Awarn:.*local.*listen.*$/im }
+
+ when_the_repository "has a node" do
+ before { file "nodes/x.json", {} }
+
+ context "When chef_zero.enabled is true" do
+ before(:each) do
+ Chef::Config.chef_zero.enabled = true
+ end
+
+ it "knife raw /nodes/x should retrieve the node in socketless mode" do
+ Chef::Config.treat_deprecation_warnings_as_errors(true)
+ knife("raw /nodes/x").should_succeed( /"name": "x"/ )
+ end
+
+ it "knife raw /nodes/x should retrieve the node" do
+ knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ end
+
+ context "And chef_zero.port is 9999" do
+ before(:each) { Chef::Config.chef_zero.port = 9999 }
+
+ it "knife raw /nodes/x should retrieve the node" do
+ knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
+ end
+ end
+
+ # 0.0.0.0 is not a valid address to bind to on windows.
+ context "And chef_zero.host is 0.0.0.0", :unix_only do
+ before(:each) { Chef::Config.chef_zero.host = "0.0.0.0" }
+
+ it "knife raw /nodes/x should retrieve the role" do
+ knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ end
+ end
+
+ context "and there is a private key" do
+ before do
+ file "mykey.pem", <<~EOM
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
+ 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
+ NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
+ 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
+ AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
+ HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
+ 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
+ zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
+ k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
+ i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
+ G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
+ ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
+ awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
+ 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
+ g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
+ Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
+ HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
+ V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
+ fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
+ lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
+ c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
+ fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
+ YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
+ syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
+ +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
+ -----END RSA PRIVATE KEY-----
+ EOM
+ end
+
+ it "knife raw /nodes/x should retrieve the node" do
+ knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ end
+ end
+ end
+
+ it "knife raw -z /nodes/x retrieves the node in socketless mode" do
+ Chef::Config.treat_deprecation_warnings_as_errors(true)
+ knife("raw -z /nodes/x").should_succeed( /"name": "x"/ )
+ end
+
+ it "knife raw -z /nodes/x retrieves the node" do
+ knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ end
+
+ it "knife raw --local-mode /nodes/x retrieves the node" do
+ knife("raw --local-mode --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ end
+
+ it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do
+ knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
+ end
+
+ context "when the default port (8889) is already bound" do
+ before :each do
+
+ @server = ChefZero::Server.new(host: "localhost", port: 8889)
+ @server.start_background
+ rescue Errno::EADDRINUSE
+ # OK. Don't care who has it in use, as long as *someone* does.
+
+ end
+ after :each do
+ @server.stop if @server
+ end
+
+ it "knife raw -z /nodes/x retrieves the node" do
+ knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(URI(Chef::Config.chef_server_url).port).to be > 8889
+ end
+ end
+
+ context "when port 9999 is already bound" do
+ before :each do
+
+ @server = ChefZero::Server.new(host: "localhost", port: 9999)
+ @server.start_background
+ rescue Errno::EADDRINUSE
+ # OK. Don't care who has it in use, as long as *someone* does.
+
+ end
+ after :each do
+ @server.stop if @server
+ end
+
+ it "knife raw -z --chef-zero-port=9999-20000 /nodes/x" do
+ knife("raw -z --chef-zero-port=9999-20000 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(URI(Chef::Config.chef_server_url).port).to be > 9999
+ end
+
+ it "knife raw -z --chef-zero-port=9999-9999,19423" do
+ knife("raw -z --chef-zero-port=9999-9999,19423 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(URI(Chef::Config.chef_server_url).port).to be == 19423
+ end
+ end
+
+ it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do
+ knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
+ expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
+ end
+ end
+end
diff --git a/knife/spec/integration/config_list_spec.rb b/knife/spec/integration/config_list_spec.rb
new file mode 100644
index 0000000000..5193608f36
--- /dev/null
+++ b/knife/spec/integration/config_list_spec.rb
@@ -0,0 +1,220 @@
+#
+# Copyright 2018, Noah Kantrowitz
+#
+# 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife config list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_repository("has a custom env") do
+ let(:cmd_args) { [] }
+ let(:knife_list) do
+ knife("config", "list", *cmd_args, instance_filter: lambda { |instance|
+ # Fake the failsafe check because this command doesn't actually process knife.rb.
+ $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
+ allow(File).to receive(:file?).and_call_original
+ })
+ end
+ subject { knife_list.stdout }
+
+ around do |ex|
+ # Store and reset the value of some env vars.
+ old_home = ENV["HOME"]
+ old_wd = Dir.pwd
+ # Clear these out because they are cached permanently.
+ ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
+ Chef::Knife::ConfigList.reset_config_loader!
+ begin
+ ex.run
+ ensure
+ ENV["HOME"] = old_home
+ Dir.chdir(old_wd)
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ end
+ end
+
+ before do
+ # Always run from the temp folder. This can't be in the `around` block above
+ # because it has to run after the before set in the "with a chef repo" shared context.
+ directory("repo")
+ Dir.chdir(path_to("repo"))
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ ENV["HOME"] = path_to(".")
+ allow(TTY::Screen).to receive(:width).and_return(200)
+ end
+
+ # NOTE: The funky formatting with # at the end of the line of some of the
+ # output examples are because of how the format strings are built, there is
+ # substantial trailing whitespace in most cases which many editors "helpfully" remove.
+
+ context "with no credentials file" do
+ subject { knife_list.stderr }
+ it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
+ end
+
+ context "with an empty credentials file" do
+ before { file(".chef/credentials", "") }
+ subject { knife_list.stderr }
+ it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
+ end
+
+ context "with a simple default profile" do
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to eq <<~EOH.delete("#") }
+ Profile Client Key Server #
+ --------------------------------------------------------------------------------#
+ *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
+ EOH
+ end
+
+ context "with multiple profiles" do
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+
+ [prod]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/prod"
+
+ [qa]
+ client_name = "qauser"
+ client_key = "~/src/qauser.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to eq <<~EOH.delete("#") }
+ Profile Client Key Server #
+ --------------------------------------------------------------------------------#
+ *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
+ prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
+ qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
+ EOH
+ end
+
+ context "with a non-default active profile" do
+ let(:cmd_args) { %w{--profile prod} }
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+
+ [prod]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/prod"
+
+ [qa]
+ client_name = "qauser"
+ client_key = "~/src/qauser.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to eq <<~EOH.delete("#") }
+ Profile Client Key Server #
+ --------------------------------------------------------------------------------#
+ default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
+ *prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
+ qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
+ EOH
+ end
+
+ context "with a bad profile as an active profile" do
+ let(:cmd_args) { %w{--profile production} }
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+
+ [prod]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/prod"
+
+ [qa]
+ client_name = "qauser"
+ client_key = "~/src/qauser.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to eq <<~EOH.delete("#") }
+ Profile Client Key Server #
+ --------------------------------------------------------------------------------#
+ default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
+ prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
+ qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
+ EOH
+ end
+
+ context "with a minimal profile" do
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to match %r{^*default .*? https://example.com/organizations/testorg} }
+ end
+
+ context "with -i" do
+ let(:cmd_args) { %w{-i} }
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it { is_expected.to eq <<~EOH.delete("#") }
+ Profile Client Key Server #
+ --------------------------------------------------------------#
+ *default https://example.com/organizations/testorg #
+ EOH
+ end
+
+ context "with --format=json" do
+ let(:cmd_args) { %w{--format=json node_name} }
+ before { file(".chef/credentials", <<~EOH) }
+ [default]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+
+ [prod]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/prod"
+
+ [qa]
+ client_name = "qauser"
+ client_key = "~/src/qauser.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it {
+ expect(JSON.parse(subject)).to eq [
+ { "profile" => "default", "active" => true, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/testorg" },
+ { "profile" => "prod", "active" => false, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/prod" },
+ { "profile" => "qa", "active" => false, "client_name" => "qauser", "client_key" => path_to("src/qauser.pem"), "server_url" => "https://example.com/organizations/testorg" },
+ ]
+ }
+ end
+ end
+end
diff --git a/knife/spec/integration/config_show_spec.rb b/knife/spec/integration/config_show_spec.rb
new file mode 100644
index 0000000000..e11d001df9
--- /dev/null
+++ b/knife/spec/integration/config_show_spec.rb
@@ -0,0 +1,192 @@
+#
+# Copyright 2018, Noah Kantrowitz
+#
+# 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife config show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:cmd_args) { [] }
+
+ when_the_repository("has a custom env") do
+ subject do
+ cmd = knife("config", "show", *cmd_args, instance_filter: lambda { |instance|
+ # Clear the stub set up in KnifeSupport.
+ allow(File).to receive(:file?).and_call_original
+ # Lies, damn lies, and config files. We need to allow normal config loading
+ # behavior to be able to test stuff.
+ instance.config.delete(:config_file)
+ $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
+ })
+ cmd.stdout
+ end
+
+ around do |ex|
+ # Store and reset the value of some env vars.
+ old_chef_home = ENV["CHEF_HOME"]
+ old_knife_home = ENV["KNIFE_HOME"]
+ old_home = ENV["HOME"]
+ old_wd = Dir.pwd
+ ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
+ # Clear these out because they are cached permanently.
+ ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
+ Chef::Knife::ConfigShow.reset_config_loader!
+ begin
+ ex.run
+ ensure
+ ENV["CHEF_HOME"] = old_chef_home
+ ENV["KNIFE_HOME"] = old_knife_home
+ ENV["HOME"] = old_home
+ Dir.chdir(old_wd)
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ ChefConfig::PathHelper.per_tool_home_environment = nil
+ end
+ end
+
+ before do
+ # Always run from the temp folder. This can't be in the `around` block above
+ # because it has to run after the before set in the "with a chef repo" shared context.
+ directory("repo")
+ Dir.chdir(path_to("repo"))
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ ENV["HOME"] = path_to(".")
+ end
+
+ context "with a global knife.rb" do
+ before { file(".chef/knife.rb", "node_name 'one'\n") }
+
+ it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) }
+ it { is_expected.to match(/^node_name:\s+one$/) }
+ end
+
+ context "with a repo knife.rb" do
+ before { file("repo/.chef/knife.rb", "node_name 'two'\n") }
+
+ it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) }
+ it { is_expected.to match(/^node_name:\s+two$/) }
+ end
+
+ context "with both knife.rb" do
+ before do
+ file(".chef/knife.rb", "node_name 'one'\n")
+ file("repo/.chef/knife.rb", "node_name 'two'\n")
+ end
+
+ it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) }
+ it { is_expected.to match(/^node_name:\s+two$/) }
+ end
+
+ context "with a credentials file" do
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) }
+ it { is_expected.to match(/^node_name:\s+three$/) }
+ end
+
+ context "with a credentials file and knife.rb" do
+ before do
+ file(".chef/knife.rb", "node_name 'one'\n")
+ file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
+ end
+
+ it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) }
+ it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) }
+ it { is_expected.to match(/^node_name:\s+one$/) }
+ end
+
+ context "with a config dot d files" do
+ before { file(".chef/config.d/abc.rb", "node_name 'one'\n") }
+
+ it { is_expected.to match(%r{^Loading from .d/ configuration file .*/#{File.basename(path_to("."))}/.chef/config.d/abc.rb$}) }
+ it { is_expected.to match(/^node_name:\s+one$/) }
+ end
+
+ context "with a credentials file and CHEF_HOME" do
+ before do
+ file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
+ file("foo/.chef/credentials", "[default]\nclient_name = \"four\"\n")
+ ENV["CHEF_HOME"] = path_to("foo")
+ end
+
+ it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/foo/.chef/credentials$}) }
+ it { is_expected.to match(/^node_name:\s+four$/) }
+ end
+
+ context "with a credentials file and KNIFE_HOME" do
+ before do
+ file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
+ file("bar/.chef/credentials", "[default]\nclient_name = \"four\"\n")
+ ENV["KNIFE_HOME"] = path_to("bar")
+ end
+
+ it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/bar/.chef/credentials$}) }
+ it { is_expected.to match(/^node_name:\s+four$/) }
+ end
+
+ context "with single argument" do
+ let(:cmd_args) { %w{node_name} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { is_expected.to match(/^node_name:\s+three\Z/) }
+ end
+
+ context "with two arguments" do
+ let(:cmd_args) { %w{node_name client_key} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\nclient_key = \"three.pem\"") }
+
+ it { is_expected.to match(%r{^client_key:\s+\S*/.chef/three.pem\nnode_name:\s+three\Z}) }
+ end
+
+ context "with a dotted argument" do
+ let(:cmd_args) { %w{knife.ssh_user} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n[default.knife]\nssh_user = \"foo\"\n") }
+
+ it { is_expected.to match(/^knife.ssh_user:\s+foo\Z/) }
+ end
+
+ context "with regex argument" do
+ let(:cmd_args) { %w{/name/} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { is_expected.to match(/^node_name:\s+three\Z/) }
+ end
+
+ context "with --all" do
+ let(:cmd_args) { %w{-a /key_contents/} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { is_expected.to match(/^client_key_contents:\s+\nvalidation_key_contents:\s+\Z/) }
+ end
+
+ context "with --raw" do
+ let(:cmd_args) { %w{-r node_name} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { is_expected.to eq("three\n") }
+ end
+
+ context "with --format=json" do
+ let(:cmd_args) { %w{--format=json node_name} }
+ before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
+
+ it { expect(JSON.parse(subject)).to eq({ "node_name" => "three" }) }
+ end
+ end
+end
diff --git a/knife/spec/integration/config_use_spec.rb b/knife/spec/integration/config_use_spec.rb
new file mode 100644
index 0000000000..4a982bc0bd
--- /dev/null
+++ b/knife/spec/integration/config_use_spec.rb
@@ -0,0 +1,198 @@
+#
+# Copyright 2018, Noah Kantrowitz
+#
+# 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife config use", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:cmd_args) { [] }
+
+ when_the_repository("has a custom env") do
+ let(:knife_use) do
+ knife("config", "use", *cmd_args, instance_filter: lambda { |instance|
+ # Fake the failsafe check because this command doesn't actually process knife.rb.
+ $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
+ allow(File).to receive(:file?).and_call_original
+ })
+ end
+
+ subject { knife_use.stdout }
+
+ around do |ex|
+ # Store and reset the value of some env vars.
+ old_chef_home = ENV["CHEF_HOME"]
+ old_knife_home = ENV["KNIFE_HOME"]
+ old_home = ENV["HOME"]
+ old_wd = Dir.pwd
+ ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
+ # Clear these out because they are cached permanently.
+ ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
+ Chef::Knife::ConfigUse.reset_config_loader!
+ begin
+ ex.run
+ ensure
+ ENV["CHEF_HOME"] = old_chef_home
+ ENV["KNIFE_HOME"] = old_knife_home
+ ENV["HOME"] = old_home
+ Dir.chdir(old_wd)
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ ChefConfig::PathHelper.per_tool_home_environment = nil
+ end
+ end
+
+ before do
+ # Always run from the temp folder. This can't be in the `around` block above
+ # because it has to run after the before set in the "with a chef repo" shared context.
+ directory("repo")
+ Dir.chdir(path_to("repo"))
+ ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
+ ENV["HOME"] = path_to(".")
+ end
+
+ context "with no argument" do
+ context "with no configuration" do
+ it { is_expected.to eq "default\n" }
+ end
+
+ context "with --profile" do
+ let(:cmd_args) { %w{--profile production} }
+ it { is_expected.to eq "production\n" }
+ end
+
+ context "with an environment variable" do
+ around do |ex|
+ old_chef_profile = ENV["CHEF_PROFILE"]
+ begin
+ ENV["CHEF_PROFILE"] = "staging"
+ ex.run
+ ensure
+ ENV["CHEF_PROFILE"] = old_chef_profile
+ end
+ end
+
+ it { is_expected.to eq "staging\n" }
+ end
+
+ context "with a context file" do
+ before { file(".chef/context", "development\n") }
+ it { is_expected.to eq "development\n" }
+ end
+
+ context "with a context file under $CHEF_HOME" do
+ before do
+ file("chefhome/.chef/context", "other\n")
+ ENV["CHEF_HOME"] = path_to("chefhome")
+ end
+
+ it { is_expected.to eq "other\n" }
+ end
+
+ context "with a context file under $KNIFE_HOME" do
+ before do
+ file("knifehome/.chef/context", "other\n")
+ ENV["KNIFE_HOME"] = path_to("knifehome")
+ end
+
+ it { is_expected.to eq "other\n" }
+ end
+ end
+
+ context "with an argument" do
+ let(:cmd_args) { %w{production} }
+ before { file(".chef/credentials", <<~EOH) }
+ [production]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ it do
+ is_expected.to eq "Set default profile to production\n"
+ expect(File.read(path_to(".chef/context"))).to eq "production\n"
+ end
+ end
+
+ context "with no credentials file" do
+ let(:cmd_args) { %w{production} }
+ subject { knife_use.stderr }
+ it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
+ end
+
+ context "with an empty credentials file" do
+ let(:cmd_args) { %w{production} }
+ before { file(".chef/credentials", "") }
+ subject { knife_use.stderr }
+ it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
+ end
+
+ context "with an wrong argument" do
+ let(:cmd_args) { %w{staging} }
+ before { file(".chef/credentials", <<~EOH) }
+ [production]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ subject { knife_use }
+ it { expect { subject }.to raise_error ChefConfig::ConfigurationError, "Profile staging doesn't exist. Please add it to #{path_to(".chef/credentials")} and if it is profile with DNS name check that you are not missing single quotes around it as per docs https://docs.chef.io/workstation/knife_setup/#knife-profiles." }
+ end
+
+ context "with $CHEF_HOME" do
+ let(:cmd_args) { %w{staging} }
+ before do
+ ENV["CHEF_HOME"] = path_to("chefhome"); file("chefhome/tmp", "")
+ file("chefhome/.chef/credentials", <<~EOH
+ [staging]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ )
+ end
+
+ it do
+ is_expected.to eq "Set default profile to staging\n"
+ expect(File.read(path_to("chefhome/.chef/context"))).to eq "staging\n"
+ expect(File.exist?(path_to(".chef/context"))).to be_falsey
+ end
+ end
+
+ context "with $KNIFE_HOME" do
+ let(:cmd_args) { %w{development} }
+
+ before do
+ ENV["KNIFE_HOME"] = path_to("knifehome"); file("knifehome/tmp", "")
+ file("knifehome/.chef/credentials", <<~EOH
+ [development]
+ client_name = "testuser"
+ client_key = "testkey.pem"
+ chef_server_url = "https://example.com/organizations/testorg"
+ EOH
+ )
+ end
+
+ it do
+ is_expected.to eq "Set default profile to development\n"
+ expect(File.read(path_to("knifehome/.chef/context"))).to eq "development\n"
+ expect(File.exist?(path_to(".chef/context"))).to be_falsey
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/cookbook_api_ipv6_spec.rb b/knife/spec/integration/cookbook_api_ipv6_spec.rb
new file mode 100644
index 0000000000..5d0ce0707f
--- /dev/null
+++ b/knife/spec/integration/cookbook_api_ipv6_spec.rb
@@ -0,0 +1,113 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/mixin/shell_out"
+
+describe "Knife cookbook API integration with IPv6", :workstation, :not_supported_on_gce do
+ include IntegrationSupport
+ include Chef::Mixin::ShellOut
+
+ when_the_chef_server "is bound to IPv6" do
+ let(:chef_zero_opts) { { host: "::1" } }
+
+ let(:client_key) do
+ <<~END_VALIDATION_PEM
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
+ 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
+ NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
+ 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
+ AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
+ HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
+ 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
+ zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
+ k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
+ i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
+ G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
+ ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
+ awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
+ 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
+ g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
+ Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
+ HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
+ V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
+ fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
+ lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
+ c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
+ fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
+ YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
+ syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
+ +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
+ -----END RSA PRIVATE KEY-----
+ END_VALIDATION_PEM
+ end
+
+ let(:cache_path) do
+ Dir.mktmpdir
+ end
+
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..", "knife", "bin") }
+ let(:knife) { "ruby '#{chef_dir}/knife'" }
+
+ let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" }
+
+ # Some Solaris test platforms are too old for IPv6. These tests should not
+ # otherwise be platform dependent, so exclude solaris
+ context "and the chef_server_url contains an IPv6 literal", :not_supported_on_solaris do
+
+ # This provides helper functions we need such as #path_to()
+ when_the_repository "has the cookbook to be uploaded" do
+
+ let(:knife_rb_content) do
+ <<~END_CLIENT_RB
+ chef_server_url "http://[::1]:8900"
+ syntax_check_cache_path '#{cache_path}'
+ client_key '#{path_to("config/knifeuser.pem")}'
+ node_name 'whoisthisis'
+ cookbook_path '#{CHEF_SPEC_DATA}/cookbooks'
+ END_CLIENT_RB
+ end
+
+ before do
+ file "config/knife.rb", knife_rb_content
+ file "config/knifeuser.pem", client_key
+ end
+
+ it "successfully uploads a cookbook" do
+ shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir)
+ versions_list_json = Chef::HTTP::Simple.new("http://[::1]:8900").get("/cookbooks/apache2", "accept" => "application/json")
+ versions_list = Chef::JSONCompat.from_json(versions_list_json)
+ expect(versions_list["apache2"]["versions"]).not_to be_empty
+ end
+
+ context "and the cookbook has been uploaded to the server" do
+ before do
+ shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir)
+ end
+
+ it "downloads the cookbook" do
+ shell_out!("#{knife} cookbook download apache2 #{knife_config_flag} -d #{cache_path}", cwd: chef_dir)
+ expect(Dir["#{cache_path}/*"].map { |entry| File.basename(entry) }).to include("apache2-0.0.1")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/cookbook_bulk_delete_spec.rb b/knife/spec/integration/cookbook_bulk_delete_spec.rb
new file mode 100644
index 0000000000..0e791f5a1e
--- /dev/null
+++ b/knife/spec/integration/cookbook_bulk_delete_spec.rb
@@ -0,0 +1,65 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/cookbook_bulk_delete"
+
+describe "knife cookbook bulk delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "foo", "1.0.0"
+ cookbook "foo", "0.6.5"
+ cookbook "fox", "0.6.0"
+ cookbook "fox", "0.6.5"
+ cookbook "fax", "0.6.0"
+ cookbook "zfa", "0.6.5"
+ end
+
+ # rubocop:disable Layout/TrailingWhitespace
+ it "knife cookbook bulk delete deletes all matching cookbooks" do
+ stdout = <<~EOM
+ All versions of the following cookbooks will be deleted:
+
+ foo fox
+
+ Do you really want to delete these cookbooks? (Y/N)
+ EOM
+
+ stderr = <<~EOM
+ Deleted cookbook foo [1.0.0]
+ Deleted cookbook foo [0.6.5]
+ Deleted cookbook fox [0.6.5]
+ Deleted cookbook fox [0.6.0]
+ EOM
+
+ knife("cookbook bulk delete ^fo.*", input: "Y").should_succeed(stderr: stderr, stdout: stdout)
+
+ knife("cookbook list -a").should_succeed <<~EOM
+ fax 0.6.0
+ zfa 0.6.5
+ EOM
+ end
+ # rubocop:enable Layout/TrailingWhitespace
+
+ end
+end
diff --git a/knife/spec/integration/cookbook_download_spec.rb b/knife/spec/integration/cookbook_download_spec.rb
new file mode 100644
index 0000000000..589417126c
--- /dev/null
+++ b/knife/spec/integration/cookbook_download_spec.rb
@@ -0,0 +1,72 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/cookbook_download"
+require "tmpdir"
+
+describe "knife cookbook download", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:tmpdir) { Dir.mktmpdir }
+
+ when_the_chef_server "has only one cookbook" do
+ before do
+ cookbook "x", "1.0.1"
+ end
+
+ it "knife cookbook download downloads the latest version" do
+ knife("cookbook download -d #{tmpdir} x").should_succeed stderr: <<~EOM
+ Downloading x cookbook version 1.0.1
+ Downloading root_files
+ Cookbook downloaded to #{tmpdir}/x-1.0.1
+ EOM
+ end
+
+ it "knife cookbook download with a version downloads the specified version" do
+ knife("cookbook download -d #{tmpdir} x 1.0.1").should_succeed stderr: <<~EOM
+ Downloading x cookbook version 1.0.1
+ Downloading root_files
+ Cookbook downloaded to #{tmpdir}/x-1.0.1
+ EOM
+ end
+
+ it "knife cookbook download with an unknown version raises an error" do
+ expect { knife("cookbook download -d #{tmpdir} x 1.0.0") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+
+ when_the_chef_server "has multiple cookbook versions" do
+ before do
+ cookbook "x", "1.0.1"
+ cookbook "x", "1.0.0"
+ end
+
+ it "knife cookbook download with no version prompts" do
+ knife("cookbook download -d #{tmpdir} x", input: "2\n").should_succeed(stderr: <<~EOM, stdout: "Which version do you want to download?\n1. x 1.0.0\n2. x 1.0.1\n\n"
+ Downloading x cookbook version 1.0.1
+ Downloading root_files
+ Cookbook downloaded to #{tmpdir}/x-1.0.1
+ EOM
+ )
+ end
+ end
+end
diff --git a/knife/spec/integration/cookbook_list_spec.rb b/knife/spec/integration/cookbook_list_spec.rb
new file mode 100644
index 0000000000..e712ae3235
--- /dev/null
+++ b/knife/spec/integration/cookbook_list_spec.rb
@@ -0,0 +1,55 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/cookbook_list"
+
+describe "knife cookbook list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "x", "1.0.0"
+ cookbook "x", "0.6.5"
+ cookbook "x", "0.6.0"
+ cookbook "y", "0.6.5"
+ cookbook "y", "0.6.0"
+ cookbook "z", "0.6.5"
+ end
+
+ it "knife cookbook list shows all the cookbooks" do
+ knife("cookbook list").should_succeed <<~EOM
+ x 1.0.0
+ y 0.6.5
+ z 0.6.5
+ EOM
+ end
+
+ it "knife cookbook list -a shows all the versions of all the cookbooks" do
+ knife("cookbook list -a").should_succeed <<~EOM
+ x 1.0.0 0.6.5 0.6.0
+ y 0.6.5 0.6.0
+ z 0.6.5
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/cookbook_show_spec.rb b/knife/spec/integration/cookbook_show_spec.rb
new file mode 100644
index 0000000000..d8c2e38f64
--- /dev/null
+++ b/knife/spec/integration/cookbook_show_spec.rb
@@ -0,0 +1,149 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/cookbook_show"
+
+describe "knife cookbook show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "file 'n'", "x.rb" => "" } }
+ cookbook "x", "0.6.5"
+ end
+
+ it "knife cookbook show x shows all the versions" do
+ knife("cookbook show x").should_succeed "x 1.0.0 0.6.5\n"
+ end
+
+ # rubocop:disable Layout/TrailingWhitespace
+ it "knife cookbook show x 1.0.0 shows the correct version" do
+ knife("cookbook show x 1.0.0").should_succeed <<~EOM
+ cookbook_name: x
+ frozen?: false
+ metadata:
+ chef_versions:
+ dependencies:
+ description:
+ eager_load_libraries: true
+ gems:
+ issues_url:
+ license: All rights reserved
+ long_description:
+ maintainer:
+ maintainer_email:
+ name: x
+ ohai_versions:
+ platforms:
+ privacy: false
+ providing:
+ x: >= 0.0.0
+ x::x: >= 0.0.0
+ recipes:
+ x:
+ x::x:
+ source_url:
+ version: 1.0.0
+ name: x-1.0.0
+ recipes:
+ checksum: 4631b34cf58de10c5ef1304889941b2e
+ name: recipes/default.rb
+ path: recipes/default.rb
+ specificity: default
+ url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e
+
+ checksum: d41d8cd98f00b204e9800998ecf8427e
+ name: recipes/x.rb
+ path: recipes/x.rb
+ specificity: default
+ url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e
+ root_files:
+ checksum: 8226671f751ba102dea6a6b6bd32fa8d
+ name: metadata.rb
+ path: metadata.rb
+ specificity: default
+ url: http://127.0.0.1:8900/file_store/checksums/8226671f751ba102dea6a6b6bd32fa8d
+ version: 1.0.0
+ EOM
+ end
+
+ it "knife cookbook show x 1.0.0 metadata shows the metadata" do
+ knife("cookbook show x 1.0.0 metadata").should_succeed <<~EOM
+ chef_versions:
+ dependencies:
+ description:
+ eager_load_libraries: true
+ gems:
+ issues_url:
+ license: All rights reserved
+ long_description:
+ maintainer:
+ maintainer_email:
+ name: x
+ ohai_versions:
+ platforms:
+ privacy: false
+ providing:
+ x: >= 0.0.0
+ x::x: >= 0.0.0
+ recipes:
+ x:
+ x::x:
+ source_url:
+ version: 1.0.0
+ EOM
+ end
+
+ it "knife cookbook show x 1.0.0 recipes shows all the recipes" do
+ knife("cookbook show x 1.0.0 recipes").should_succeed <<~EOM
+ checksum: 4631b34cf58de10c5ef1304889941b2e
+ name: recipes/default.rb
+ path: recipes/default.rb
+ specificity: default
+ url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e
+
+ checksum: d41d8cd98f00b204e9800998ecf8427e
+ name: recipes/x.rb
+ path: recipes/x.rb
+ specificity: default
+ url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e
+ EOM
+ end
+ # rubocop:enable Layout/TrailingWhitespace
+
+ it "knife cookbook show x 1.0.0 recipes default.rb shows the default recipe" do
+ knife("cookbook show x 1.0.0 recipes default.rb").should_succeed "file 'n'\n"
+ end
+
+ it "knife cookbook show with a non-existent file displays an error" do
+ expect { knife("cookbook show x 1.0.0 recipes moose.rb") }.to raise_error(Chef::Exceptions::FileNotFound)
+ end
+
+ it "knife cookbook show with a non-existent version displays an error" do
+ expect { knife("cookbook show x 1.0.1") }.to raise_error(Net::HTTPClientException)
+ end
+
+ it "knife cookbook show with a non-existent cookbook displays an error" do
+ expect { knife("cookbook show y") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+end
diff --git a/knife/spec/integration/cookbook_upload_spec.rb b/knife/spec/integration/cookbook_upload_spec.rb
new file mode 100644
index 0000000000..f42683b2a3
--- /dev/null
+++ b/knife/spec/integration/cookbook_upload_spec.rb
@@ -0,0 +1,128 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/cookbook_upload"
+
+describe "knife cookbook upload", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:cb_dir) { "#{@repository_dir}/cookbooks" }
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ end
+
+ it "knife cookbook upload uploads the cookbook" do
+ knife("cookbook upload x -o #{cb_dir}").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploaded 1 cookbook.
+ EOM
+ end
+
+ it "knife cookbook upload --freeze uploads and freezes the cookbook" do
+ knife("cookbook upload x -o #{cb_dir} --freeze").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploaded 1 cookbook.
+ EOM
+ # Modify the file, attempt to reupload
+ file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different'
+ knife("cookbook upload x -o #{cb_dir} --freeze").should_fail stderr: <<~EOM
+ Uploading x [1.0.0]
+ ERROR: Version 1.0.0 of cookbook x is frozen. Use --force to override.
+ WARNING: Not updating version constraints for x in the environment as the cookbook is frozen.
+ ERROR: Failed to upload 1 cookbook.
+ EOM
+ end
+ end
+
+ when_the_repository "has a cookbook that depends on another cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\ndepends 'y'")
+ file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
+ end
+
+ it "knife cookbook upload --include-dependencies uploads both cookbooks" do
+ knife("cookbook upload --include-dependencies x -o #{cb_dir}").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploading y [1.0.0]
+ Uploaded 2 cookbooks.
+ EOM
+ end
+
+ it "knife cookbook upload fails due to missing dependencies" do
+ knife("cookbook upload x -o #{cb_dir}").should_fail stderr: <<~EOM
+ Uploading x [1.0.0]
+ ERROR: Cookbook x depends on cookbooks which are not currently
+ ERROR: being uploaded and cannot be found on the server.
+ ERROR: The missing cookbook(s) are: 'y' version '>= 0.0.0'
+ EOM
+ end
+
+ it "knife cookbook upload -a uploads both cookbooks" do
+ knife("cookbook upload -a -o #{cb_dir}").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploading y [1.0.0]
+ Uploaded all cookbooks.
+ EOM
+ end
+ end
+
+ when_the_repository "has cookbook metadata without name attribute in metadata file" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0")
+ end
+
+ it "knife cookbook upload x " do
+ expect { knife("cookbook upload x -o #{cb_dir}") }.to raise_error(Chef::Exceptions::MetadataNotValid)
+ end
+ end
+
+ when_the_repository "has cookbooks at multiple paths" do
+
+ let(:cb_dir_first) do
+ File.join(@repository_dir, "cookbooks")
+ .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
+ end
+
+ let(:cb_dir_second) do
+ File.join(@repository_dir, "test_cookbooks")
+ .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
+ end
+
+ before(:each) do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "test_cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
+ end
+
+ it "knife cookbook upload with -o or --cookbook-path" do
+ knife("cookbook upload x y -o #{cb_dir_first}#{File::PATH_SEPARATOR}#{cb_dir_second}").should_succeed stderr: <<~EOM
+ Uploading x [1.0.0]
+ Uploading y [1.0.0]
+ Uploaded 2 cookbooks.
+ EOM
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/integration/data_bag_create_spec.rb b/knife/spec/integration/data_bag_create_spec.rb
new file mode 100644
index 0000000000..439d69507c
--- /dev/null
+++ b/knife/spec/integration/data_bag_create_spec.rb
@@ -0,0 +1,125 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/data_bag_create"
+
+describe "knife data bag create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:err) { "Created data_bag[foo]\n" }
+ let(:out) { "Created data_bag_item[bar]\n" }
+ let(:exists) { "Data bag foo already exists\n" }
+ let(:secret) { "abc" }
+
+ when_the_chef_server "is empty" do
+ context "with encryption key" do
+ it "creates a new data bag and item" do
+ pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" })
+ allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
+ knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err
+ expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n")
+ expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n")
+ end
+
+ it "creates a new data bag and an empty item" do
+ knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err
+ expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
+ expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n")
+ end
+ end
+
+ context "without encryption key" do
+ it "creates a new data bag" do
+ knife("data bag create foo").should_succeed stderr: err
+ expect(knife("data bag show foo").stderr).to eq("")
+ end
+
+ it "creates a new data bag and item" do
+ knife("data bag create foo bar").should_succeed stdout: out, stderr: err
+ expect(knife("data bag show foo").stdout).to eq("bar\n")
+ end
+ end
+ end
+
+ when_the_chef_server "has some data bags" do
+ before do
+ data_bag "foo", {}
+ data_bag "bag", { "box" => {} }
+ end
+
+ context "with encryption key" do
+ it "creates a new data bag and item" do
+ pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" })
+ allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
+ knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM
+ Created data_bag[rocket]
+ EOM
+ expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n")
+ expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n")
+ end
+
+ it "creates a new data bag and an empty item" do
+ knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM
+ Created data_bag[rocket]
+ EOM
+ expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
+ expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\n")
+ end
+
+ it "adds a new item to an existing bag" do
+ knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: exists
+ expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
+ expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n")
+ end
+
+ it "fails to add an existing item" do
+ expect { knife("data bag create bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+
+ context "without encryption key" do
+ it "creates a new data bag" do
+ knife("data bag create rocket").should_succeed stderr: <<~EOM
+ Created data_bag[rocket]
+ EOM
+ end
+
+ it "creates a new data bag and item" do
+ knife("data bag create rocket bar").should_succeed stdout: out, stderr: <<~EOM
+ Created data_bag[rocket]
+ EOM
+ end
+
+ it "adds a new item to an existing bag" do
+ knife("data bag create foo bar").should_succeed stdout: out, stderr: exists
+ end
+
+ it "refuses to create an existing data bag" do
+ knife("data bag create foo").should_succeed stderr: exists
+ end
+
+ it "fails to add an existing item" do
+ expect { knife("data bag create bag box") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/data_bag_delete_spec.rb b/knife/spec/integration/data_bag_delete_spec.rb
new file mode 100644
index 0000000000..a7fac7e2ee
--- /dev/null
+++ b/knife/spec/integration/data_bag_delete_spec.rb
@@ -0,0 +1,59 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/data_bag_delete"
+
+describe "knife data bag delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some data bags" do
+ before do
+ data_bag "x", {}
+ data_bag "canteloupe", {}
+ data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
+ end
+
+ it "with an empty data bag" do
+ knife("data bag delete canteloupe", input: "y").should_succeed <<~EOM
+ Do you really want to delete canteloupe? (Y/N) Deleted data_bag[canteloupe]
+ EOM
+ end
+
+ it "with a bag with some items" do
+ knife("data bag delete rocket", input: "y").should_succeed <<~EOM
+ Do you really want to delete rocket? (Y/N) Deleted data_bag[rocket]
+ EOM
+ end
+
+ it "with a single item" do
+ knife("data bag delete rocket falcon9", input: "y").should_succeed <<~EOM
+ Do you really want to delete falcon9? (Y/N) Deleted data_bag_item[falcon9]
+ EOM
+ end
+
+ it "choosing not to delete" do
+ knife("data bag delete rocket falcon9", input: "n").should_succeed <<~EOM, exit_code: 3
+ Do you really want to delete falcon9? (Y/N) You said no, so I'm done here.
+ EOM
+ end
+ end
+end
diff --git a/knife/spec/integration/data_bag_edit_spec.rb b/knife/spec/integration/data_bag_edit_spec.rb
new file mode 100644
index 0000000000..1071df2a78
--- /dev/null
+++ b/knife/spec/integration/data_bag_edit_spec.rb
@@ -0,0 +1,105 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/data_bag_edit"
+
+describe "knife data bag edit", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Saved data_bag_item[box]\n" }
+ let(:err) { "Saving data bag unencrypted. To encrypt it, provide an appropriate secret.\n" }
+ let(:secret) { "abc" }
+ let(:encrypt) { "Encrypted data bag detected, decrypting with provided secret.\n" }
+
+ when_the_chef_server "is empty" do
+ context "with encryption key" do
+ it "fails to edit an item" do
+ expect { knife("data bag edit bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+
+ context "without encryption key" do
+ it "fails to edit an item" do
+ expect { knife("data bag edit bag box") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+ end
+
+ when_the_chef_server "has some data bags" do
+ before do
+ data_bag "foo", {}
+ data_bag "bag", { "box" => {} }
+ data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
+ data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } }
+ end
+
+ context "with encryption key" do
+ it "fails to edit a non-existing item" do
+ expect { knife("data bag edit foo box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
+ end
+
+ it "edits an encrypted data bag item" do
+ pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", foo: "bar" })
+ allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
+ knife("data bag edit encrypt box --secret #{secret}")
+ knife("data bag show encrypt box --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM
+ foo: bar
+ id: box
+ EOM
+ end
+
+ it "encrypts an unencrypted data bag item" do
+ knife("data bag edit rocket falcon9 --secret #{secret}")
+ knife("data bag show rocket falcon9 --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM
+ heavy: true
+ id: falcon9
+ EOM
+ end
+ end
+
+ context "without encryption key" do
+ it "fails to edit a non-existing item" do
+ expect { knife("data bag edit foo box") }.to raise_error(Net::HTTPClientException)
+ end
+ it "edits an empty data bag item" do
+ pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", ab: "abc" })
+ allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
+ knife("data bag edit bag box").should_succeed stderr: err, stdout: out
+ knife("data bag show bag box").should_succeed <<~EOM
+ ab: abc
+ id: box
+ EOM
+ end
+ it "edits a non-empty data bag item" do
+ pretty_json = Chef::JSONCompat.to_json_pretty({ id: "falcon9", heavy: false })
+ allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
+ knife("data bag edit rocket falcon9").should_succeed stderr: err, stdout: <<~EOM
+ Saved data_bag_item[falcon9]
+ EOM
+ knife("data bag show rocket falcon9").should_succeed <<~EOM
+ heavy: false
+ id: falcon9
+ EOM
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/data_bag_from_file_spec.rb b/knife/spec/integration/data_bag_from_file_spec.rb
new file mode 100644
index 0000000000..bb8bd192f0
--- /dev/null
+++ b/knife/spec/integration/data_bag_from_file_spec.rb
@@ -0,0 +1,116 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife data bag from file", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:db_dir) { "#{@repository_dir}/data_bags" }
+
+ when_the_chef_server "has an empty data bag" do
+ before do
+ data_bag "foo", {}
+ data_bag "bar", {}
+ end
+
+ when_the_repository "has some data bag items" do
+ before do
+ file "data_bags/foo/bar.json", { "id" => "bar", "foo" => "bar " }
+ file "data_bags/foo/bzr.json", { "id" => "bzr", "foo" => "bar " }
+ file "data_bags/foo/cat.json", { "id" => "cat", "foo" => "bar " }
+ file "data_bags/foo/dog.json", { "id" => "dog", "foo" => "bar " }
+ file "data_bags/foo/encrypted.json", <<~EOM
+ {
+ "id": "encrypted",
+ "password": {
+ "encrypted_data": "H6ab5RY9a9JAkS8A0RCMspXtOJh0ai8cNeA4Q3gLO8s=\\n",
+ "iv": "uWKKKxrJgtELlGMCOLJdkA==\\n",
+ "version": 1,
+ "cipher": "aes-256-cbc"
+ }
+ }
+ EOM
+ file "data_bags/bar/round_trip.json", <<~EOM
+ {
+ "name": "data_bag_item_bar_round_trip",
+ "json_class": "Chef::DataBagItem",
+ "chef_type": "data_bag_item",
+ "data_bag": "bar",
+ "raw_data": {
+ "id": "round_trip",
+ "root_password": {
+ "encrypted_data": "noDOsTpsTAZlTU5sprhmYZzUDfr8du7hH/zRDOjRAmoTJHTZyfYoR221EOOW\\nXJ1D\\n",
+ "iv": "Bnqhfy6n0Hx1wCe9pxHLoA==\\n",
+ "version": 1,
+ "cipher": "aes-256-cbc"
+ },
+ "admin_password": {
+ "encrypted_data": "TcC7dU1gx6OnE5Ab4i/k42UEf0Nnr7cAyuTHId/LNjNOwpNf7XZc27DQSjuy\\nHPlt\\n",
+ "iv": "+TAWJuPWCI2+WB8lGJAyvw==\\n",
+ "version": 1,
+ "cipher": "aes-256-cbc"
+ }
+ }
+ }
+ EOM
+ end
+
+ it "uploads a single file" do
+ knife("data bag from file foo #{db_dir}/foo/bar.json").should_succeed stderr: <<~EOM
+ Updated data_bag_item[foo::bar]
+ EOM
+ end
+
+ it "uploads a single encrypted file" do
+ knife("data bag from file foo #{db_dir}/foo/encrypted.json").should_succeed stderr: <<~EOM
+ Updated data_bag_item[foo::encrypted]
+ EOM
+ end
+
+ it "uploads a file in chef's internal format" do
+ pending "chef/chef#4815"
+ knife("data bag from file bar #{db_dir}/bar/round_trip.json").should_succeed stderr: <<~EOM
+ Updated data_bag_item[bar::round_trip]
+ EOM
+ end
+
+ it "uploads many files" do
+ knife("data bag from file foo #{db_dir}/foo/bar.json #{db_dir}/foo/bzr.json").should_succeed stderr: <<~EOM
+ Updated data_bag_item[foo::bar]
+ Updated data_bag_item[foo::bzr]
+ EOM
+ end
+
+ it "uploads a whole directory" do
+ knife("data bag from file foo #{db_dir}/foo")
+ knife("data bag show foo").should_succeed <<~EOM
+ bar
+ bzr
+ cat
+ dog
+ encrypted
+ EOM
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/integration/data_bag_list_spec.rb b/knife/spec/integration/data_bag_list_spec.rb
new file mode 100644
index 0000000000..1e7734db64
--- /dev/null
+++ b/knife/spec/integration/data_bag_list_spec.rb
@@ -0,0 +1,44 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/data_bag_list"
+
+describe "knife data bag list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some data bags" do
+ before do
+ data_bag "x", {}
+ data_bag "canteloupe", {}
+ data_bag "rocket", {}
+ end
+
+ it "knife data bag list shows all the cookbooks" do
+ knife("data bag list").should_succeed <<~EOM
+ canteloupe
+ rocket
+ x
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/data_bag_show_spec.rb b/knife/spec/integration/data_bag_show_spec.rb
new file mode 100644
index 0000000000..91ebf605f1
--- /dev/null
+++ b/knife/spec/integration/data_bag_show_spec.rb
@@ -0,0 +1,95 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/data_bag_show"
+
+describe "knife data bag show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "is empty" do
+ it "raises error if try to retrieve it" do
+ expect { knife("data bag show bag") }.to raise_error(Net::HTTPClientException)
+ end
+ end
+
+ when_the_chef_server "contains data bags" do
+ let(:right_secret) { "abc" }
+ let(:wrong_secret) { "ab" }
+ let(:err) { "Encrypted data bag detected, decrypting with provided secret.\n" }
+ before do
+ data_bag "x", {}
+ data_bag "canteloupe", {}
+ data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
+ data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } }
+ end
+
+ context "with encrypted data" do
+ context "provided secret key" do
+ it "shows data if secret key is correct" do
+ knife("data bag show encrypt box --secret #{right_secret}").should_succeed stderr: err, stdout: <<~EOM
+ foo: bar
+ id: box
+ EOM
+ end
+
+ it "raises error if secret key is incorrect" do
+ expect { knife("data bag show encrypt box --secret #{wrong_secret}") }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
+ end
+ end
+
+ context "not provided secret key" do
+ it "shows encrypted data with a warning" do
+ expect(knife("data bag show encrypt box").stderr).to eq("WARNING: Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.\n")
+ end
+ end
+ end
+
+ context "with unencrypted data" do
+ context "provided secret key" do
+ it "shows unencrypted data with a warning" do
+ expect(knife("data bag show rocket falcon9 --secret #{right_secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
+ end
+ end
+
+ context "not provided secret key" do
+ it "shows null with an empty data bag" do
+ knife("data bag show canteloupe").should_succeed "\n"
+ end
+
+ it "show list of items in a bag" do
+ knife("data bag show rocket").should_succeed <<~EOM
+ ariane
+ atlas
+ falcon9
+ EOM
+ end
+
+ it "show data of the item" do
+ knife("data bag show rocket falcon9").should_succeed <<~EOM
+ heavy: true
+ id: falcon9
+ EOM
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/delete_spec.rb b/knife/spec/integration/delete_spec.rb
new file mode 100644
index 0000000000..e00949e7f4
--- /dev/null
+++ b/knife/spec/integration/delete_spec.rb
@@ -0,0 +1,1018 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/delete"
+require "chef/knife/list"
+require "chef/knife/raw"
+
+describe "knife delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ let :everything do
+ <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ let :server_everything do
+ <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ end
+ let :server_nothing do
+ <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /cookbooks
+ /data_bags
+ /environments
+ /environments/_default.json
+ /nodes
+ /roles
+ /users
+ /users/admin.json
+ EOM
+ end
+
+ let :nothing do
+ <<~EOM
+ /clients
+ /cookbooks
+ /data_bags
+ /environments
+ /nodes
+ /roles
+ /users
+ EOM
+ end
+
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", "{}"
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => "{}" }
+ environment "x", "{}"
+ node "x", "{}"
+ role "x", "{}"
+ user "x", "{}"
+ end
+
+ when_the_repository "also has one of each thing" do
+ before do
+ file "clients/x.json", {}
+ file "cookbooks/x/metadata.rb", ""
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", {}
+ file "environments/x.json", {}
+ file "nodes/x.json", {}
+ file "roles/x.json", {}
+ file "users/x.json", {}
+ end
+
+ it "knife delete --both /cookbooks/x fails" do
+ knife("delete --both /cookbooks/x").should_fail <<~EOM
+ ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete.
+ ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete.
+ EOM
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /cookbooks/x deletes x" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete -r --local /cookbooks/x deletes x locally but not remotely" do
+ knife("delete -r --local /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete -r /cookbooks/x deletes x remotely but not locally" do
+ knife("delete -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ # TODO delete empty data bag (particularly different on local side)
+ context "with an empty data bag on both" do
+ before do
+ data_bag "empty", {}
+ directory "data_bags/empty"
+ end
+
+ it "knife delete --both /data_bags/empty fails but deletes local version" do
+ knife("delete --both /data_bags/empty").should_fail <<~EOM
+ ERROR: /data_bags/empty (remote) must be deleted recursively! Pass -r to knife delete.
+ ERROR: /data_bags/empty (local) must be deleted recursively! Pass -r to knife delete.
+ EOM
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/empty
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/empty
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+ end
+
+ it "knife delete --both /data_bags/x fails" do
+ knife("delete --both /data_bags/x").should_fail <<~EOM
+ ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete.
+ ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete.
+ EOM
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /data_bags/x deletes x" do
+ knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /environments/x.json deletes x" do
+ knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /roles/x.json deletes x" do
+ knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /environments/_default.json fails but still deletes the local copy" do
+ knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /environments/nonexistent.json fails" do
+ knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both / fails" do
+ knife("delete --both /").should_fail <<~EOM
+ ERROR: / (remote) cannot be deleted.
+ ERROR: / (local) cannot be deleted.
+ EOM
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /* fails" do
+ knife("delete --both -r /*").should_fail <<~EOM
+ ERROR: / (remote) cannot be deleted.
+ ERROR: / (local) cannot be deleted.
+ ERROR: /clients (remote) cannot be deleted.
+ ERROR: /clients (local) cannot be deleted.
+ ERROR: /cookbooks (remote) cannot be deleted.
+ ERROR: /cookbooks (local) cannot be deleted.
+ ERROR: /data_bags (remote) cannot be deleted.
+ ERROR: /data_bags (local) cannot be deleted.
+ ERROR: /environments (remote) cannot be deleted.
+ ERROR: /environments (local) cannot be deleted.
+ ERROR: /nodes (remote) cannot be deleted.
+ ERROR: /nodes (local) cannot be deleted.
+ ERROR: /roles (remote) cannot be deleted.
+ ERROR: /roles (local) cannot be deleted.
+ ERROR: /users (remote) cannot be deleted.
+ ERROR: /users (local) cannot be deleted.
+ EOM
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed everything
+ end
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife delete --both /cookbooks/x fails" do
+ knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete.\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both -r /cookbooks/x deletes x" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both /data_bags/x fails" do
+ knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete.\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both -r /data_bags/x deletes x" do
+ knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both /environments/x.json deletes x" do
+ knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both /roles/x.json deletes x" do
+ knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
+ knife("list -Rf /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /users
+ /users/admin.json
+ /users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both /environments/_default.json fails" do
+ knife("delete --both /environments/_default.json").should_fail "", stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both / fails" do
+ knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both -r /* fails" do
+ knife("delete --both -r /*").should_fail <<~EOM
+ ERROR: / (remote) cannot be deleted.
+ ERROR: / (local) cannot be deleted.
+ ERROR: /clients (remote) cannot be deleted.
+ ERROR: /clients (local) cannot be deleted.
+ ERROR: /cookbooks (remote) cannot be deleted.
+ ERROR: /cookbooks (local) cannot be deleted.
+ ERROR: /data_bags (remote) cannot be deleted.
+ ERROR: /data_bags (local) cannot be deleted.
+ ERROR: /environments (remote) cannot be deleted.
+ ERROR: /environments (local) cannot be deleted.
+ ERROR: /nodes (remote) cannot be deleted.
+ ERROR: /nodes (local) cannot be deleted.
+ ERROR: /roles (remote) cannot be deleted.
+ ERROR: /roles (local) cannot be deleted.
+ ERROR: /users (remote) cannot be deleted.
+ ERROR: /users (local) cannot be deleted.
+ EOM
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ it "knife delete --both /environments/nonexistent.json fails" do
+ knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+ knife("list -Rf /").should_succeed server_everything
+ knife("list -Rf --local /").should_succeed nothing
+ end
+
+ context "and cwd is at the top level" do
+ before { cwd "." }
+ it "knife delete fails" do
+ knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/
+ knife("list -Rf /").should_succeed <<~EOM
+ clients
+ clients/chef-validator.json
+ clients/chef-webui.json
+ clients/x.json
+ cookbooks
+ cookbooks/x
+ cookbooks/x/metadata.rb
+ data_bags
+ data_bags/x
+ data_bags/x/y.json
+ environments
+ environments/_default.json
+ environments/x.json
+ nodes
+ nodes/x.json
+ roles
+ roles/x.json
+ users
+ users/admin.json
+ users/x.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ clients
+ cookbooks
+ data_bags
+ environments
+ nodes
+ roles
+ users
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has one of each thing" do
+ before do
+ file "clients/x.json", {}
+ file "cookbooks/x/metadata.rb", ""
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", {}
+ file "environments/x.json", {}
+ file "nodes/x.json", {}
+ file "roles/x.json", {}
+ file "users/x.json", {}
+ end
+
+ it "knife delete --both /cookbooks/x fails" do
+ knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete.\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /cookbooks/x deletes x" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /data_bags/x fails" do
+ knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete.\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /data_bags/x deletes x" do
+ knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /environments/x.json deletes x" do
+ knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /roles/x.json deletes x" do
+ knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/_default.json
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both /environments/_default.json fails but still deletes the local copy" do
+ knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed <<~EOM
+ /clients
+ /clients/x.json
+ /cookbooks
+ /cookbooks/x
+ /cookbooks/x/metadata.rb
+ /data_bags
+ /data_bags/x
+ /data_bags/x/y.json
+ /environments
+ /environments/x.json
+ /nodes
+ /nodes/x.json
+ /roles
+ /roles/x.json
+ /users
+ /users/x.json
+ EOM
+ end
+
+ it "knife delete --both / fails" do
+ knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both -r /* fails" do
+ knife("delete --both -r /*").should_fail <<~EOM
+ ERROR: / (remote) cannot be deleted.
+ ERROR: / (local) cannot be deleted.
+ ERROR: /clients (remote) cannot be deleted.
+ ERROR: /clients (local) cannot be deleted.
+ ERROR: /cookbooks (remote) cannot be deleted.
+ ERROR: /cookbooks (local) cannot be deleted.
+ ERROR: /data_bags (remote) cannot be deleted.
+ ERROR: /data_bags (local) cannot be deleted.
+ ERROR: /environments (remote) cannot be deleted.
+ ERROR: /environments (local) cannot be deleted.
+ ERROR: /nodes (remote) cannot be deleted.
+ ERROR: /nodes (local) cannot be deleted.
+ ERROR: /roles (remote) cannot be deleted.
+ ERROR: /roles (local) cannot be deleted.
+ ERROR: /users (remote) cannot be deleted.
+ ERROR: /users (local) cannot be deleted.
+ EOM
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ it "knife delete --both /environments/nonexistent.json fails" do
+ knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+ knife("list -Rf /").should_succeed server_nothing
+ knife("list -Rf --local /").should_succeed everything
+ end
+
+ context "and cwd is at the top level" do
+ before { cwd "." }
+ it "knife delete fails" do
+ knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/
+ knife("list -Rf /").should_succeed <<~EOM
+ clients
+ clients/chef-validator.json
+ clients/chef-webui.json
+ cookbooks
+ data_bags
+ environments
+ environments/_default.json
+ nodes
+ roles
+ users
+ users/admin.json
+ EOM
+ knife("list -Rf --local /").should_succeed <<~EOM
+ clients
+ clients/x.json
+ cookbooks
+ cookbooks/x
+ cookbooks/x/metadata.rb
+ data_bags
+ data_bags/x
+ data_bags/x/y.json
+ environments
+ environments/_default.json
+ environments/x.json
+ nodes
+ nodes/x.json
+ roles
+ roles/x.json
+ users
+ users/x.json
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", 'version "1.0.0"'
+ file "cookbooks/x/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ # TODO this seems wrong
+ it "knife delete --both -r /cookbooks/x deletes the latest version on the server and the local version" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("raw /cookbooks/x").should_succeed(/1.0.0/)
+ knife("list --local /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife delete --both /cookbooks/x deletes the latest version on the server and the local version" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("raw /cookbooks/x").should_succeed(/0.9.9/)
+ knife("list --local /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before { cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } }
+
+ it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("raw /cookbooks/x").should_fail(/404/)
+ knife("list --local /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before { cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } }
+
+ it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("raw /cookbooks/x").should_fail(/404/)
+ knife("list --local /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ when_the_chef_server "has two versions of a cookbook" do
+ before do
+ cookbook "x", "2.0.11"
+ cookbook "x", "11.0.0"
+ end
+
+ it "knife delete deletes the latest version" do
+ knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
+ knife("raw /cookbooks/x").should_succeed( /2.0.11/ )
+ end
+ end
+ end
+
+ when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
+ before do
+ organization "foo" do
+ container "x", {}
+ group "x", {}
+ policy "x", "1.2.3", {}
+ policy_group "x", { "policies" => { "x" => { "revision_id" => "1.2.3" } } }
+ end
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
+ end
+
+ it "knife delete /acls/containers/environments.json fails with a reasonable error" do
+ knife("delete /acls/containers/environments.json").should_fail "ERROR: /acls/containers/environments.json (remote) ACLs cannot be deleted.\n"
+ end
+
+ it "knife delete /containers/x.json succeeds" do
+ knife("delete /containers/x.json").should_succeed "Deleted /containers/x.json\n"
+ knife("raw /containers/x.json").should_fail(/404/)
+ end
+
+ it "knife delete /groups/x.json succeeds" do
+ knife("delete /groups/x.json").should_succeed "Deleted /groups/x.json\n"
+ knife("raw /groups/x.json").should_fail(/404/)
+ end
+
+ it "knife delete /policies/x-1.2.3.json succeeds" do
+ knife("raw /policies/x/revisions/1.2.3").should_succeed "{\n \"name\": \"x\",\n \"revision_id\": \"1.2.3\",\n \"run_list\": [\n\n ],\n \"cookbook_locks\": {\n\n }\n}\n"
+ knife("delete /policies/x-1.2.3.json").should_succeed "Deleted /policies/x-1.2.3.json\n"
+ knife("raw /policies/x/revisions/1.2.3").should_fail(/404/)
+ end
+
+ it "knife delete /policy_groups/x.json succeeds" do
+ knife("raw /policy_groups/x").should_succeed "{\n \"uri\": \"http://127.0.0.1:8900/organizations/foo/policy_groups/x\",\n \"policies\": {\n \"x\": {\n \"revision_id\": \"1.2.3\"\n }\n }\n}\n"
+ knife("delete /policy_groups/x.json").should_succeed "Deleted /policy_groups/x.json\n"
+ knife("raw /policy_groups/x").should_fail(/404/)
+ end
+
+ it "knife delete /org.json fails with a reasonable error" do
+ knife("delete /org.json").should_fail "ERROR: /org.json (remote) cannot be deleted.\n"
+ end
+
+ it "knife delete /invitations.json fails with a reasonable error" do
+ knife("delete /invitations.json").should_fail "ERROR: /invitations.json (remote) cannot be deleted.\n"
+ end
+
+ it "knife delete /members.json fails with a reasonable error" do
+ knife("delete /members.json").should_fail "ERROR: /members.json (remote) cannot be deleted.\n"
+ end
+ end
+end
diff --git a/knife/spec/integration/deps_spec.rb b/knife/spec/integration/deps_spec.rb
new file mode 100644
index 0000000000..9875277f14
--- /dev/null
+++ b/knife/spec/integration/deps_spec.rb
@@ -0,0 +1,703 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/deps"
+
+describe "knife deps", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ context "local" do
+ when_the_repository "has a role with no run_list" do
+ before { file "roles/starring.json", {} }
+ it "knife deps reports no dependencies" do
+ knife("deps /roles/starring.json").should_succeed "/roles/starring.json\n"
+ end
+ end
+
+ when_the_repository "has a role with a default run_list" do
+ before do
+ file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ file "roles/minor.json", {}
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ file "cookbooks/soup/metadata.rb", 'name "soup"'
+ file "cookbooks/soup/recipes/chicken.rb", ""
+ end
+ it "knife deps reports all dependencies" do
+ knife("deps /roles/starring.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ when_the_repository "has a role with an env_run_list" do
+ before do
+ file "roles/starring.json", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } }
+ file "roles/minor.json", {}
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ file "cookbooks/soup/metadata.rb", 'name "soup"'
+ file "cookbooks/soup/recipes/chicken.rb", ""
+ end
+ it "knife deps reports all dependencies" do
+ knife("deps /roles/starring.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ when_the_repository "has a node with no environment or run_list" do
+ before { file "nodes/mort.json", {} }
+ it "knife deps reports just the node" do
+ knife("deps /nodes/mort.json").should_succeed "/nodes/mort.json\n"
+ end
+ end
+ when_the_repository "has a node with an environment" do
+ before do
+ file "environments/desert.json", {}
+ file "nodes/mort.json", { "chef_environment" => "desert" }
+ end
+ it "knife deps reports just the node" do
+ knife("deps /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n"
+ end
+ end
+ when_the_repository "has a node with roles and recipes in its run_list" do
+ before do
+ file "roles/minor.json", {}
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ file "cookbooks/soup/metadata.rb", 'name "soup"'
+ file "cookbooks/soup/recipes/chicken.rb", ""
+ file "nodes/mort.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ end
+ it "knife deps reports just the node" do
+ knife("deps /nodes/mort.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /nodes/mort.json
+ EOM
+ end
+ end
+ when_the_repository "has a cookbook with no dependencies" do
+ before do
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ end
+ it "knife deps reports just the cookbook" do
+ knife("deps /cookbooks/quiche").should_succeed "/cookbooks/quiche\n"
+ end
+ end
+ when_the_repository "has a cookbook with dependencies" do
+ before do
+ file "cookbooks/kettle/metadata.rb", 'name "kettle"'
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"
+depends "kettle"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ end
+ it "knife deps reports just the cookbook" do
+ knife("deps /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
+ end
+ end
+ when_the_repository "has a data bag" do
+ before { file "data_bags/bag/item.json", {} }
+ it "knife deps reports just the data bag" do
+ knife("deps /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n"
+ end
+ end
+ when_the_repository "has an environment" do
+ before { file "environments/desert.json", {} }
+ it "knife deps reports just the environment" do
+ knife("deps /environments/desert.json").should_succeed "/environments/desert.json\n"
+ end
+ end
+ when_the_repository "has a deep dependency tree" do
+ before do
+ file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ file "roles/minor.json", {}
+ file "cookbooks/quiche/metadata.rb", 'name "quiche"'
+ file "cookbooks/quiche/recipes/default.rb", ""
+ file "cookbooks/soup/metadata.rb", 'name "soup"'
+ file "cookbooks/soup/recipes/chicken.rb", ""
+ file "environments/desert.json", {}
+ file "nodes/mort.json", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] }
+ file "nodes/bart.json", { "run_list" => [ "role[minor]" ] }
+ end
+
+ it "knife deps reports all dependencies" do
+ knife("deps /nodes/mort.json").should_succeed <<~EOM
+ /environments/desert.json
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps * reports all dependencies of all things" do
+ knife("deps /nodes/*").should_succeed <<~EOM
+ /roles/minor.json
+ /nodes/bart.json
+ /environments/desert.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps a b reports all dependencies of a and b" do
+ knife("deps /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM
+ /roles/minor.json
+ /nodes/bart.json
+ /environments/desert.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps --tree /* shows dependencies in a tree" do
+ knife("deps --tree /nodes/*").should_succeed <<~EOM
+ /nodes/bart.json
+ /roles/minor.json
+ /nodes/mort.json
+ /environments/desert.json
+ /roles/starring.json
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ EOM
+ end
+ it "knife deps --tree --no-recurse shows only the first level of dependencies" do
+ knife("deps --tree --no-recurse /nodes/*").should_succeed <<~EOM
+ /nodes/bart.json
+ /roles/minor.json
+ /nodes/mort.json
+ /environments/desert.json
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ context "circular dependencies" do
+ when_the_repository "has cookbooks with circular dependencies" do
+ before do
+ file "cookbooks/foo/metadata.rb", 'name "foo"
+depends "bar"'
+ file "cookbooks/bar/metadata.rb", 'name "bar"
+depends "baz"'
+ file "cookbooks/baz/metadata.rb", 'name "baz"
+depends "foo"'
+ end
+
+ it "knife deps prints each once" do
+ knife("deps /cookbooks/foo").should_succeed(
+ stdout: "/cookbooks/baz\n/cookbooks/bar\n/cookbooks/foo\n"
+ )
+ end
+ it "knife deps --tree prints each once" do
+ knife("deps --tree /cookbooks/foo").should_succeed(
+ stdout: "/cookbooks/foo\n /cookbooks/bar\n /cookbooks/baz\n /cookbooks/foo\n"
+ )
+ end
+ end
+ when_the_repository "has roles with circular dependencies" do
+ before do
+ file "roles/foo.json", { "run_list" => [ "role[bar]" ] }
+ file "roles/bar.json", { "run_list" => [ "role[baz]" ] }
+ file "roles/baz.json", { "run_list" => [ "role[foo]" ] }
+ file "roles/self.json", { "run_list" => [ "role[self]" ] }
+ end
+ it "knife deps prints each once" do
+ knife("deps /roles/foo.json /roles/self.json").should_succeed <<~EOM
+ /roles/baz.json
+ /roles/bar.json
+ /roles/foo.json
+ /roles/self.json
+ EOM
+ end
+ it "knife deps --tree prints each once" do
+ knife("deps --tree /roles/foo.json /roles/self.json") do
+ expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n")
+ expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n")
+ end
+ end
+ end
+ end
+
+ context "missing objects" do
+ when_the_repository "is empty" do
+ it "knife deps /blah reports an error" do
+ knife("deps /blah").should_fail(
+ exit_code: 2,
+ stdout: "/blah\n",
+ stderr: "ERROR: /blah: No such file or directory\n"
+ )
+ end
+ it "knife deps /roles/x.json reports an error" do
+ knife("deps /roles/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/roles/x.json\n",
+ stderr: "ERROR: /roles/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /nodes/x.json reports an error" do
+ knife("deps /nodes/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/nodes/x.json\n",
+ stderr: "ERROR: /nodes/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /environments/x.json reports an error" do
+ knife("deps /environments/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/environments/x.json\n",
+ stderr: "ERROR: /environments/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /cookbooks/x reports an error" do
+ knife("deps /cookbooks/x").should_fail(
+ exit_code: 2,
+ stdout: "/cookbooks/x\n",
+ stderr: "ERROR: /cookbooks/x: No such file or directory\n"
+ )
+ end
+ it "knife deps /data_bags/bag/item.json reports an error" do
+ knife("deps /data_bags/bag/item.json").should_fail(
+ exit_code: 2,
+ stdout: "/data_bags/bag/item.json\n",
+ stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n"
+ )
+ end
+ end
+ when_the_repository "is missing a dependent cookbook" do
+ before do
+ file "roles/starring.json", { "run_list" => [ "recipe[quiche]"] }
+ end
+ it "knife deps reports the cookbook, along with an error" do
+ knife("deps /roles/starring.json").should_fail(
+ exit_code: 2,
+ stdout: "/cookbooks/quiche\n/roles/starring.json\n",
+ stderr: "ERROR: /cookbooks/quiche: No such file or directory\n"
+ )
+ end
+ end
+ when_the_repository "is missing a dependent environment" do
+ before do
+ file "nodes/mort.json", { "chef_environment" => "desert" }
+ end
+ it "knife deps reports the environment, along with an error" do
+ knife("deps /nodes/mort.json").should_fail(
+ exit_code: 2,
+ stdout: "/environments/desert.json\n/nodes/mort.json\n",
+ stderr: "ERROR: /environments/desert.json: No such file or directory\n"
+ )
+ end
+ end
+ when_the_repository "is missing a dependent role" do
+ before do
+ file "roles/starring.json", { "run_list" => [ "role[minor]"] }
+ end
+ it "knife deps reports the role, along with an error" do
+ knife("deps /roles/starring.json").should_fail(
+ exit_code: 2,
+ stdout: "/roles/minor.json\n/roles/starring.json\n",
+ stderr: "ERROR: /roles/minor.json: No such file or directory\n"
+ )
+ end
+ end
+ end
+ context "invalid objects" do
+ when_the_repository "is empty" do
+ it "knife deps / reports itself only" do
+ knife("deps /").should_succeed("/\n")
+ end
+ it "knife deps /roles reports an error" do
+ knife("deps /roles").should_fail(
+ exit_code: 2,
+ stderr: "ERROR: /roles: No such file or directory\n",
+ stdout: "/roles\n"
+ )
+ end
+ end
+ when_the_repository "has a data bag" do
+ before { file "data_bags/bag/item.json", "" }
+ it "knife deps /data_bags/bag shows no dependencies" do
+ knife("deps /data_bags/bag").should_succeed("/data_bags/bag\n")
+ end
+ end
+ when_the_repository "has a cookbook" do
+ before { file "cookbooks/blah/metadata.rb", 'name "blah"' }
+ it "knife deps on a cookbook file shows no dependencies" do
+ knife("deps /cookbooks/blah/metadata.rb").should_succeed(
+ "/cookbooks/blah/metadata.rb\n"
+ )
+ end
+ end
+ end
+ end
+
+ context "remote" do
+ include_context "default config options"
+
+ when_the_chef_server "has a role with no run_list" do
+ before { role "starring", {} }
+ it "knife deps reports no dependencies" do
+ knife("deps --remote /roles/starring.json").should_succeed "/roles/starring.json\n"
+ end
+ end
+
+ when_the_chef_server "has a role with a default run_list" do
+ before do
+ role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ role "minor", {}
+ cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
+ cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
+ end
+ it "knife deps reports all dependencies" do
+ knife("deps --remote /roles/starring.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ when_the_chef_server "has a role with an env_run_list" do
+ before do
+ role "starring", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } }
+ role "minor", {}
+ cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
+ cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
+ end
+ it "knife deps reports all dependencies" do
+ knife("deps --remote /roles/starring.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ when_the_chef_server "has a node with no environment or run_list" do
+ before { node "mort", {} }
+ it "knife deps reports just the node" do
+ knife("deps --remote /nodes/mort.json").should_succeed "/nodes/mort.json\n"
+ end
+ end
+ when_the_chef_server "has a node with an environment" do
+ before do
+ environment "desert", {}
+ node "mort", { "chef_environment" => "desert" }
+ end
+ it "knife deps reports just the node" do
+ knife("deps --remote /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n"
+ end
+ end
+ when_the_chef_server "has a node with roles and recipes in its run_list" do
+ before do
+ role "minor", {}
+ cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
+ cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
+ node "mort", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ end
+ it "knife deps reports just the node" do
+ knife("deps --remote /nodes/mort.json").should_succeed <<~EOM
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /nodes/mort.json
+ EOM
+ end
+ end
+ when_the_chef_server "has a cookbook with no dependencies" do
+ before do
+ cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
+ end
+ it "knife deps reports just the cookbook" do
+ knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/quiche\n"
+ end
+ end
+ when_the_chef_server "has a cookbook with dependencies" do
+ before do
+ cookbook "kettle", "1.0.0", { "metadata.rb" => %Q{name "kettle"\nversion "1.0.0"\n} }
+ cookbook "quiche", "1.0.0", { "metadata.rb" => 'name "quiche"
+depends "kettle"', "recipes" => { "default.rb" => "" } }
+ end
+ it "knife deps reports the cookbook and its dependencies" do
+ knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
+ end
+ end
+ when_the_chef_server "has a data bag" do
+ before { data_bag "bag", { "item" => {} } }
+ it "knife deps reports just the data bag" do
+ knife("deps --remote /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n"
+ end
+ end
+ when_the_chef_server "has an environment" do
+ before { environment "desert", {} }
+ it "knife deps reports just the environment" do
+ knife("deps --remote /environments/desert.json").should_succeed "/environments/desert.json\n"
+ end
+ end
+ when_the_chef_server "has a deep dependency tree" do
+ before do
+ role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
+ role "minor", {}
+ cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
+ cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
+ environment "desert", {}
+ node "mort", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] }
+ node "bart", { "run_list" => [ "role[minor]" ] }
+ end
+
+ it "knife deps reports all dependencies" do
+ knife("deps --remote /nodes/mort.json").should_succeed <<~EOM
+ /environments/desert.json
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps * reports all dependencies of all things" do
+ knife("deps --remote /nodes/*").should_succeed <<~EOM
+ /roles/minor.json
+ /nodes/bart.json
+ /environments/desert.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps a b reports all dependencies of a and b" do
+ knife("deps --remote /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM
+ /roles/minor.json
+ /nodes/bart.json
+ /environments/desert.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ /roles/starring.json
+ /nodes/mort.json
+ EOM
+ end
+ it "knife deps --tree /* shows dependencies in a tree" do
+ knife("deps --remote --tree /nodes/*").should_succeed <<~EOM
+ /nodes/bart.json
+ /roles/minor.json
+ /nodes/mort.json
+ /environments/desert.json
+ /roles/starring.json
+ /roles/minor.json
+ /cookbooks/quiche
+ /cookbooks/soup
+ EOM
+ end
+ it "knife deps --tree --no-recurse shows only the first level of dependencies" do
+ knife("deps --remote --tree --no-recurse /nodes/*").should_succeed <<~EOM
+ /nodes/bart.json
+ /roles/minor.json
+ /nodes/mort.json
+ /environments/desert.json
+ /roles/starring.json
+ EOM
+ end
+ end
+
+ context "circular dependencies" do
+ when_the_chef_server "has cookbooks with circular dependencies" do
+ before do
+ cookbook "foo", "1.0.0", { "metadata.rb" => 'name "foo"
+depends "bar"' }
+ cookbook "bar", "1.0.0", { "metadata.rb" => 'name "bar"
+depends "baz"' }
+ cookbook "baz", "1.0.0", { "metadata.rb" => 'name "baz"
+depends "foo"' }
+ cookbook "self", "1.0.0", { "metadata.rb" => 'name "self"
+depends "self"' }
+ end
+ it "knife deps prints each once" do
+ knife("deps --remote /cookbooks/foo /cookbooks/self").should_succeed <<~EOM
+ /cookbooks/baz
+ /cookbooks/bar
+ /cookbooks/foo
+ /cookbooks/self
+ EOM
+ end
+ it "knife deps --tree prints each once" do
+ knife("deps --remote --tree /cookbooks/foo /cookbooks/self").should_succeed <<~EOM
+ /cookbooks/foo
+ /cookbooks/bar
+ /cookbooks/baz
+ /cookbooks/foo
+ /cookbooks/self
+ /cookbooks/self
+ EOM
+ end
+ end
+ when_the_chef_server "has roles with circular dependencies" do
+ before do
+ role "foo", { "run_list" => [ "role[bar]" ] }
+ role "bar", { "run_list" => [ "role[baz]" ] }
+ role "baz", { "run_list" => [ "role[foo]" ] }
+ role "self", { "run_list" => [ "role[self]" ] }
+ end
+ it "knife deps prints each once" do
+ knife("deps --remote /roles/foo.json /roles/self.json").should_succeed <<~EOM
+ /roles/baz.json
+ /roles/bar.json
+ /roles/foo.json
+ /roles/self.json
+ EOM
+ end
+ it "knife deps --tree prints each once" do
+ knife("deps --remote --tree /roles/foo.json /roles/self.json") do
+ expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n")
+ expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n")
+ end
+ end
+ end
+ end
+
+ context "missing objects" do
+ when_the_chef_server "is empty" do
+ it "knife deps /blah reports an error" do
+ knife("deps --remote /blah").should_fail(
+ exit_code: 2,
+ stdout: "/blah\n",
+ stderr: "ERROR: /blah: No such file or directory\n"
+ )
+ end
+ it "knife deps /roles/x.json reports an error" do
+ knife("deps --remote /roles/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/roles/x.json\n",
+ stderr: "ERROR: /roles/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /nodes/x.json reports an error" do
+ knife("deps --remote /nodes/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/nodes/x.json\n",
+ stderr: "ERROR: /nodes/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /environments/x.json reports an error" do
+ knife("deps --remote /environments/x.json").should_fail(
+ exit_code: 2,
+ stdout: "/environments/x.json\n",
+ stderr: "ERROR: /environments/x.json: No such file or directory\n"
+ )
+ end
+ it "knife deps /cookbooks/x reports an error" do
+ knife("deps --remote /cookbooks/x").should_fail(
+ exit_code: 2,
+ stdout: "/cookbooks/x\n",
+ stderr: "ERROR: /cookbooks/x: No such file or directory\n"
+ )
+ end
+ it "knife deps /data_bags/bag/item reports an error" do
+ knife("deps --remote /data_bags/bag/item.json").should_fail(
+ exit_code: 2,
+ stdout: "/data_bags/bag/item.json\n",
+ stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n"
+ )
+ end
+ end
+ when_the_chef_server "is missing a dependent cookbook" do
+ before do
+ role "starring", { "run_list" => [ "recipe[quiche]"] }
+ end
+ it "knife deps reports the cookbook, along with an error" do
+ knife("deps --remote /roles/starring.json").should_fail(
+ exit_code: 2,
+ stdout: "/cookbooks/quiche\n/roles/starring.json\n",
+ stderr: "ERROR: /cookbooks/quiche: No such file or directory\n"
+ )
+ end
+ end
+ when_the_chef_server "is missing a dependent environment" do
+ before do
+ node "mort", { "chef_environment" => "desert" }
+ end
+ it "knife deps reports the environment, along with an error" do
+ knife("deps --remote /nodes/mort.json").should_fail(
+ exit_code: 2,
+ stdout: "/environments/desert.json\n/nodes/mort.json\n",
+ stderr: "ERROR: /environments/desert.json: No such file or directory\n"
+ )
+ end
+ end
+ when_the_chef_server "is missing a dependent role" do
+ before do
+ role "starring", { "run_list" => [ "role[minor]"] }
+ end
+ it "knife deps reports the role, along with an error" do
+ knife("deps --remote /roles/starring.json").should_fail(
+ exit_code: 2,
+ stdout: "/roles/minor.json\n/roles/starring.json\n",
+ stderr: "ERROR: /roles/minor.json: No such file or directory\n"
+ )
+ end
+ end
+ end
+ context "invalid objects" do
+ when_the_chef_server "is empty" do
+ it "knife deps / reports an error" do
+ knife("deps --remote /").should_succeed("/\n")
+ end
+ it "knife deps /roles reports an error" do
+ knife("deps --remote /roles").should_succeed("/roles\n")
+ end
+ end
+ when_the_chef_server "has a data bag" do
+ before { data_bag "bag", { "item" => {} } }
+ it "knife deps /data_bags/bag shows no dependencies" do
+ knife("deps --remote /data_bags/bag").should_succeed("/data_bags/bag\n")
+ end
+ end
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "blah", "1.0.0", { "metadata.rb" => 'name "blah"' }
+ end
+ it "knife deps on a cookbook file shows no dependencies" do
+ knife("deps --remote /cookbooks/blah/metadata.rb").should_succeed(
+ "/cookbooks/blah/metadata.rb\n"
+ )
+ end
+ end
+ end
+ end
+
+ it "knife deps --no-recurse reports an error" do
+ knife("deps --no-recurse /").should_fail("ERROR: --no-recurse requires --tree\n")
+ end
+end
diff --git a/knife/spec/integration/diff_spec.rb b/knife/spec/integration/diff_spec.rb
new file mode 100644
index 0000000000..c69573735a
--- /dev/null
+++ b/knife/spec/integration/diff_spec.rb
@@ -0,0 +1,605 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/diff"
+
+describe "knife diff", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ context "without versioned cookbooks" do
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", "{}"
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => "{}" }
+ environment "x", "{}"
+ node "x", "{}"
+ role "x", "{}"
+ user "x", "{}"
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife diff reports everything as deleted" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients/chef-validator.json
+ D\t/clients/chef-webui.json
+ D\t/clients/x.json
+ D\t/cookbooks/x
+ D\t/data_bags/x
+ D\t/environments/_default.json
+ D\t/environments/x.json
+ D\t/nodes/x.json
+ D\t/roles/x.json
+ D\t/users/admin.json
+ D\t/users/x.json
+ EOM
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife diff reports no differences" do
+ knife("diff /").should_succeed ""
+ end
+
+ it "knife diff /environments/nonexistent.json reports an error" do
+ knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
+ end
+
+ it "knife diff /environments/*.txt reports an error" do
+ knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "foo": "bar"
+ }
+ EOM
+ end
+
+ it "knife diff reports the role as different" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ M\t/roles/x.json
+ EOM
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/blah.rb", ""
+ file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife diff reports the new files as added" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ A\t/clients/y.json
+ A\t/cookbooks/x/blah.rb
+ A\t/cookbooks/y
+ A\t/data_bags/x/z.json
+ A\t/data_bags/y
+ A\t/environments/y.json
+ A\t/nodes/y.json
+ A\t/roles/y.json
+ A\t/users/y.json
+ EOM
+ end
+
+ context "when cwd is the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife diff reports different data bags" do
+ knife("diff --name-status").should_succeed <<~EOM
+ A\tx/z.json
+ A\ty
+ EOM
+ end
+ it "knife diff * reports different data bags" do
+ knife("diff --name-status *").should_succeed <<~EOM
+ A\tx/z.json
+ A\ty
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife diff reports everything as deleted" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/onlyin1.0.0.rb", ""
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
+ end
+
+ it "knife diff /cookbooks/x shows differences" do
+ knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+
+ it "knife diff --diff-filter=MAT does not show deleted files" do
+ knife("diff --diff-filter=MAT --name-status /cookbooks/x").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
+ end
+ it "knife diff /cookbooks/x shows no differences" do
+ knife("diff --name-status /cookbooks/x").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
+ end
+
+ it "knife diff /cookbooks/x shows the differences" do
+ knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
+ end
+
+ it "knife diff /cookbooks/x shows the differences" do
+ knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin0.9.9.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+ end
+
+ context "json diff tests" do
+ when_the_repository "has an empty environment file" do
+ before do
+ file "environments/x.json", {}
+ end
+
+ when_the_chef_server "has an empty environment" do
+ before { environment "x", {} }
+ it "knife diff returns no differences" do
+ knife("diff /environments/x.json").should_succeed ""
+ end
+ end
+ when_the_chef_server "has an environment with a different value" do
+ before { environment "x", { "description" => "hi" } }
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+- "name": "x",
+- "description": "hi"
+\+ "name": "x"
+ }
+/)
+ end
+ end
+ end
+
+ when_the_repository "has an environment file with a value in it" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+
+ when_the_chef_server "has an environment with the same value" do
+ before do
+ environment "x", { "description" => "hi" }
+ end
+ it "knife diff returns no differences" do
+ knife("diff /environments/x.json").should_succeed ""
+ end
+ end
+ when_the_chef_server "has an environment with no value" do
+ before do
+ environment "x", {}
+ end
+
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+- "name": "x"
+\+ "name": "x",
+\+ "description": "hi"
+ }
+/)
+ end
+ end
+ when_the_chef_server "has an environment with a different value" do
+ before do
+ environment "x", { "description" => "lo" }
+ end
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+ "name": "x",
+- "description": "lo"
+\+ "description": "hi"
+ }
+/)
+ end
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before { environment "x", {} }
+ when_the_repository "has an environment with bad JSON" do
+ before { file "environments/x.json", "{" }
+ it "knife diff reports an error and does a textual diff" do
+ error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF"
+ error_match = Regexp.new(Regexp.escape(error_text))
+ knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match)
+ end
+ end
+ end
+ end # without versioned cookbooks
+
+ context "with versioned cookbooks" do
+ before { Chef::Config[:versioned_cookbooks] = true }
+
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", "{}"
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => "{}" }
+ environment "x", "{}"
+ node "x", "{}"
+ role "x", "{}"
+ user "x", "{}"
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife diff reports everything as deleted" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients/chef-validator.json
+ D\t/clients/chef-webui.json
+ D\t/clients/x.json
+ D\t/cookbooks/x-1.0.0
+ D\t/data_bags/x
+ D\t/environments/_default.json
+ D\t/environments/x.json
+ D\t/nodes/x.json
+ D\t/roles/x.json
+ D\t/users/admin.json
+ D\t/users/x.json
+ EOM
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife diff reports no differences" do
+ knife("diff /").should_succeed ""
+ end
+
+ it "knife diff /environments/nonexistent.json reports an error" do
+ knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
+ end
+
+ it "knife diff /environments/*.txt reports an error" do
+ knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "foo": "bar"
+ }
+ EOM
+ end
+
+ it "knife diff reports the role as different" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ M\t/roles/x.json
+ EOM
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", {}
+ file "cookbooks/x-1.0.0/blah.rb", ""
+ file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0")
+ file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0")
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", {}
+ end
+
+ it "knife diff reports the new files as added" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ A\t/clients/y.json
+ A\t/cookbooks/x-1.0.0/blah.rb
+ A\t/cookbooks/x-2.0.0
+ A\t/cookbooks/y-1.0.0
+ A\t/data_bags/x/z.json
+ A\t/data_bags/y
+ A\t/environments/y.json
+ A\t/nodes/y.json
+ A\t/roles/y.json
+ A\t/users/y.json
+ EOM
+ end
+
+ context "when cwd is the data_bags directory" do
+ before { cwd "data_bags" }
+ it "knife diff reports different data bags" do
+ knife("diff --name-status").should_succeed <<~EOM
+ A\tx/z.json
+ A\ty
+ EOM
+ end
+ it "knife diff * reports different data bags" do
+ knife("diff --name-status *").should_succeed <<~EOM
+ A\tx/z.json
+ A\ty
+ EOM
+ end
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife diff reports everything as deleted" do
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x-1.0.0/onlyin1.0.0.rb", ""
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
+ end
+
+ it "knife diff /cookbooks shows differences" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x-1.0.1
+ EOM
+ end
+
+ it "knife diff --diff-filter=MAT does not show deleted files" do
+ knife("diff --diff-filter=MAT --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
+ end
+ it "knife diff /cookbooks shows the differences" do
+ knife("diff --name-status /cookbooks").should_succeed "D\t/cookbooks/x-0.9.9\n"
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
+ end
+
+ it "knife diff /cookbooks shows the differences" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x-1.0.1
+ A\t/cookbooks/x-1.0.0
+ EOM
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
+ end
+
+ it "knife diff /cookbooks shows the differences" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x-0.9.9
+ A\t/cookbooks/x-1.0.0
+ EOM
+ end
+ end
+ end
+
+ context "json diff tests" do
+ when_the_repository "has an empty environment file" do
+ before { file "environments/x.json", {} }
+ when_the_chef_server "has an empty environment" do
+ before { environment "x", {} }
+ it "knife diff returns no differences" do
+ knife("diff /environments/x.json").should_succeed ""
+ end
+ end
+ when_the_chef_server "has an environment with a different value" do
+ before { environment "x", { "description" => "hi" } }
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+- "name": "x",
+- "description": "hi"
+\+ "name": "x"
+ }
+/)
+ end
+ end
+ end
+
+ when_the_repository "has an environment file with a value in it" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+
+ when_the_chef_server "has an environment with the same value" do
+ before do
+ environment "x", { "description" => "hi" }
+ end
+ it "knife diff returns no differences" do
+ knife("diff /environments/x.json").should_succeed ""
+ end
+ end
+ when_the_chef_server "has an environment with no value" do
+ before { environment "x", {} }
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+- "name": "x"
+\+ "name": "x",
+\+ "description": "hi"
+ }
+/)
+ end
+ end
+ when_the_chef_server "has an environment with a different value" do
+ before do
+ environment "x", { "description" => "lo" }
+ end
+ it "knife diff reports the difference" do
+ knife("diff /environments/x.json").should_succeed(/
+ {
+ "name": "x",
+- "description": "lo"
+\+ "description": "hi"
+ }
+/)
+ end
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before { environment "x", {} }
+ when_the_repository "has an environment with bad JSON" do
+ before { file "environments/x.json", "{" }
+ it "knife diff reports an error and does a textual diff" do
+ error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF"
+ error_match = Regexp.new(Regexp.escape(error_text))
+ knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match)
+ end
+ end
+ end
+ end # without versioned cookbooks
+end
diff --git a/knife/spec/integration/download_spec.rb b/knife/spec/integration/download_spec.rb
new file mode 100644
index 0000000000..29200d66f2
--- /dev/null
+++ b/knife/spec/integration/download_spec.rb
@@ -0,0 +1,1336 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/download"
+require "chef/knife/diff"
+
+describe "knife download", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ context "without versioned cookbooks" do
+ when_the_chef_server "has one of each thing" do
+
+ before do
+ client "x", {}
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ node "x", {}
+ role "x", {}
+ user "x", {}
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife download downloads everything" do
+ knife("download /").should_succeed <<~EOM
+ Created /clients/chef-validator.json
+ Created /clients/chef-webui.json
+ Created /clients/x.json
+ Created /cookbooks/x
+ Created /cookbooks/x/metadata.rb
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments/_default.json
+ Created /environments/x.json
+ Created /nodes/x.json
+ Created /roles/x.json
+ Created /users/admin.json
+ Created /users/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife download makes no changes" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife download --purge makes no changes" do
+ knife("download --purge /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "chef_type": "role",
+ "default_attributes": {
+ },
+ "description": "blarghle",
+ "env_run_lists": {
+ },
+ "json_class": "Chef::Role",
+ "name": "x",
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife download changes the role" do
+ knife("download /").should_succeed "Updated /roles/x.json\n"
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife download --no-diff does not change the role" do
+ knife("download --no-diff /").should_succeed ""
+ knife("diff --name-status /").should_succeed "M\t/roles/x.json\n"
+ end
+ end
+
+ context "except the role file is textually different, but not ACTUALLY different" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "chef_type": "role",
+ "default_attributes": {
+ },
+ "env_run_lists": {
+ },
+ "json_class": "Chef::Role",
+ "name": "x",
+ "description": "",
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife download / does not change anything" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/blah.rb", ""
+ file "cookbooks/y/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife download does nothing" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ A\t/clients/y.json
+ A\t/cookbooks/x/blah.rb
+ A\t/cookbooks/y
+ A\t/data_bags/x/z.json
+ A\t/data_bags/y
+ A\t/environments/y.json
+ A\t/nodes/y.json
+ A\t/roles/y.json
+ A\t/users/y.json
+ EOM
+ end
+
+ it "knife download --purge deletes the extra files" do
+ knife("download --purge /").should_succeed <<~EOM
+ Deleted extra entry /clients/y.json (purge is on)
+ Deleted extra entry /cookbooks/x/blah.rb (purge is on)
+ Deleted extra entry /cookbooks/y (purge is on)
+ Deleted extra entry /data_bags/x/z.json (purge is on)
+ Deleted extra entry /data_bags/y (purge is on)
+ Deleted extra entry /environments/y.json (purge is on)
+ Deleted extra entry /nodes/y.json (purge is on)
+ Deleted extra entry /roles/y.json (purge is on)
+ Deleted extra entry /users/y.json (purge is on)
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife download creates the extra files" do
+ knife("download /").should_succeed <<~EOM
+ Created /clients
+ Created /clients/chef-validator.json
+ Created /clients/chef-webui.json
+ Created /clients/x.json
+ Created /cookbooks
+ Created /cookbooks/x
+ Created /cookbooks/x/metadata.rb
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments
+ Created /environments/_default.json
+ Created /environments/x.json
+ Created /nodes
+ Created /nodes/x.json
+ Created /roles
+ Created /roles/x.json
+ Created /users
+ Created /users/admin.json
+ Created /users/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife download --no-diff creates the extra files" do
+ knife("download --no-diff /").should_succeed <<~EOM
+ Created /clients
+ Created /clients/chef-validator.json
+ Created /clients/chef-webui.json
+ Created /clients/x.json
+ Created /cookbooks
+ Created /cookbooks/x
+ Created /cookbooks/x/metadata.rb
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments
+ Created /environments/_default.json
+ Created /environments/x.json
+ Created /nodes
+ Created /nodes/x.json
+ Created /roles
+ Created /roles/x.json
+ Created /users
+ Created /users/admin.json
+ Created /users/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "when current directory is top level" do
+ before do
+ cwd "."
+ end
+
+ it "knife download with no parameters reports an error" do
+ knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
+ end
+ end
+ end
+ end
+
+ # Test download of an item when the other end doesn't even have the container
+ when_the_repository "is empty" do
+ when_the_chef_server "has two data bag items" do
+ before do
+ data_bag "x", { "y" => {}, "z" => {} }
+ end
+
+ it "knife download of one data bag item itself succeeds" do
+ knife("download /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/z.json
+ EOM
+ end
+
+ it "knife download /data_bags/x /data_bags/x/y.json downloads x once" do
+ knife("download /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /data_bags/x/z.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has three data bag items" do
+ before do
+ file "data_bags/x/deleted.json", <<~EOM
+ {
+ "id": "deleted"
+ }
+ EOM
+ file "data_bags/x/modified.json", <<~EOM
+ {
+ "id": "modified"
+ }
+ EOM
+ file "data_bags/x/unmodified.json", <<~EOM
+ {
+ "id": "unmodified"
+ }
+ EOM
+ end
+
+ when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
+ before do
+ data_bag "x", {
+ "added" => {},
+ "modified" => { "foo" => "bar" },
+ "unmodified" => {},
+ }
+ end
+
+ it "knife download of the modified file succeeds" do
+ knife("download /data_bags/x/modified.json").should_succeed <<~EOM
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the unmodified file does nothing" do
+ knife("download /data_bags/x/unmodified.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the added file succeeds" do
+ knife("download /data_bags/x/added.json").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the deleted file does nothing" do
+ knife("download /data_bags/x/deleted.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download --purge of the deleted file deletes it" do
+ knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ EOM
+ end
+ it "knife download of the entire data bag downloads everything" do
+ knife("download /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download --purge of the entire data bag downloads everything" do
+ knife("download --purge /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ context "when cwd is the /data_bags directory" do
+ before do
+ cwd "data_bags"
+ end
+ it "knife download fails" do
+ knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
+ end
+ it "knife download --purge . downloads everything" do
+ knife("download --purge .").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ it "knife download --purge * downloads everything" do
+ knife("download --purge *").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/z.rb", ""
+ end
+
+ when_the_chef_server "has a modified, added and deleted file for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "metadata.rb" => cb_metadata("x", "1.0.0", "#extra content"), "y.rb" => "hi" }
+ end
+
+ it "knife download of a modified file succeeds" do
+ knife("download /cookbooks/x/metadata.rb").should_succeed "Updated /cookbooks/x/metadata.rb\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/y.rb
+ A\t/cookbooks/x/z.rb
+ EOM
+ end
+ it "knife download of a deleted file does nothing" do
+ knife("download /cookbooks/x/z.rb").should_succeed ""
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/y.rb
+ A\t/cookbooks/x/z.rb
+ EOM
+ end
+ it "knife download --purge of a deleted file succeeds" do
+ knife("download --purge /cookbooks/x/z.rb").should_succeed "Deleted extra entry /cookbooks/x/z.rb (purge is on)\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/y.rb
+ EOM
+ end
+ it "knife download of an added file succeeds" do
+ knife("download /cookbooks/x/y.rb").should_succeed "Created /cookbooks/x/y.rb\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ A\t/cookbooks/x/z.rb
+ EOM
+ end
+ it "knife download of the cookbook itself succeeds" do
+ knife("download /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/metadata.rb
+ Created /cookbooks/x/y.rb
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ A\t/cookbooks/x/z.rb
+ EOM
+ end
+ it "knife download --purge of the cookbook itself succeeds" do
+ knife("download --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/metadata.rb
+ Created /cookbooks/x/y.rb
+ Deleted extra entry /cookbooks/x/z.rb (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the latest version" do
+ knife("download --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/metadata.rb
+ Created /cookbooks/x/onlyin1.0.1.rb
+ Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the updated file" do
+ knife("download --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/onlyin1.0.0.rb
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the latest version" do
+ knife("download --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/metadata.rb
+ Created /cookbooks/x/onlyin1.0.1.rb
+ Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the old version" do
+ knife("download --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x/metadata.rb
+ Created /cookbooks/x/onlyin0.9.9.rb
+ Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "has a role" do
+ before do
+ role "x", {}
+ end
+ when_the_repository "has the role in ruby" do
+ before do
+ file "roles/x.rb", <<~EOM
+ name "x"
+ description "x"
+ EOM
+ end
+
+ it "knife download refuses to change the role" do
+ knife("download /roles/x.json").should_succeed "", stderr: "WARNING: /roles/x.rb cannot be updated (can't safely update ruby files).\n"
+ knife("diff --name-status /roles/x.json").should_succeed "M\t/roles/x.rb\n"
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before do
+ environment "x", {}
+ end
+ when_the_repository "has an environment with bad JSON" do
+ before do
+ file "environments/x.json", "{"
+ end
+ it "knife download succeeds" do
+ warning = <<~EOH
+ WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
+ {
+ (right here) ------^
+
+ EOH
+ knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n", stderr: warning
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+
+ when_the_repository "has the same environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+ it "knife download succeeds" do
+ knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+
+ when_the_repository "has the same environment with no name in the file" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+ it "knife download succeeds" do
+ knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+ end
+ end # without versioned cookbooks
+
+ context "with versioned cookbooks" do
+ before { Chef::Config[:versioned_cookbooks] = true }
+
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", {}
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ node "x", {}
+ role "x", {}
+ user "x", {}
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife download downloads everything" do
+ knife("download /").should_succeed <<~EOM
+ Created /clients/chef-validator.json
+ Created /clients/chef-webui.json
+ Created /clients/x.json
+ Created /cookbooks/x-1.0.0
+ Created /cookbooks/x-1.0.0/metadata.rb
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments/_default.json
+ Created /environments/x.json
+ Created /nodes/x.json
+ Created /roles/x.json
+ Created /users/admin.json
+ Created /users/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife download makes no changes" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife download --purge makes no changes" do
+ knife("download --purge /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", { "description" => "blarghle" }
+ end
+
+ it "knife download changes the role" do
+ knife("download /").should_succeed "Updated /roles/x.json\n"
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "except the role file is textually different, but not ACTUALLY different" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "chef_type": "role" ,
+ "default_attributes": {
+ },
+ "env_run_lists": {
+ },
+ "json_class": "Chef::Role",
+ "name": "x",
+ "description": "",
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife download / does not change anything" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x-1.0.0/blah.rb", ""
+ file "cookbooks/x-2.0.0/metadata.rb", 'version "2.0.0"'
+ file "cookbooks/y-1.0.0/metadata.rb", 'version "1.0.0"'
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife download does nothing" do
+ knife("download /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ A\t/clients/y.json
+ A\t/cookbooks/x-1.0.0/blah.rb
+ A\t/cookbooks/x-2.0.0
+ A\t/cookbooks/y-1.0.0
+ A\t/data_bags/x/z.json
+ A\t/data_bags/y
+ A\t/environments/y.json
+ A\t/nodes/y.json
+ A\t/roles/y.json
+ A\t/users/y.json
+ EOM
+ end
+
+ it "knife download --purge deletes the extra files" do
+ knife("download --purge /").should_succeed <<~EOM
+ Deleted extra entry /clients/y.json (purge is on)
+ Deleted extra entry /cookbooks/x-1.0.0/blah.rb (purge is on)
+ Deleted extra entry /cookbooks/x-2.0.0 (purge is on)
+ Deleted extra entry /cookbooks/y-1.0.0 (purge is on)
+ Deleted extra entry /data_bags/x/z.json (purge is on)
+ Deleted extra entry /data_bags/y (purge is on)
+ Deleted extra entry /environments/y.json (purge is on)
+ Deleted extra entry /nodes/y.json (purge is on)
+ Deleted extra entry /roles/y.json (purge is on)
+ Deleted extra entry /users/y.json (purge is on)
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife download creates the extra files" do
+ knife("download /").should_succeed <<~EOM
+ Created /clients
+ Created /clients/chef-validator.json
+ Created /clients/chef-webui.json
+ Created /clients/x.json
+ Created /cookbooks
+ Created /cookbooks/x-1.0.0
+ Created /cookbooks/x-1.0.0/metadata.rb
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments
+ Created /environments/_default.json
+ Created /environments/x.json
+ Created /nodes
+ Created /nodes/x.json
+ Created /roles
+ Created /roles/x.json
+ Created /users
+ Created /users/admin.json
+ Created /users/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "when current directory is top level" do
+ before do
+ cwd "."
+ end
+ it "knife download with no parameters reports an error" do
+ knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
+ end
+ end
+ end
+ end
+
+ # Test download of an item when the other end doesn't even have the container
+ when_the_repository "is empty" do
+ when_the_chef_server "has two data bag items" do
+ before do
+ data_bag "x", { "y" => {}, "z" => {} }
+ end
+
+ it "knife download of one data bag item itself succeeds" do
+ knife("download /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/z.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has three data bag items" do
+ before do
+ file "data_bags/x/deleted.json", <<~EOM
+ {
+ "id": "deleted"
+ }
+ EOM
+ file "data_bags/x/modified.json", <<~EOM
+ {
+ "id": "modified"
+ }
+ EOM
+ file "data_bags/x/unmodified.json", <<~EOM
+ {
+ "id": "unmodified"
+ }
+ EOM
+ end
+
+ when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
+ before do
+ data_bag "x", {
+ "added" => {},
+ "modified" => { "foo" => "bar" },
+ "unmodified" => {},
+ }
+ end
+
+ it "knife download of the modified file succeeds" do
+ knife("download /data_bags/x/modified.json").should_succeed <<~EOM
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the unmodified file does nothing" do
+ knife("download /data_bags/x/unmodified.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the added file succeeds" do
+ knife("download /data_bags/x/added.json").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download of the deleted file does nothing" do
+ knife("download /data_bags/x/deleted.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download --purge of the deleted file deletes it" do
+ knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/added.json
+ M\t/data_bags/x/modified.json
+ EOM
+ end
+ it "knife download of the entire data bag downloads everything" do
+ knife("download /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ A\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife download --purge of the entire data bag downloads everything" do
+ knife("download --purge /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ context "when cwd is the /data_bags directory" do
+ before do
+ cwd "data_bags"
+ end
+ it "knife download fails" do
+ knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
+ end
+ it "knife download --purge . downloads everything" do
+ knife("download --purge .").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ it "knife download --purge * downloads everything" do
+ knife("download --purge *").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", 'name "x"; version "1.0.0"#unmodified'
+ file "cookbooks/x-1.0.0/z.rb", ""
+ end
+
+ when_the_chef_server "has a modified, added and deleted file for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "y.rb" => "hi" }
+ end
+
+ it "knife download of a modified file succeeds" do
+ knife("download /cookbooks/x-1.0.0/metadata.rb").should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x-1.0.0/y.rb
+ A\t/cookbooks/x-1.0.0/z.rb
+ EOM
+ end
+ it "knife download of a deleted file does nothing" do
+ knife("download /cookbooks/x-1.0.0/z.rb").should_succeed ""
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x-1.0.0/metadata.rb
+ D\t/cookbooks/x-1.0.0/y.rb
+ A\t/cookbooks/x-1.0.0/z.rb
+ EOM
+ end
+ it "knife download --purge of a deleted file succeeds" do
+ knife("download --purge /cookbooks/x-1.0.0/z.rb").should_succeed "Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x-1.0.0/metadata.rb
+ D\t/cookbooks/x-1.0.0/y.rb
+ EOM
+ end
+ it "knife download of an added file succeeds" do
+ knife("download /cookbooks/x-1.0.0/y.rb").should_succeed "Created /cookbooks/x-1.0.0/y.rb\n"
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x-1.0.0/metadata.rb
+ A\t/cookbooks/x-1.0.0/z.rb
+ EOM
+ end
+ it "knife download of the cookbook itself succeeds" do
+ knife("download /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0/metadata.rb
+ Created /cookbooks/x-1.0.0/y.rb
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ A\t/cookbooks/x-1.0.0/z.rb
+ EOM
+ end
+ it "knife download --purge of the cookbook itself succeeds" do
+ knife("download --purge /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0/metadata.rb
+ Created /cookbooks/x-1.0.0/y.rb
+ Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the latest version" do
+ knife("download --purge /cookbooks").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
+ Created /cookbooks/x-1.0.1
+ Created /cookbooks/x-1.0.1/metadata.rb
+ Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks downloads the updated file" do
+ knife("download --purge /cookbooks").should_succeed <<~EOM
+ Created /cookbooks/x-0.9.9
+ Created /cookbooks/x-0.9.9/metadata.rb
+ Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
+ Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife download /cookbooks/x downloads the latest version" do
+ knife("download --purge /cookbooks").should_succeed <<~EOM
+ Created /cookbooks/x-1.0.1
+ Created /cookbooks/x-1.0.1/metadata.rb
+ Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
+ Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife download --purge /cookbooks downloads the old version and deletes the new version" do
+ knife("download --purge /cookbooks").should_succeed <<~EOM
+ Created /cookbooks/x-0.9.9
+ Created /cookbooks/x-0.9.9/metadata.rb
+ Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
+ Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before do
+ environment "x", {}
+ end
+
+ when_the_repository "has the same environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+
+ it "knife download succeeds" do
+ knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+
+ when_the_repository "has the same environment with no name in the file" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+
+ it "knife download succeeds" do
+ knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+ end
+ end # with versioned cookbooks
+
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "x", "1.0.0"
+ end
+
+ when_the_repository "is empty" do
+ it "knife download /cookbooks/x signs all requests" do
+
+ # Check that BasicClient.request() always gets called with X-OPS-USERID
+ original_new = Chef::HTTP::BasicClient.method(:new)
+ expect(Chef::HTTP::BasicClient).to receive(:new) { |args|
+ new_result = original_new.call(*args)
+ original_request = new_result.method(:request)
+ expect(new_result).to receive(:request) { |method, url, body, headers, &response_handler|
+ expect(headers["X-OPS-USERID"]).not_to be_nil
+ original_request.call(method, url, body, headers, &response_handler)
+ }.at_least(:once)
+ new_result
+ }.at_least(:once)
+
+ knife("download /cookbooks/x").should_succeed <<~EOM
+ Created /cookbooks
+ Created /cookbooks/x
+ Created /cookbooks/x/metadata.rb
+ EOM
+ end
+ end
+ end
+
+ when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
+ before do
+ user "foo", {}
+ user "bar", {}
+ user "foobar", {}
+ organization "foo", { "full_name" => "Something" }
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
+ end
+
+ when_the_repository "has all the default stuff" do
+ before do
+ knife("download /").should_succeed <<~EOM
+ Created /acls
+ Created /acls/clients
+ Created /acls/clients/foo-validator.json
+ Created /acls/containers
+ Created /acls/containers/clients.json
+ Created /acls/containers/containers.json
+ Created /acls/containers/cookbook_artifacts.json
+ Created /acls/containers/cookbooks.json
+ Created /acls/containers/data.json
+ Created /acls/containers/environments.json
+ Created /acls/containers/groups.json
+ Created /acls/containers/nodes.json
+ Created /acls/containers/policies.json
+ Created /acls/containers/policy_groups.json
+ Created /acls/containers/roles.json
+ Created /acls/containers/sandboxes.json
+ Created /acls/cookbook_artifacts
+ Created /acls/cookbooks
+ Created /acls/data_bags
+ Created /acls/environments
+ Created /acls/environments/_default.json
+ Created /acls/groups
+ Created /acls/groups/admins.json
+ Created /acls/groups/billing-admins.json
+ Created /acls/groups/clients.json
+ Created /acls/groups/users.json
+ Created /acls/nodes
+ Created /acls/policies
+ Created /acls/policy_groups
+ Created /acls/roles
+ Created /acls/organization.json
+ Created /clients
+ Created /clients/foo-validator.json
+ Created /containers
+ Created /containers/clients.json
+ Created /containers/containers.json
+ Created /containers/cookbook_artifacts.json
+ Created /containers/cookbooks.json
+ Created /containers/data.json
+ Created /containers/environments.json
+ Created /containers/groups.json
+ Created /containers/nodes.json
+ Created /containers/policies.json
+ Created /containers/policy_groups.json
+ Created /containers/roles.json
+ Created /containers/sandboxes.json
+ Created /cookbook_artifacts
+ Created /cookbooks
+ Created /data_bags
+ Created /environments
+ Created /environments/_default.json
+ Created /groups
+ Created /groups/admins.json
+ Created /groups/billing-admins.json
+ Created /groups/clients.json
+ Created /groups/users.json
+ Created /invitations.json
+ Created /members.json
+ Created /nodes
+ Created /org.json
+ Created /policies
+ Created /policy_groups
+ Created /roles
+ EOM
+ end
+
+ context "and the server has one of each thing" do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ client "x", {}
+ cookbook "x", "1.0.0"
+ cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") }
+ container "x", {}
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ group "x", {}
+ org_invite "foo"
+ org_member "bar"
+ node "x", {}
+ policy "x", "1.0.0", {}
+ policy "blah", "1.0.0", {}
+ policy_group "x", {
+ "policies" => {
+ "x" => { "revision_id" => "1.0.0" },
+ "blah" => { "revision_id" => "1.0.0" },
+ },
+ }
+ role "x", {}
+ end
+
+ before do
+ knife("download /acls /groups/clients.json /groups/users.json").should_succeed <<~EOM
+ Created /acls/clients/x.json
+ Created /acls/containers/x.json
+ Created /acls/cookbook_artifacts/x.json
+ Created /acls/cookbooks/x.json
+ Created /acls/data_bags/x.json
+ Created /acls/environments/x.json
+ Created /acls/groups/x.json
+ Created /acls/nodes/x.json
+ Created /acls/policies/blah.json
+ Created /acls/policies/x.json
+ Created /acls/policy_groups/x.json
+ Created /acls/roles/x.json
+ Updated /groups/clients.json
+ Updated /groups/users.json
+ EOM
+ end
+
+ it "knife download / downloads everything" do
+ knife("download /").should_succeed <<~EOM
+ Created /clients/x.json
+ Created /containers/x.json
+ Created /cookbook_artifacts/x-1x1
+ Created /cookbook_artifacts/x-1x1/metadata.rb
+ Created /cookbooks/x
+ Created /cookbooks/x/metadata.rb
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments/x.json
+ Created /groups/x.json
+ Updated /invitations.json
+ Updated /members.json
+ Created /nodes/x.json
+ Created /policies/blah-1.0.0.json
+ Created /policies/x-1.0.0.json
+ Created /policy_groups/x.json
+ Created /roles/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "and the repository has an identical copy of each thing" do
+ before do
+ # TODO We have to upload acls for an existing group due to a lack of
+ # dependency detection during upload. Fix that!
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "containers/x.json", {}
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/x.json", {}
+ file "groups/x.json", {}
+ file "invitations.json", [ "foo" ]
+ file "members.json", [ "bar" ]
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "org.json", { "full_name" => "Something" }
+ file "policies/x-1.0.0.json", {}
+ file "policies/blah-1.0.0.json", {}
+ file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } }
+ file "roles/x.json", {}
+ end
+
+ it "knife download makes no changes" do
+ knife("download /").should_succeed ""
+ end
+ end
+
+ context "and the repository has a slightly different copy of each thing" do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ file "clients/x.json", { "validator" => true }
+ file "containers/x.json", {}
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.1")
+ file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.1")
+ file "data_bags/x/y.json", { "a" => "b" }
+ file "environments/x.json", { "description" => "foo" }
+ file "groups/x.json", { "description" => "foo" }
+ file "groups/x.json", { "groups" => [ "admin" ] }
+ file "nodes/x.json", { "normal" => { "tags" => [] }, "run_list" => [ "blah" ] }
+ file "org.json", { "full_name" => "Something Else " }
+ file "policies/x-1.0.0.json", { "run_list" => [ "blah" ] }
+ file "policy_groups/x.json", {
+ "policies" => {
+ "x" => { "revision_id" => "1.0.1" },
+ "y" => { "revision_id" => "1.0.0" },
+ },
+ }
+ file "roles/x.json", { "run_list" => [ "blah" ] }
+ end
+
+ it "knife download updates everything" do
+ knife("download /").should_succeed <<~EOM
+ Updated /clients/x.json
+ Updated /cookbook_artifacts/x-1x1/metadata.rb
+ Updated /cookbooks/x/metadata.rb
+ Updated /data_bags/x/y.json
+ Updated /environments/x.json
+ Updated /groups/x.json
+ Updated /invitations.json
+ Updated /members.json
+ Updated /nodes/x.json
+ Updated /org.json
+ Created /policies/blah-1.0.0.json
+ Updated /policies/x-1.0.0.json
+ Updated /policy_groups/x.json
+ Updated /roles/x.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/environment_compare_spec.rb b/knife/spec/integration/environment_compare_spec.rb
new file mode 100644
index 0000000000..a8d207466a
--- /dev/null
+++ b/knife/spec/integration/environment_compare_spec.rb
@@ -0,0 +1,75 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment compare", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some environments" do
+ before do
+ cookbook "blah", "1.0.1"
+ cookbook "blah", "1.1.1"
+ cookbook "krad", "1.1.1"
+ environment "x", {
+ "cookbook_versions" => {
+ "blah" => "= 1.0.0",
+ "krad" => ">= 1.0.0",
+ },
+ }
+ environment "y", {
+ "cookbook_versions" => {
+ "blah" => "= 1.1.0",
+ "krad" => ">= 1.0.0",
+ },
+ }
+ end
+
+ # rubocop:disable Layout/TrailingWhitespace
+ it "displays the cookbooks for a single environment" do
+ knife("environment compare x").should_succeed <<~EOM
+ x
+ blah = 1.0.0
+ krad >= 1.0.0
+
+ EOM
+ end
+
+ it "compares the cookbooks for two environments" do
+ knife("environment compare x y").should_succeed <<~EOM
+ x y
+ blah = 1.0.0 = 1.1.0
+ krad >= 1.0.0 >= 1.0.0
+
+ EOM
+ end
+
+ it "compares the cookbooks for all environments" do
+ knife("environment compare --all").should_succeed <<~EOM
+ x y
+ blah = 1.0.0 = 1.1.0
+ krad >= 1.0.0 >= 1.0.0
+
+ EOM
+ end
+ # rubocop:enable Layout/TrailingWhitespace
+ end
+end
diff --git a/knife/spec/integration/environment_create_spec.rb b/knife/spec/integration/environment_create_spec.rb
new file mode 100644
index 0000000000..496828073d
--- /dev/null
+++ b/knife/spec/integration/environment_create_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Created bah\n" }
+
+ when_the_chef_server "is empty" do
+ it "creates a new environment" do
+ knife("environment create bah").should_succeed out
+ end
+
+ it "refuses to add an existing environment" do
+ pending "Knife environment create must not blindly overwrite an existing environment"
+ knife("environment create bah").should_succeed out
+ expect { knife("environment create bah") }.to raise_error(Net::HTTPClientException)
+ end
+
+ end
+end
diff --git a/knife/spec/integration/environment_delete_spec.rb b/knife/spec/integration/environment_delete_spec.rb
new file mode 100644
index 0000000000..93427aaf2f
--- /dev/null
+++ b/knife/spec/integration/environment_delete_spec.rb
@@ -0,0 +1,37 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has an environment" do
+ before do
+ environment "y", {}
+ end
+
+ it "deletes an environment" do
+ knife("environment delete y", input: "y").should_succeed "Do you really want to delete y? (Y/N) Deleted y\n"
+ end
+
+ end
+end
diff --git a/knife/spec/integration/environment_from_file_spec.rb b/knife/spec/integration/environment_from_file_spec.rb
new file mode 100644
index 0000000000..e5ba056bb7
--- /dev/null
+++ b/knife/spec/integration/environment_from_file_spec.rb
@@ -0,0 +1,116 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment from file", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ # include_context "default config options"
+
+ let(:env_dir) { "#{@repository_dir}/environments" }
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has some environments" do
+ before do
+
+ file "environments/cons.json", <<~EOM
+ {
+ "name": "cons",
+ "description": "An environment",
+ "cookbook_versions": {
+
+ },
+ "json_class": "Chef::Environment",
+ "chef_type": "environment",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ file "environments/car.json", <<~EOM
+ {
+ "name": "car",
+ "description": "An environment for list nodes",
+ "cookbook_versions": {
+
+ },
+ "json_class": "Chef::Environment",
+ "chef_type": "environment",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ file "environments/cdr.json", <<~EOM
+ {
+ "name": "cdr",
+ "description": "An environment for last nodes",
+ "cookbook_versions": {
+
+ },
+ "json_class": "Chef::Environment",
+ "chef_type": "environment",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ end
+
+ it "uploads a single file" do
+ knife("environment from file #{env_dir}/cons.json").should_succeed stderr: <<~EOM
+ Updated Environment cons
+ EOM
+ end
+
+ it "uploads many files" do
+ knife("environment from file #{env_dir}/cons.json #{env_dir}/car.json #{env_dir}/cdr.json").should_succeed stderr: <<~EOM
+ Updated Environment cons
+ Updated Environment car
+ Updated Environment cdr
+ EOM
+ end
+
+ it "uploads all environments in the repository" do
+ cwd(".")
+ knife("environment from file --all")
+ knife("environment list").should_succeed <<~EOM
+ _default
+ car
+ cdr
+ cons
+ EOM
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/integration/environment_list_spec.rb b/knife/spec/integration/environment_list_spec.rb
new file mode 100644
index 0000000000..f74b2b6360
--- /dev/null
+++ b/knife/spec/integration/environment_list_spec.rb
@@ -0,0 +1,42 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some environments" do
+ before do
+ environment "b", {}
+ environment "y", {}
+ end
+
+ it "lists all the environments" do
+ knife("environment list").should_succeed <<~EOM
+ _default
+ b
+ y
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/environment_show_spec.rb b/knife/spec/integration/environment_show_spec.rb
new file mode 100644
index 0000000000..b961e85734
--- /dev/null
+++ b/knife/spec/integration/environment_show_spec.rb
@@ -0,0 +1,77 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife environment show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some environments" do
+ before do
+ environment "b", {
+ "default_attributes" => { "foo" => "bar", "baz" => { "raz.my" => "mataz" } },
+ }
+ end
+
+ # rubocop:disable Layout/TrailingWhitespace
+ it "shows an environment" do
+ knife("environment show b").should_succeed <<~EOM
+ chef_type: environment
+ cookbook_versions:
+ default_attributes:
+ baz:
+ raz.my: mataz
+ foo: bar
+ description:
+ json_class: Chef::Environment
+ name: b
+ override_attributes:
+ EOM
+ end
+ # rubocop:enable Layout/TrailingWhitespace
+
+ it "shows the requested attribute of an environment" do
+ knife("environment show b -a default_attributes").should_succeed <<~EOM
+ b:
+ default_attributes:
+ baz:
+ raz.my: mataz
+ foo: bar
+ EOM
+ end
+
+ it "shows the requested nested attribute of an environment" do
+ knife("environment show b -a default_attributes.baz").should_succeed <<~EON
+ b:
+ default_attributes.baz:
+ raz.my: mataz
+ EON
+ end
+
+ it "shows the requested attribute of an environment with custom field separator" do
+ knife("environment show b -S: -a default_attributes:baz").should_succeed <<~EOT
+ b:
+ default_attributes:baz:
+ raz.my: mataz
+ EOT
+ end
+ end
+end
diff --git a/knife/spec/integration/list_spec.rb b/knife/spec/integration/list_spec.rb
new file mode 100644
index 0000000000..8228ba6056
--- /dev/null
+++ b/knife/spec/integration/list_spec.rb
@@ -0,0 +1,1060 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/list"
+
+describe "knife list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "is empty" do
+ it "knife list / returns all top level directories" do
+ knife("list /").should_succeed <<~EOM
+ /clients
+ /cookbooks
+ /data_bags
+ /environments
+ /nodes
+ /roles
+ /users
+ EOM
+ end
+
+ it "knife list -R / returns everything" do
+ knife("list -R /").should_succeed <<~EOM
+ /:
+ clients
+ cookbooks
+ data_bags
+ environments
+ nodes
+ roles
+ users
+
+ /clients:
+ chef-validator.json
+ chef-webui.json
+
+ /cookbooks:
+
+ /data_bags:
+
+ /environments:
+ _default.json
+
+ /nodes:
+
+ /roles:
+
+ /users:
+ admin.json
+ EOM
+ end
+ end
+
+ when_the_chef_server "has plenty of stuff in it" do
+ before do
+ client "client1", {}
+ client "client2", {}
+ cookbook "cookbook1", "1.0.0"
+ cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } }
+ data_bag "bag1", { "item1" => {}, "item2" => {} }
+ data_bag "bag2", { "item1" => {}, "item2" => {} }
+ environment "environment1", {}
+ environment "environment2", {}
+ node "node1", {}
+ node "node2", {}
+ policy "policy1", "1.2.3", {}
+ policy "policy2", "1.2.3", {}
+ policy "policy2", "1.3.5", {}
+ role "role1", {}
+ role "role2", {}
+ user "user1", {}
+ user "user2", {}
+ end
+
+ it "knife list / returns all top level directories" do
+ knife("list /").should_succeed <<~EOM
+ /clients
+ /cookbooks
+ /data_bags
+ /environments
+ /nodes
+ /roles
+ /users
+ EOM
+ end
+
+ it "knife list -R / returns everything" do
+ knife("list -R /").should_succeed <<~EOM
+ /:
+ clients
+ cookbooks
+ data_bags
+ environments
+ nodes
+ roles
+ users
+
+ /clients:
+ chef-validator.json
+ chef-webui.json
+ client1.json
+ client2.json
+
+ /cookbooks:
+ cookbook1
+ cookbook2
+
+ /cookbooks/cookbook1:
+ metadata.rb
+
+ /cookbooks/cookbook2:
+ metadata.rb
+ recipes
+
+ /cookbooks/cookbook2/recipes:
+ default.rb
+
+ /data_bags:
+ bag1
+ bag2
+
+ /data_bags/bag1:
+ item1.json
+ item2.json
+
+ /data_bags/bag2:
+ item1.json
+ item2.json
+
+ /environments:
+ _default.json
+ environment1.json
+ environment2.json
+
+ /nodes:
+ node1.json
+ node2.json
+
+ /roles:
+ role1.json
+ role2.json
+
+ /users:
+ admin.json
+ user1.json
+ user2.json
+ EOM
+ end
+
+ it "knife list -R --flat / returns everything" do
+ knife("list -R --flat /").should_succeed <<~EOM
+ /clients
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/client1.json
+ /clients/client2.json
+ /cookbooks
+ /cookbooks/cookbook1
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes
+ /cookbooks/cookbook2/recipes/default.rb
+ /data_bags
+ /data_bags/bag1
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles
+ /roles/role1.json
+ /roles/role2.json
+ /users
+ /users/admin.json
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ it "knife list -Rfp / returns everything" do
+ knife("list -Rfp /").should_succeed <<~EOM
+ /clients/
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/client1.json
+ /clients/client2.json
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes/
+ /cookbooks/cookbook2/recipes/default.rb
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes/
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles/
+ /roles/role1.json
+ /roles/role2.json
+ /users/
+ /users/admin.json
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ it "knife list /cookbooks returns the list of cookbooks" do
+ knife("list /cookbooks").should_succeed <<~EOM
+ /cookbooks/cookbook1
+ /cookbooks/cookbook2
+ EOM
+ end
+
+ it "knife list /cookbooks/*2/*/*.rb returns the one file" do
+ knife("list /cookbooks/*2/*/*.rb").should_succeed "/cookbooks/cookbook2/recipes/default.rb\n"
+ end
+
+ it "knife list /**.rb returns all ruby files" do
+ knife("list /**.rb").should_succeed <<~EOM
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes/default.rb
+ EOM
+ end
+
+ it "knife list /cookbooks/**.rb returns all ruby files" do
+ knife("list /cookbooks/**.rb").should_succeed <<~EOM
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes/default.rb
+ EOM
+ end
+
+ it "knife list /**.json returns all json files" do
+ knife("list /**.json").should_succeed <<~EOM
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/client1.json
+ /clients/client2.json
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles/role1.json
+ /roles/role2.json
+ /users/admin.json
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ it "knife list /data**.json returns all data bag json files" do
+ knife("list /data**.json").should_succeed <<~EOM
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ EOM
+ end
+
+ it "knife list /environments/missing_file.json reports missing file" do
+ knife("list /environments/missing_file.json").should_fail "ERROR: /environments/missing_file.json: No such file or directory\n"
+ end
+
+ context "missing file/directory exact match tests" do
+ it "knife list /blarghle reports missing directory" do
+ knife("list /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n"
+ end
+ end
+
+ context "symlink tests" do
+ when_the_repository "is empty" do
+ context "when cwd is at the top of the repository" do
+ before { cwd "." }
+
+ it "knife list -Rfp returns everything" do
+ knife("list -Rfp").should_succeed <<~EOM
+ clients/
+ clients/chef-validator.json
+ clients/chef-webui.json
+ clients/client1.json
+ clients/client2.json
+ cookbooks/
+ cookbooks/cookbook1/
+ cookbooks/cookbook1/metadata.rb
+ cookbooks/cookbook2/
+ cookbooks/cookbook2/metadata.rb
+ cookbooks/cookbook2/recipes/
+ cookbooks/cookbook2/recipes/default.rb
+ data_bags/
+ data_bags/bag1/
+ data_bags/bag1/item1.json
+ data_bags/bag1/item2.json
+ data_bags/bag2/
+ data_bags/bag2/item1.json
+ data_bags/bag2/item2.json
+ environments/
+ environments/_default.json
+ environments/environment1.json
+ environments/environment2.json
+ nodes/
+ nodes/node1.json
+ nodes/node2.json
+ roles/
+ roles/role1.json
+ roles/role2.json
+ users/
+ users/admin.json
+ users/user1.json
+ users/user2.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbooks directory" do
+ before { directory "cookbooks" }
+ context "when cwd is in cookbooks/" do
+ before { cwd "cookbooks" }
+
+ it "knife list -Rfp / returns everything" do
+ knife("list -Rfp /").should_succeed <<~EOM
+ /clients/
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/client1.json
+ /clients/client2.json
+ ./
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes/
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles/
+ /roles/role1.json
+ /roles/role2.json
+ /users/
+ /users/admin.json
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ it "knife list -Rfp .. returns everything" do
+ knife("list -Rfp ..").should_succeed <<~EOM
+ /clients/
+ /clients/chef-validator.json
+ /clients/chef-webui.json
+ /clients/client1.json
+ /clients/client2.json
+ ./
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes/
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles/
+ /roles/role1.json
+ /roles/role2.json
+ /users/
+ /users/admin.json
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbooks/cookbook2 directory" do
+ before { directory "cookbooks/cookbook2" }
+
+ context "when cwd is in cookbooks/cookbook2" do
+ before { cwd "cookbooks/cookbook2" }
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ metadata.rb
+ recipes/
+ recipes/default.rb
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbooks directory and a symlinked cookbooks directory", skip: (ChefUtils.windows?) do
+ before do
+ directory "cookbooks"
+ symlink "symlinked", "cookbooks"
+ end
+
+ context "when cwd is in cookbooks/" do
+ before { cwd "cookbooks" }
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ EOM
+ end
+ end
+
+ context "when cwd is in symlinked/" do
+ before { cwd "symlinked" }
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a real_cookbooks directory and a cookbooks symlink to it", skip: (ChefUtils.windows?) do
+ before do
+ directory "real_cookbooks"
+ symlink "cookbooks", "real_cookbooks"
+ end
+
+ context "when cwd is in real_cookbooks/" do
+ before { cwd "real_cookbooks" }
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ EOM
+ end
+ end
+
+ context "when cwd is in cookbooks/" do
+ before { cwd "cookbooks" }
+
+ it "knife list -Rfp returns cookbooks" do
+ knife("list -Rfp").should_succeed <<~EOM
+ cookbook1/
+ cookbook1/metadata.rb
+ cookbook2/
+ cookbook2/metadata.rb
+ cookbook2/recipes/
+ cookbook2/recipes/default.rb
+ EOM
+ end
+ end
+ end
+ end
+ end
+
+ context "--local" do
+ when_the_repository "is empty" do
+ it "knife list --local / returns nothing" do
+ knife("list --local /").should_succeed ""
+ end
+
+ it "knife list /roles returns nothing" do
+ knife("list --local /roles").should_fail "ERROR: /roles: No such file or directory\n"
+ end
+ end
+
+ when_the_repository "has a bunch of stuff" do
+ before do
+ file "clients/client1.json", {}
+ file "clients/client2.json", {}
+
+ directory "cookbooks/cookbook1" do
+ file "metadata.rb", cb_metadata("cookbook1", "1.0.0")
+ end
+ directory "cookbooks/cookbook2" do
+ file "metadata.rb", cb_metadata("cookbook2", "2.0.0")
+ file "recipes/default.rb", ""
+ end
+
+ directory "data_bags" do
+ directory "bag1" do
+ file "item1.json", {}
+ file "item2.json", {}
+ end
+ directory "bag2" do
+ file "item1.json", {}
+ file "item2.json", {}
+ end
+ end
+
+ file "environments/environment1.json", {}
+ file "environments/environment2.json", {}
+ file "nodes/node1.json", {}
+ file "nodes/node2.json", {}
+
+ file "roles/role1.json", {}
+ file "roles/role2.json", {}
+ file "users/user1.json", {}
+ file "users/user2.json", {}
+ end
+
+ it "knife list -Rfp / returns everything" do
+ knife("list -Rp --local --flat /").should_succeed <<~EOM
+ /clients/
+ /clients/client1.json
+ /clients/client2.json
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes/
+ /cookbooks/cookbook2/recipes/default.rb
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/
+ /environments/environment1.json
+ /environments/environment2.json
+ /nodes/
+ /nodes/node1.json
+ /nodes/node2.json
+ /roles/
+ /roles/role1.json
+ /roles/role2.json
+ /users/
+ /users/user1.json
+ /users/user2.json
+ EOM
+ end
+
+ context "missing file/directory tests" do
+ it "knife list --local /blarghle reports missing directory" do
+ knife("list --local /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n"
+ end
+
+ it "knife list /roles/blarghle reports missing directory" do
+ knife("list --local /roles/blarghle").should_fail "ERROR: /roles/blarghle: No such file or directory\n"
+ end
+
+ it "knife list /roles/blarghle/blorghle reports missing directory" do
+ knife("list --local /roles/blarghle/blorghle").should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n"
+ end
+ end
+ end
+ end
+
+ when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
+ before do
+ organization "foo"
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
+ end
+
+ context "and is empty" do
+ it "knife list / returns all top level directories" do
+ knife("list /").should_succeed <<~EOM
+ /acls
+ /clients
+ /containers
+ /cookbook_artifacts
+ /cookbooks
+ /data_bags
+ /environments
+ /groups
+ /invitations.json
+ /members.json
+ /nodes
+ /org.json
+ /policies
+ /policy_groups
+ /roles
+ EOM
+ end
+
+ it "knife list -R / returns everything" do
+ knife("list -R /").should_succeed <<~EOM
+ /:
+ acls
+ clients
+ containers
+ cookbook_artifacts
+ cookbooks
+ data_bags
+ environments
+ groups
+ invitations.json
+ members.json
+ nodes
+ org.json
+ policies
+ policy_groups
+ roles
+
+ /acls:
+ clients
+ containers
+ cookbook_artifacts
+ cookbooks
+ data_bags
+ environments
+ groups
+ nodes
+ organization.json
+ policies
+ policy_groups
+ roles
+
+ /acls/clients:
+ foo-validator.json
+
+ /acls/containers:
+ clients.json
+ containers.json
+ cookbook_artifacts.json
+ cookbooks.json
+ data.json
+ environments.json
+ groups.json
+ nodes.json
+ policies.json
+ policy_groups.json
+ roles.json
+ sandboxes.json
+
+ /acls/cookbook_artifacts:
+
+ /acls/cookbooks:
+
+ /acls/data_bags:
+
+ /acls/environments:
+ _default.json
+
+ /acls/groups:
+ admins.json
+ billing-admins.json
+ clients.json
+ users.json
+
+ /acls/nodes:
+
+ /acls/policies:
+
+ /acls/policy_groups:
+
+ /acls/roles:
+
+ /clients:
+ foo-validator.json
+
+ /containers:
+ clients.json
+ containers.json
+ cookbook_artifacts.json
+ cookbooks.json
+ data.json
+ environments.json
+ groups.json
+ nodes.json
+ policies.json
+ policy_groups.json
+ roles.json
+ sandboxes.json
+
+ /cookbook_artifacts:
+
+ /cookbooks:
+
+ /data_bags:
+
+ /environments:
+ _default.json
+
+ /groups:
+ admins.json
+ billing-admins.json
+ clients.json
+ users.json
+
+ /nodes:
+
+ /policies:
+
+ /policy_groups:
+
+ /roles:
+ EOM
+ end
+ end
+
+ it "knife list -R / returns everything" do
+ knife("list -R /").should_succeed <<~EOM
+ /:
+ acls
+ clients
+ containers
+ cookbook_artifacts
+ cookbooks
+ data_bags
+ environments
+ groups
+ invitations.json
+ members.json
+ nodes
+ org.json
+ policies
+ policy_groups
+ roles
+
+ /acls:
+ clients
+ containers
+ cookbook_artifacts
+ cookbooks
+ data_bags
+ environments
+ groups
+ nodes
+ organization.json
+ policies
+ policy_groups
+ roles
+
+ /acls/clients:
+ foo-validator.json
+
+ /acls/containers:
+ clients.json
+ containers.json
+ cookbook_artifacts.json
+ cookbooks.json
+ data.json
+ environments.json
+ groups.json
+ nodes.json
+ policies.json
+ policy_groups.json
+ roles.json
+ sandboxes.json
+
+ /acls/cookbook_artifacts:
+
+ /acls/cookbooks:
+
+ /acls/data_bags:
+
+ /acls/environments:
+ _default.json
+
+ /acls/groups:
+ admins.json
+ billing-admins.json
+ clients.json
+ users.json
+
+ /acls/nodes:
+
+ /acls/policies:
+
+ /acls/policy_groups:
+
+ /acls/roles:
+
+ /clients:
+ foo-validator.json
+
+ /containers:
+ clients.json
+ containers.json
+ cookbook_artifacts.json
+ cookbooks.json
+ data.json
+ environments.json
+ groups.json
+ nodes.json
+ policies.json
+ policy_groups.json
+ roles.json
+ sandboxes.json
+
+ /cookbook_artifacts:
+
+ /cookbooks:
+
+ /data_bags:
+
+ /environments:
+ _default.json
+
+ /groups:
+ admins.json
+ billing-admins.json
+ clients.json
+ users.json
+
+ /nodes:
+
+ /policies:
+
+ /policy_groups:
+
+ /roles:
+ EOM
+ end
+
+ context "has plenty of stuff in it" do
+ before do
+ client "client1", {}
+ client "client2", {}
+ container "container1", {}
+ container "container2", {}
+ cookbook "cookbook1", "1.0.0"
+ cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } }
+ cookbook_artifact "cookbook_artifact1", "1x1"
+ cookbook_artifact "cookbook_artifact2", "2x2", { "recipes" => { "default.rb" => "" } }
+ data_bag "bag1", { "item1" => {}, "item2" => {} }
+ data_bag "bag2", { "item1" => {}, "item2" => {} }
+ environment "environment1", {}
+ environment "environment2", {}
+ group "group1", {}
+ group "group2", {}
+ node "node1", {}
+ node "node2", {}
+ org_invite "user1"
+ org_member "user2"
+ policy "policy1", "1.2.3", {}
+ policy "policy2", "1.2.3", {}
+ policy "policy2", "1.3.5", {}
+ policy_group "policy_group1", { "policies" => { "policy1" => { "revision_id" => "1.2.3" } } }
+ policy_group "policy_group2", { "policies" => { "policy2" => { "revision_id" => "1.3.5" } } }
+ role "role1", {}
+ role "role2", {}
+ user "user1", {}
+ user "user2", {}
+ end
+
+ it "knife list -Rfp / returns everything" do
+ knife("list -Rfp /").should_succeed <<~EOM
+ /acls/
+ /acls/clients/
+ /acls/clients/client1.json
+ /acls/clients/client2.json
+ /acls/clients/foo-validator.json
+ /acls/containers/
+ /acls/containers/clients.json
+ /acls/containers/container1.json
+ /acls/containers/container2.json
+ /acls/containers/containers.json
+ /acls/containers/cookbook_artifacts.json
+ /acls/containers/cookbooks.json
+ /acls/containers/data.json
+ /acls/containers/environments.json
+ /acls/containers/groups.json
+ /acls/containers/nodes.json
+ /acls/containers/policies.json
+ /acls/containers/policy_groups.json
+ /acls/containers/roles.json
+ /acls/containers/sandboxes.json
+ /acls/cookbook_artifacts/
+ /acls/cookbook_artifacts/cookbook_artifact1.json
+ /acls/cookbook_artifacts/cookbook_artifact2.json
+ /acls/cookbooks/
+ /acls/cookbooks/cookbook1.json
+ /acls/cookbooks/cookbook2.json
+ /acls/data_bags/
+ /acls/data_bags/bag1.json
+ /acls/data_bags/bag2.json
+ /acls/environments/
+ /acls/environments/_default.json
+ /acls/environments/environment1.json
+ /acls/environments/environment2.json
+ /acls/groups/
+ /acls/groups/admins.json
+ /acls/groups/billing-admins.json
+ /acls/groups/clients.json
+ /acls/groups/group1.json
+ /acls/groups/group2.json
+ /acls/groups/users.json
+ /acls/nodes/
+ /acls/nodes/node1.json
+ /acls/nodes/node2.json
+ /acls/organization.json
+ /acls/policies/
+ /acls/policies/policy1.json
+ /acls/policies/policy2.json
+ /acls/policy_groups/
+ /acls/policy_groups/policy_group1.json
+ /acls/policy_groups/policy_group2.json
+ /acls/roles/
+ /acls/roles/role1.json
+ /acls/roles/role2.json
+ /clients/
+ /clients/client1.json
+ /clients/client2.json
+ /clients/foo-validator.json
+ /containers/
+ /containers/clients.json
+ /containers/container1.json
+ /containers/container2.json
+ /containers/containers.json
+ /containers/cookbook_artifacts.json
+ /containers/cookbooks.json
+ /containers/data.json
+ /containers/environments.json
+ /containers/groups.json
+ /containers/nodes.json
+ /containers/policies.json
+ /containers/policy_groups.json
+ /containers/roles.json
+ /containers/sandboxes.json
+ /cookbook_artifacts/
+ /cookbook_artifacts/cookbook_artifact1-1x1/
+ /cookbook_artifacts/cookbook_artifact1-1x1/metadata.rb
+ /cookbook_artifacts/cookbook_artifact2-2x2/
+ /cookbook_artifacts/cookbook_artifact2-2x2/metadata.rb
+ /cookbook_artifacts/cookbook_artifact2-2x2/recipes/
+ /cookbook_artifacts/cookbook_artifact2-2x2/recipes/default.rb
+ /cookbooks/
+ /cookbooks/cookbook1/
+ /cookbooks/cookbook1/metadata.rb
+ /cookbooks/cookbook2/
+ /cookbooks/cookbook2/metadata.rb
+ /cookbooks/cookbook2/recipes/
+ /cookbooks/cookbook2/recipes/default.rb
+ /data_bags/
+ /data_bags/bag1/
+ /data_bags/bag1/item1.json
+ /data_bags/bag1/item2.json
+ /data_bags/bag2/
+ /data_bags/bag2/item1.json
+ /data_bags/bag2/item2.json
+ /environments/
+ /environments/_default.json
+ /environments/environment1.json
+ /environments/environment2.json
+ /groups/
+ /groups/admins.json
+ /groups/billing-admins.json
+ /groups/clients.json
+ /groups/group1.json
+ /groups/group2.json
+ /groups/users.json
+ /invitations.json
+ /members.json
+ /nodes/
+ /nodes/node1.json
+ /nodes/node2.json
+ /org.json
+ /policies/
+ /policies/policy1-1.2.3.json
+ /policies/policy2-1.2.3.json
+ /policies/policy2-1.3.5.json
+ /policy_groups/
+ /policy_groups/policy_group1.json
+ /policy_groups/policy_group2.json
+ /roles/
+ /roles/role1.json
+ /roles/role2.json
+ EOM
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/node_bulk_delete_spec.rb b/knife/spec/integration/node_bulk_delete_spec.rb
new file mode 100644
index 0000000000..8784b5ea8a
--- /dev/null
+++ b/knife/spec/integration/node_bulk_delete_spec.rb
@@ -0,0 +1,52 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node bulk delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some nodes" do
+ before do
+ node "cons", {}
+ node "car", {}
+ node "cdr", {}
+ node "cat", {}
+ end
+
+ it "deletes all matching nodes" do
+ knife("node bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
+ The following nodes will be deleted:
+
+ car cat
+
+ Are you sure you want to delete these nodes? (Y/N) Deleted node car
+ Deleted node cat
+ EOM
+
+ knife("node list").should_succeed <<~EOM
+ cdr
+ cons
+ EOM
+ end
+ end
+
+end
diff --git a/knife/spec/integration/node_create_spec.rb b/knife/spec/integration/node_create_spec.rb
new file mode 100644
index 0000000000..d3debb8f00
--- /dev/null
+++ b/knife/spec/integration/node_create_spec.rb
@@ -0,0 +1,47 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "openssl"
+
+describe "knife node create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Created node[bah]\n" }
+
+ when_the_chef_server "is empty" do
+ it "creates a new node" do
+ knife("node create bah").should_succeed out
+ end
+
+ it "creates a new validator node" do
+ knife("node create bah").should_succeed out
+ knife("node show bah").should_succeed(/Node Name: bah/)
+ end
+
+ it "refuses to add an existing node" do
+ pending "Knife node create must not blindly overwrite an existing node"
+ knife("node create bah").should_succeed out
+ expect { knife("node create bah") }.to raise_error(Net::HTTPClientException)
+ end
+
+ end
+end
diff --git a/knife/spec/integration/node_delete_spec.rb b/knife/spec/integration/node_delete_spec.rb
new file mode 100644
index 0000000000..3cece6ebaf
--- /dev/null
+++ b/knife/spec/integration/node_delete_spec.rb
@@ -0,0 +1,48 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some nodes" do
+ before do
+ node "cons", {}
+ node "car", {}
+ node "cdr", {}
+ node "cat", {}
+ end
+
+ it "deletes a node" do
+ knife("node delete car", input: "Y").should_succeed <<~EOM
+ Do you really want to delete car? (Y/N) Deleted node[car]
+ EOM
+
+ knife("node list").should_succeed <<~EOM
+ cat
+ cdr
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/node_environment_set_spec.rb b/knife/spec/integration/node_environment_set_spec.rb
new file mode 100644
index 0000000000..51b288fe39
--- /dev/null
+++ b/knife/spec/integration/node_environment_set_spec.rb
@@ -0,0 +1,46 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node environment set", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node and an environment" do
+ before do
+ node "cons", {}
+ environment "lisp", {}
+ end
+
+ it "sets an environment on a node" do
+ knife("node environment set cons lisp").should_succeed(/chef_environment:.*lisp/)
+ knife("node show cons -a chef_environment").should_succeed <<~EOM
+ cons:
+ chef_environment: lisp
+ EOM
+ end
+
+ it "with no environment" do
+ knife("node environment set adam").should_fail stderr: "FATAL: You must specify a node name and an environment.\n",
+ stdout: /^USAGE: knife node environment set NODE ENVIRONMENT\n/
+ end
+ end
+end
diff --git a/knife/spec/integration/node_from_file_spec.rb b/knife/spec/integration/node_from_file_spec.rb
new file mode 100644
index 0000000000..5dcaaaa463
--- /dev/null
+++ b/knife/spec/integration/node_from_file_spec.rb
@@ -0,0 +1,59 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node from file", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ # include_context "default config options"
+
+ let(:node_dir) { "#{@repository_dir}/nodes" }
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has some nodes" do
+ before do
+
+ file "nodes/cons.json", <<~EOM
+ {
+ "name": "cons",
+ "chef_environment": "_default",
+ "run_list": [
+ "recipe[cons]"
+ ]
+ ,
+ "normal": {
+ "tags": [
+
+ ]
+ }
+ }
+ EOM
+
+ end
+
+ it "uploads a single file" do
+ knife("node from file #{node_dir}/cons.json").should_succeed stderr: <<~EOM
+ Updated Node cons
+ EOM
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/integration/node_list_spec.rb b/knife/spec/integration/node_list_spec.rb
new file mode 100644
index 0000000000..65c201be3f
--- /dev/null
+++ b/knife/spec/integration/node_list_spec.rb
@@ -0,0 +1,45 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some nodes" do
+ before do
+ node "cons", {}
+ node "car", {}
+ node "cdr", {}
+ node "cat", {}
+ end
+
+ it "lists all cookbooks" do
+ knife("node list").should_succeed <<~EOM
+ car
+ cat
+ cdr
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/node_run_list_add_spec.rb b/knife/spec/integration/node_run_list_add_spec.rb
new file mode 100644
index 0000000000..72b5328b17
--- /dev/null
+++ b/knife/spec/integration/node_run_list_add_spec.rb
@@ -0,0 +1,54 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node run list add", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node with no run_list" do
+ before do
+ node "cons", {}
+ end
+
+ it "sets the run list" do
+ knife("node run list add cons recipe[foo]").should_succeed(/run_list:\s*recipe\[foo\]\n/)
+ end
+ end
+
+ when_the_chef_server "has a node with a run_list" do
+ before do
+ node "cons", { run_list: ["recipe[bar]"] }
+ end
+
+ it "appends to the run list" do
+ knife("node run list add cons recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m)
+ end
+
+ it "adds to the run list before the specified item" do
+ knife("node run list add cons -b recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[foo\]\n\s*recipe\[bar\]\n/m)
+ end
+
+ it "adds to the run list after the specified item" do
+ knife("node run list add cons -a recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m)
+ end
+ end
+end
diff --git a/knife/spec/integration/node_run_list_remove_spec.rb b/knife/spec/integration/node_run_list_remove_spec.rb
new file mode 100644
index 0000000000..19aeb81806
--- /dev/null
+++ b/knife/spec/integration/node_run_list_remove_spec.rb
@@ -0,0 +1,36 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node run list remove", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node with a run_list" do
+ before do
+ node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
+ end
+
+ it "removes the item from the run list" do
+ knife("node run list remove cons recipe[bar]").should_succeed(/run_list:\s*recipe\[foo\]\n/m)
+ end
+ end
+end
diff --git a/knife/spec/integration/node_run_list_set_spec.rb b/knife/spec/integration/node_run_list_set_spec.rb
new file mode 100644
index 0000000000..d83e74dd04
--- /dev/null
+++ b/knife/spec/integration/node_run_list_set_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node run list set", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node with a run_list" do
+ before do
+ node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
+ end
+
+ it "sets the run list" do
+ knife("node run list set cons recipe[bar]").should_succeed(/run_list:\s*recipe\[bar\]\n/m)
+ end
+
+ it "with no role or recipe" do
+ knife("node run list set cons").should_fail stderr: "FATAL: You must supply both a node name and a run list.\n",
+ stdout: /^USAGE: knife node run_list set NODE ENTRIES \(options\)/m
+ end
+ end
+end
diff --git a/knife/spec/integration/node_show_spec.rb b/knife/spec/integration/node_show_spec.rb
new file mode 100644
index 0000000000..be63011ef8
--- /dev/null
+++ b/knife/spec/integration/node_show_spec.rb
@@ -0,0 +1,36 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node with a run_list" do
+ before do
+ node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
+ end
+
+ it "shows the node" do
+ knife("node show cons").should_succeed(/Run List:\s*recipe\[bar\], recipe\[foo\]\n/m)
+ end
+ end
+end
diff --git a/knife/spec/integration/raw_spec.rb b/knife/spec/integration/raw_spec.rb
new file mode 100644
index 0000000000..8e7e913b02
--- /dev/null
+++ b/knife/spec/integration/raw_spec.rb
@@ -0,0 +1,297 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/raw"
+require "chef/knife/show"
+require "tiny_server"
+
+describe "knife raw", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", "{}"
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => "{}" }
+ environment "x", "{}"
+ node "x", "{}"
+ role "x", "{}"
+ user "x", "{}"
+ end
+
+ it "knife raw /nodes/x returns the node" do
+ knife("raw /nodes/x").should_succeed <<~EOM
+ {
+ "name": "x",
+ "json_class": "Chef::Node",
+ "chef_type": "node",
+ "chef_environment": "_default",
+ "override": {
+
+ },
+ "normal": {
+ "tags": [
+
+ ]
+ },
+ "default": {
+
+ },
+ "automatic": {
+
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife raw /blarghle returns 404" do
+ knife("raw /blarghle").should_fail(/ERROR: Server responded with error 404 "Not Found\s*"/)
+ end
+
+ it "knife raw -m DELETE /roles/x succeeds" do
+ knife("raw -m DELETE /roles/x").should_succeed <<~EOM
+ {
+ "name": "x",
+ "description": "",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ knife("show /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
+ end
+
+ it "knife raw -m PUT -i blah.txt /roles/x succeeds" do
+ Tempfile.open("raw_put_input") do |file|
+ file.write <<~EOM
+ {
+ "name": "x",
+ "description": "eek",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ file.close
+
+ knife("raw -m PUT -i #{file.path} /roles/x").should_succeed <<~EOM
+ {
+ "name": "x",
+ "description": "eek",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ knife("show /roles/x.json").should_succeed <<~EOM
+ /roles/x.json:
+ {
+ "name": "x",
+ "description": "eek",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ end
+ end
+
+ it "knife raw -m POST -i blah.txt /roles succeeds" do
+ Tempfile.open("raw_put_input") do |file|
+ file.write <<~EOM
+ {
+ "name": "y",
+ "description": "eek",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+ },
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+ }
+ }
+ EOM
+ file.close
+
+ knife("raw -m POST -i #{file.path} /roles").should_succeed <<~EOM
+ {
+ "uri": "#{Chef::Config.chef_server_url}/roles/y"
+ }
+ EOM
+ knife("show /roles/y.json").should_succeed <<~EOM
+ /roles/y.json:
+ {
+ "name": "y",
+ "description": "eek",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ end
+ end
+
+ context "When a server returns raw json" do
+ def start_tiny_server(**server_opts)
+ @server = TinyServer::Manager.new(**server_opts)
+ @server.start
+ @api = TinyServer::API.instance
+ @api.clear
+
+ @api.get("/blah", 200, nil, { "Content-Type" => "application/json" }) do
+ '{ "x": "y", "a": "b" }'
+ end
+ end
+
+ def stop_tiny_server
+ @server.stop
+ @server = @api = nil
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = "http://localhost:9000"
+ start_tiny_server
+ end
+
+ after :each do
+ stop_tiny_server
+ end
+
+ it "knife raw /blah returns the prettified json" do
+ knife("raw /blah").should_succeed <<~EOM
+ {
+ "x": "y",
+ "a": "b"
+ }
+ EOM
+ end
+
+ it "knife raw --no-pretty /blah returns the raw json" do
+ knife("raw --no-pretty /blah").should_succeed <<~EOM
+ { "x": "y", "a": "b" }
+ EOM
+ end
+ end
+
+ context "When a server returns text" do
+ def start_tiny_server(**server_opts)
+ @server = TinyServer::Manager.new(**server_opts)
+ @server.start
+ @api = TinyServer::API.instance
+ @api.clear
+
+ @api.get("/blah", 200, nil, { "Content-Type" => "text" }) do
+ '{ "x": "y", "a": "b" }'
+ end
+ end
+
+ def stop_tiny_server
+ @server.stop
+ @server = @api = nil
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = "http://localhost:9000"
+ start_tiny_server
+ end
+
+ after :each do
+ stop_tiny_server
+ end
+
+ it "knife raw /blah returns the raw text" do
+ knife("raw /blah").should_succeed(<<~EOM)
+ { "x": "y", "a": "b" }
+ EOM
+ end
+
+ it "knife raw --no-pretty /blah returns the raw text" do
+ knife("raw --no-pretty /blah").should_succeed(<<~EOM)
+ { "x": "y", "a": "b" }
+ EOM
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/redirection_spec.rb b/knife/spec/integration/redirection_spec.rb
new file mode 100644
index 0000000000..eea5556cff
--- /dev/null
+++ b/knife/spec/integration/redirection_spec.rb
@@ -0,0 +1,64 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tiny_server"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/list"
+
+describe "redirection", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ def start_tiny_server(real_chef_server_url, **server_opts)
+ @server = TinyServer::Manager.new(**server_opts)
+ @server.start
+ @api = TinyServer::API.instance
+ @api.clear
+
+ @api.get("/roles", 302, nil, { "Content-Type" => "text", "Location" => "#{real_chef_server_url}/roles" }) do
+ end
+ end
+
+ def stop_tiny_server
+ @server.stop
+ @server = @api = nil
+ end
+
+ include_context "default config options"
+
+ when_the_chef_server "has a role" do
+ before { role "x", {} }
+
+ context "and another server redirects to it with 302" do
+ before(:each) do
+ real_chef_server_url = Chef::Config.chef_server_url
+ Chef::Config.chef_server_url = "http://localhost:9000"
+ start_tiny_server(real_chef_server_url)
+ end
+
+ after(:each) do
+ stop_tiny_server
+ end
+
+ it "knife list /roles returns the role" do
+ knife("list /roles").should_succeed "/roles/x.json\n"
+ end
+ end
+ end
+end
diff --git a/knife/spec/integration/role_bulk_delete_spec.rb b/knife/spec/integration/role_bulk_delete_spec.rb
new file mode 100644
index 0000000000..76745d9b6a
--- /dev/null
+++ b/knife/spec/integration/role_bulk_delete_spec.rb
@@ -0,0 +1,52 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role bulk delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some roles" do
+ before do
+ role "cons", {}
+ role "car", {}
+ role "cdr", {}
+ role "cat", {}
+ end
+
+ it "deletes all matching roles" do
+ knife("role bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
+ The following roles will be deleted:
+
+ car cat
+
+ Are you sure you want to delete these roles? (Y/N) Deleted role car
+ Deleted role cat
+ EOM
+
+ knife("role list").should_succeed <<~EOM
+ cdr
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/role_create_spec.rb b/knife/spec/integration/role_create_spec.rb
new file mode 100644
index 0000000000..03f59d4b99
--- /dev/null
+++ b/knife/spec/integration/role_create_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role create", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ let(:out) { "Created role[bah]\n" }
+
+ when_the_chef_server "is empty" do
+ it "creates a new role" do
+ knife("role create bah").should_succeed out
+ end
+
+ it "refuses to add an existing role" do
+ pending "Knife role create must not blindly overwrite an existing role"
+ knife("role create bah").should_succeed out
+ expect { knife("role create bah") }.to raise_error(Net::HTTPClientException)
+ end
+
+ end
+end
diff --git a/knife/spec/integration/role_delete_spec.rb b/knife/spec/integration/role_delete_spec.rb
new file mode 100644
index 0000000000..22b36e5572
--- /dev/null
+++ b/knife/spec/integration/role_delete_spec.rb
@@ -0,0 +1,48 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role delete", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some roles" do
+ before do
+ role "cons", {}
+ role "car", {}
+ role "cdr", {}
+ role "cat", {}
+ end
+
+ it "deletes a role" do
+ knife("role delete car", input: "Y").should_succeed <<~EOM
+ Do you really want to delete car? (Y/N) Deleted role[car]
+ EOM
+
+ knife("role list").should_succeed <<~EOM
+ cat
+ cdr
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/role_from_file_spec.rb b/knife/spec/integration/role_from_file_spec.rb
new file mode 100644
index 0000000000..ae296122ce
--- /dev/null
+++ b/knife/spec/integration/role_from_file_spec.rb
@@ -0,0 +1,96 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role from file", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ # include_context "default config options"
+
+ let(:role_dir) { "#{@repository_dir}/roles" }
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has some roles" do
+ before do
+
+ file "roles/cons.json", <<~EOM
+ {
+ "name": "cons",
+ "description": "An role",
+ "json_class": "Chef::role",
+ "chef_type": "role",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ file "roles/car.json", <<~EOM
+ {
+ "name": "car",
+ "description": "A role for list nodes",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ file "roles/cdr.json", <<~EOM
+ {
+ "name": "cdr",
+ "description": "A role for last nodes",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+ "hola": "Amigos!"
+ },
+ "override_attributes": {
+
+ }
+ }
+ EOM
+
+ end
+
+ it "uploads a single file" do
+ knife("role from file #{role_dir}/cons.json").should_succeed stderr: <<~EOM
+ Updated Role cons
+ EOM
+ end
+
+ it "uploads many files" do
+ knife("role from file #{role_dir}/cons.json #{role_dir}/car.json #{role_dir}/cdr.json").should_succeed stderr: <<~EOM
+ Updated Role cons
+ Updated Role car
+ Updated Role cdr
+ EOM
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/integration/role_list_spec.rb b/knife/spec/integration/role_list_spec.rb
new file mode 100644
index 0000000000..39aa28783f
--- /dev/null
+++ b/knife/spec/integration/role_list_spec.rb
@@ -0,0 +1,45 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role list", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some roles" do
+ before do
+ role "cons", {}
+ role "car", {}
+ role "cdr", {}
+ role "cat", {}
+ end
+
+ it "lists all cookbooks" do
+ knife("role list").should_succeed <<~EOM
+ car
+ cat
+ cdr
+ cons
+ EOM
+ end
+
+ end
+end
diff --git a/knife/spec/integration/role_show_spec.rb b/knife/spec/integration/role_show_spec.rb
new file mode 100644
index 0000000000..a4ecea1d61
--- /dev/null
+++ b/knife/spec/integration/role_show_spec.rb
@@ -0,0 +1,51 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife role show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has some roles" do
+ before do
+ role "cons", {}
+ role "car", {}
+ role "cdr", {}
+ role "cat", {}
+ end
+
+ # rubocop:disable Layout/TrailingWhitespace
+ it "shows a cookbook" do
+ knife("role show cons").should_succeed <<~EOM
+ chef_type: role
+ default_attributes:
+ description:
+ env_run_lists:
+ json_class: Chef::Role
+ name: cons
+ override_attributes:
+ run_list:
+ EOM
+ end
+ # rubocop:enable Layout/TrailingWhitespace
+
+ end
+end
diff --git a/knife/spec/integration/search_node_spec.rb b/knife/spec/integration/search_node_spec.rb
new file mode 100644
index 0000000000..9e7935b83c
--- /dev/null
+++ b/knife/spec/integration/search_node_spec.rb
@@ -0,0 +1,40 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+
+describe "knife node show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has a node with a run_list" do
+ before do
+ node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
+ end
+
+ it "finds the node" do
+ knife("search node name:cons").should_succeed(/Node Name:\s*cons/, stderr: "1 items found\n\n")
+ end
+
+ it "does not find a node" do
+ knife("search node name:snoc").should_fail("", stderr: "0 items found\n\n", exit_code: 1)
+ end
+ end
+end
diff --git a/spec/integration/knife/serve_spec.rb b/knife/spec/integration/serve_spec.rb
index fa9b1dc47c..fa9b1dc47c 100644
--- a/spec/integration/knife/serve_spec.rb
+++ b/knife/spec/integration/serve_spec.rb
diff --git a/knife/spec/integration/show_spec.rb b/knife/spec/integration/show_spec.rb
new file mode 100644
index 0000000000..6913494916
--- /dev/null
+++ b/knife/spec/integration/show_spec.rb
@@ -0,0 +1,197 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "support/shared/context/config"
+require "chef/knife/show"
+
+describe "knife show", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ include_context "default config options"
+
+ when_the_chef_server "has one of each thing" do
+ before do
+ client "x", "{}"
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => "{}" }
+ environment "x", "{}"
+ node "x", "{}"
+ role "x", "{}"
+ user "x", "{}"
+ end
+
+ when_the_repository "also has one of each thing" do
+ before do
+ file "clients/x.json", { "foo" => "bar" }
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", { "foo" => "bar" }
+ file "environments/_default.json", { "foo" => "bar" }
+ file "environments/x.json", { "foo" => "bar" }
+ file "nodes/x.json", { "foo" => "bar" }
+ file "roles/x.json", { "foo" => "bar" }
+ file "users/x.json", { "foo" => "bar" }
+ end
+
+ it "knife show /cookbooks/x/metadata.rb shows the remote version" do
+ knife("show /cookbooks/x/metadata.rb").should_succeed <<~EOM
+ /cookbooks/x/metadata.rb:
+ name "x"; version "1.0.0"
+ EOM
+ end
+ it "knife show --local /cookbooks/x/metadata.rb shows the local version" do
+ knife("show --local /cookbooks/x/metadata.rb").should_succeed <<~EOM
+ /cookbooks/x/metadata.rb:
+ name "x"; version "1.0.0"
+ EOM
+ end
+ it "knife show /data_bags/x/y.json shows the remote version" do
+ knife("show /data_bags/x/y.json").should_succeed <<~EOM
+ /data_bags/x/y.json:
+ {
+ "id": "y"
+ }
+ EOM
+ end
+ it "knife show --local /data_bags/x/y.json shows the local version" do
+ knife("show --local /data_bags/x/y.json").should_succeed <<~EOM
+ /data_bags/x/y.json:
+ {
+ "foo": "bar"
+ }
+ EOM
+ end
+ it "knife show /environments/x.json shows the remote version" do
+ knife("show /environments/x.json").should_succeed <<~EOM
+ /environments/x.json:
+ {
+ "name": "x",
+ "description": "",
+ "cookbook_versions": {
+
+ },
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "json_class": "Chef::Environment",
+ "chef_type": "environment"
+ }
+ EOM
+ end
+ it "knife show --local /environments/x.json shows the local version" do
+ knife("show --local /environments/x.json").should_succeed <<~EOM
+ /environments/x.json:
+ {
+ "foo": "bar"
+ }
+ EOM
+ end
+ it "knife show /roles/x.json shows the remote version" do
+ knife("show /roles/x.json").should_succeed <<~EOM
+ /roles/x.json:
+ {
+ "name": "x",
+ "description": "",
+ "json_class": "Chef::Role",
+ "chef_type": "role",
+ "default_attributes": {
+
+ },
+ "override_attributes": {
+
+ },
+ "run_list": [
+
+ ],
+ "env_run_lists": {
+
+ }
+ }
+ EOM
+ end
+ it "knife show --local /roles/x.json shows the local version" do
+ knife("show --local /roles/x.json").should_succeed <<~EOM
+ /roles/x.json:
+ {
+ "foo": "bar"
+ }
+ EOM
+ end
+ # show directory
+ it "knife show /data_bags/x fails" do
+ knife("show /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n"
+ end
+ it "knife show --local /data_bags/x fails" do
+ knife("show --local /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n"
+ end
+ # show nonexistent file
+ it "knife show /environments/nonexistent.json fails" do
+ knife("show /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+ end
+ it "knife show --local /environments/nonexistent.json fails" do
+ knife("show --local /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+ end
+ end
+ end
+
+ when_the_chef_server "has a hash with multiple keys" do
+ before do
+ environment "x", {
+ "default_attributes" => { "foo" => "bar" },
+ "cookbook_versions" => { "blah" => "= 1.0.0" },
+ "override_attributes" => { "x" => "y" },
+ "description" => "woo",
+ "name" => "x",
+ }
+ end
+ it "knife show shows the attributes in a predetermined order" do
+ knife("show /environments/x.json").should_succeed <<~EOM
+ /environments/x.json:
+ {
+ "name": "x",
+ "description": "woo",
+ "cookbook_versions": {
+ "blah": "= 1.0.0"
+ },
+ "default_attributes": {
+ "foo": "bar"
+ },
+ "override_attributes": {
+ "x": "y"
+ },
+ "json_class": "Chef::Environment",
+ "chef_type": "environment"
+ }
+ EOM
+ end
+ end
+
+ when_the_repository "has an environment with bad JSON" do
+ before { file "environments/x.json", "{" }
+ it "knife show succeeds" do
+ knife("show --local /environments/x.json").should_succeed <<~EOM
+ /environments/x.json:
+ {
+ EOM
+ end
+ end
+end
diff --git a/knife/spec/integration/upload_spec.rb b/knife/spec/integration/upload_spec.rb
new file mode 100644
index 0000000000..e4bb44ad7e
--- /dev/null
+++ b/knife/spec/integration/upload_spec.rb
@@ -0,0 +1,1617 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "support/shared/integration/integration_helper"
+require "chef/knife/upload"
+require "chef/knife/diff"
+require "chef/knife/raw"
+require "chef/json_compat"
+
+describe "knife upload", :workstation do
+ include IntegrationSupport
+ include KnifeSupport
+
+ context "without versioned cookbooks" do
+
+ when_the_chef_server "has one of each thing" do
+
+ before do
+ client "x", {}
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ node "x", {}
+ role "x", {}
+ user "x", {}
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife upload does nothing" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients/chef-validator.json
+ D\t/clients/chef-webui.json
+ D\t/clients/x.json
+ D\t/cookbooks/x
+ D\t/data_bags/x
+ D\t/environments/_default.json
+ D\t/environments/x.json
+ D\t/nodes/x.json
+ D\t/roles/x.json
+ D\t/users/admin.json
+ D\t/users/x.json
+ EOM
+ end
+
+ it "knife upload --purge deletes everything" do
+ knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
+ Deleted extra entry /clients/chef-validator.json (purge is on)
+ Deleted extra entry /clients/chef-webui.json (purge is on)
+ Deleted extra entry /clients/x.json (purge is on)
+ Deleted extra entry /cookbooks/x (purge is on)
+ Deleted extra entry /data_bags/x (purge is on)
+ Deleted extra entry /environments/x.json (purge is on)
+ Deleted extra entry /nodes/x.json (purge is on)
+ Deleted extra entry /roles/x.json (purge is on)
+ Deleted extra entry /users/admin.json (purge is on)
+ Deleted extra entry /users/x.json (purge is on)
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/environments/_default.json
+ EOM
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife upload makes no changes" do
+ knife("upload /cookbooks/x").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife upload --purge makes no changes" do
+ knife("upload --purge /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", { "description" => "blarghle" }
+ end
+
+ it "knife upload changes the role" do
+ knife("upload /").should_succeed "Updated /roles/x.json\n"
+ knife("diff --name-status /").should_succeed ""
+ end
+ it "knife upload --no-diff does not change the role" do
+ knife("upload --no-diff /").should_succeed ""
+ knife("diff --name-status /").should_succeed "M\t/roles/x.json\n"
+ end
+ end
+
+ context "except the role file is textually different, but not ACTUALLY different" do
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "chef_type": "role",
+ "default_attributes": {
+ },
+ "env_run_lists": {
+ },
+ "json_class": "Chef::Role",
+ "name": "x",
+ "description": "",
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife upload / does not change anything" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "the role is in ruby" do
+ before do
+ file "roles/x.rb", <<~EOM
+ name "x"
+ description "blargle"
+ EOM
+ end
+
+ it "knife upload changes the role" do
+ knife("upload /").should_succeed "Updated /roles/x.json\n"
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife upload --no-diff does not change the role" do
+ knife("upload --no-diff /").should_succeed ""
+ knife("diff --name-status /").should_succeed "M\t/roles/x.rb\n"
+ end
+ end
+
+ context "when cookbook metadata has a self-dependency" do
+ before do
+ file "cookbooks/x/metadata.rb", "name 'x'; version '1.0.0'; depends 'x'"
+ end
+
+ it "fails with RuntimeError" do
+ expect { knife("upload /cookbooks") }.to raise_error RuntimeError, /Cookbook depends on itself/
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x/blah.rb", ""
+ file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife upload adds the new files" do
+ knife("upload /").should_succeed <<~EOM
+ Created /clients/y.json
+ Updated /cookbooks/x
+ Created /cookbooks/y
+ Created /data_bags/x/z.json
+ Created /data_bags/y
+ Created /data_bags/y/zz.json
+ Created /environments/y.json
+ Created /nodes/y.json
+ Created /roles/y.json
+ Created /users/y.json
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ D\t/cookbooks/y/metadata.json
+ EOM
+ end
+
+ it "knife upload --no-diff adds the new files" do
+ knife("upload --no-diff /").should_succeed <<~EOM
+ Created /clients/y.json
+ Updated /cookbooks/x
+ Created /cookbooks/y
+ Created /data_bags/x/z.json
+ Created /data_bags/y
+ Created /data_bags/y/zz.json
+ Created /environments/y.json
+ Created /nodes/y.json
+ Created /roles/y.json
+ Created /users/y.json
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ D\t/cookbooks/y/metadata.json
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife upload does nothing" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+
+ it "knife upload --purge deletes nothing" do
+ knife("upload --purge /").should_fail <<~EOM
+ ERROR: /clients cannot be deleted.
+ ERROR: /cookbooks cannot be deleted.
+ ERROR: /data_bags cannot be deleted.
+ ERROR: /environments cannot be deleted.
+ ERROR: /nodes cannot be deleted.
+ ERROR: /roles cannot be deleted.
+ ERROR: /users cannot be deleted.
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+
+ context "when current directory is top level" do
+ before do
+ cwd "."
+ end
+
+ it "knife upload with no parameters reports an error" do
+ knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
+ end
+ end
+ end
+ end
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has a data bag item" do
+
+ before do
+ file "data_bags/x/y.json", { "foo" => "bar" }
+ end
+
+ it "knife upload of the data bag uploads only the values in the data bag item and no other" do
+ knife("upload /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ EOM
+ expect(Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false).keys.sort).to eq(%w{foo id})
+ end
+
+ it "knife upload /data_bags/x /data_bags/x/y.json uploads x once" do
+ knife("upload /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ end
+ end
+
+ when_the_repository "has a data bag item with keys chef_type and data_bag" do
+
+ before do
+ file "data_bags/x/y.json", { "chef_type" => "aaa", "data_bag" => "bbb" }
+ end
+
+ it "upload preserves chef_type and data_bag" do
+ knife("upload /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ result = Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false)
+ expect(result.keys.sort).to eq(%w{chef_type data_bag id})
+ expect(result["chef_type"]).to eq("aaa")
+ expect(result["data_bag"]).to eq("bbb")
+ end
+ end
+
+ # Test upload of an item when the other end doesn't even have the container
+ when_the_repository "has two data bag items" do
+ before do
+ file "data_bags/x/y.json", {}
+ file "data_bags/x/z.json", {}
+ end
+ it "knife upload of one data bag item itself succeeds" do
+ knife("upload /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ A\t/data_bags/x/z.json
+ EOM
+ end
+ end
+ end
+
+ when_the_chef_server "has three data bag items" do
+
+ before do
+ data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} }
+ end
+
+ when_the_repository "has a modified, unmodified, added and deleted data bag item" do
+ before do
+ file "data_bags/x/added.json", {}
+ file "data_bags/x/modified.json", { "foo" => "bar" }
+ file "data_bags/x/unmodified.json", {}
+ end
+
+ it "knife upload of the modified file succeeds" do
+ knife("upload /data_bags/x/modified.json").should_succeed <<~EOM
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the unmodified file does nothing" do
+ knife("upload /data_bags/x/unmodified.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the added file succeeds" do
+ knife("upload /data_bags/x/added.json").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ EOM
+ end
+ it "knife upload of the deleted file does nothing" do
+ knife("upload /data_bags/x/deleted.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload --purge of the deleted file deletes it" do
+ knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the entire data bag uploads everything" do
+ knife("upload /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife upload --purge of the entire data bag uploads everything" do
+ knife("upload --purge /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ context "when cwd is the /data_bags directory" do
+
+ before do
+ cwd "data_bags"
+ end
+
+ it "knife upload fails" do
+ knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
+ end
+
+ it "knife upload --purge . uploads everything" do
+ knife("upload --purge .").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ it "knife upload --purge * uploads everything" do
+ knife("upload --purge *").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ end
+ end
+ end
+
+ # Cookbook upload is a funny thing ... direct cookbook upload works, but
+ # upload of a file is designed not to work at present. Make sure that is the
+ # case.
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "z.rb" => "" }
+ end
+
+ when_the_repository "does not have metadata file" do
+ before do
+ file "cookbooks/x/y.rb", "hi"
+ end
+
+ it "raises MetadataNotFound exception" do
+ expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotFound)
+ end
+ end
+
+ when_the_repository "does not have valid metadata" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0")
+ end
+
+ it "raises exception for invalid metadata" do
+ expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotValid)
+ end
+ end
+
+ when_the_repository "has a modified, extra and missing file for the cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "#modified")
+ file "cookbooks/x/y.rb", "hi"
+ end
+
+ it "knife upload of any individual file fails" do
+ knife("upload /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n"
+ knife("upload /cookbooks/x/y.rb").should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n"
+ knife("upload --purge /cookbooks/x/z.rb").should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n"
+ end
+
+ # TODO this is a bit of an inconsistency: if we didn't specify --purge,
+ # technically we shouldn't have deleted missing files. But ... cookbooks
+ # are a special case.
+ it "knife upload of the cookbook itself succeeds" do
+ knife("upload /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+
+ it "knife upload --purge of the cookbook itself succeeds" do
+ knife("upload /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+ when_the_repository "has a missing file for the cookbook" do
+
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ end
+
+ it "knife upload of the cookbook succeeds" do
+ knife("upload /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+ when_the_repository "has an extra file for the cookbook" do
+
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/z.rb", ""
+ file "cookbooks/x/blah.rb", ""
+ end
+
+ it "knife upload of the cookbook succeeds" do
+ knife("upload /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+
+ when_the_repository "has a different file in the cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ end
+
+ it "knife upload --freeze freezes the cookbook" do
+ knife("upload --freeze /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ # Modify a file and attempt to upload
+ file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different'
+ knife("upload /cookbooks/x").should_fail "ERROR: /cookbooks failed to write: Cookbook x is frozen\n"
+ end
+ end
+ end
+
+ when_the_chef_server "has a frozen cookbook" do
+ before do
+ cookbook "frozencook", "1.0.0", {}, frozen: true
+ end
+
+ when_the_repository "has an update to said cookbook" do
+
+ before do
+ file "cookbooks/frozencook/metadata.rb", cb_metadata("frozencook", "1.0.0", "# This is different")
+ end
+
+ it "knife upload fails to upload the frozen cookbook" do
+ knife("upload /cookbooks/frozencook").should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n"
+ end
+ it "knife upload --force uploads the frozen cookbook" do
+ knife("upload --force /cookbooks/frozencook").should_succeed <<~EOM
+ Updated /cookbooks/frozencook
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/metadata.json", { name: "x", version: "1.0.0" }
+ file "cookbooks/x/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the local version" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/metadata.json
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ knife("upload --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/metadata.json
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the local version and generates metadata.json from metadata.rb and uploads it." do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ knife("upload --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the local version generates metadata.json and uploads it." do
+ knife("upload --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the local version and generates metadata.json before upload and uploads it." do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ knife("upload --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x/metadata.rb
+ D\t/cookbooks/x/onlyin1.0.1.rb
+ A\t/cookbooks/x/onlyin1.0.0.rb
+ EOM
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the new version" do
+ knife("upload --purge /cookbooks/x").should_succeed <<~EOM
+ Updated /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before do
+ environment "x", {}
+ end
+
+ when_the_repository "has an environment with bad JSON" do
+ before do
+ file "environments/x.json", "{"
+ end
+
+ it "knife upload tries and fails" do
+ error1 = <<~EOH
+ WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
+ {
+ (right here) ------^
+
+ ERROR: /environments/x.json failed to write: Parse error reading JSON: parse error: premature EOF
+ {
+ (right here) ------^
+ EOH
+
+ warn = <<~EOH
+ WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
+ {
+ (right here) ------^
+
+ EOH
+ knife("upload /environments/x.json").should_fail(error1)
+ knife("diff --name-status /environments/x.json").should_succeed("M\t/environments/x.json\n", stderr: warn)
+ end
+ end
+
+ when_the_repository "has the same environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+ it "knife upload fails" do
+ knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
+ knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n"
+ end
+ end
+
+ when_the_repository "has the same environment with no name in the file" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "is empty" do
+
+ when_the_repository "has an environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+ it "knife upload fails" do
+ knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
+ knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n"
+ end
+ end
+
+ when_the_repository "has an environment with no name in the file" do
+
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+
+ when_the_repository "has a data bag with no id in the file" do
+ before do
+ file "data_bags/bag/x.json", { "foo" => "bar" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
+ knife("diff --name-status /data_bags/bag/x.json").should_succeed ""
+ end
+ end
+ end
+ when_the_chef_server "is empty" do
+ when_the_repository "has a cookbook with an invalid chef_version constraint in it" do
+ before do
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'")
+ end
+ it "knife upload succeeds" do
+ knife("upload /cookbooks/x").should_succeed <<~EOM
+ Created /cookbooks/x
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x/metadata.json
+ EOM
+ end
+ end
+ end
+ end # without versioned cookbooks
+
+ context "with versioned cookbooks" do
+ before { Chef::Config[:versioned_cookbooks] = true }
+
+ when_the_chef_server "has one of each thing" do
+
+ before do
+ client "x", {}
+ cookbook "x", "1.0.0"
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ node "x", {}
+ role "x", {}
+ user "x", {}
+ end
+
+ when_the_repository "has only top-level directories" do
+ before do
+ directory "clients"
+ directory "cookbooks"
+ directory "data_bags"
+ directory "environments"
+ directory "nodes"
+ directory "roles"
+ directory "users"
+ end
+
+ it "knife upload does nothing" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients/chef-validator.json
+ D\t/clients/chef-webui.json
+ D\t/clients/x.json
+ D\t/cookbooks/x-1.0.0
+ D\t/data_bags/x
+ D\t/environments/_default.json
+ D\t/environments/x.json
+ D\t/nodes/x.json
+ D\t/roles/x.json
+ D\t/users/admin.json
+ D\t/users/x.json
+ EOM
+ end
+
+ it "knife upload --purge deletes everything" do
+ knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
+ Deleted extra entry /clients/chef-validator.json (purge is on)
+ Deleted extra entry /clients/chef-webui.json (purge is on)
+ Deleted extra entry /clients/x.json (purge is on)
+ Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+ Deleted extra entry /data_bags/x (purge is on)
+ Deleted extra entry /environments/x.json (purge is on)
+ Deleted extra entry /nodes/x.json (purge is on)
+ Deleted extra entry /roles/x.json (purge is on)
+ Deleted extra entry /users/admin.json (purge is on)
+ Deleted extra entry /users/x.json (purge is on)
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/environments/_default.json
+ EOM
+ end
+ end
+
+ when_the_repository "has an identical copy of each thing" do
+ before do
+ file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/_default.json", { "description" => "The default Chef environment" }
+ file "environments/x.json", {}
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "roles/x.json", {}
+ file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
+ file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife upload makes no changes" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ it "knife upload --purge makes no changes" do
+ knife("upload --purge /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+
+ context "except the role file" do
+ before do
+ file "roles/x.json", { "description" => "blarghle" }
+ end
+
+ it "knife upload changes the role" do
+ knife("upload /").should_succeed "Updated /roles/x.json\n"
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "except the role file is textually different, but not ACTUALLY different" do
+
+ before do
+ file "roles/x.json", <<~EOM
+ {
+ "chef_type": "role",
+ "default_attributes": {
+ },
+ "env_run_lists": {
+ },
+ "json_class": "Chef::Role",
+ "name": "x",
+ "description": "",
+ "override_attributes": {
+ },
+ "run_list": [
+
+ ]
+ }
+ EOM
+ end
+
+ it "knife upload / does not change anything" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+
+ context "as well as one extra copy of each thing" do
+ before do
+ file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "cookbooks/x-1.0.0/blah.rb", ""
+ file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0")
+ file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0")
+ file "data_bags/x/z.json", {}
+ file "data_bags/y/zz.json", {}
+ file "environments/y.json", {}
+ file "nodes/y.json", {}
+ file "roles/y.json", {}
+ file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ end
+
+ it "knife upload adds the new files" do
+ knife("upload /").should_succeed <<~EOM
+ Created /clients/y.json
+ Updated /cookbooks/x-1.0.0
+ Created /cookbooks/x-2.0.0
+ Created /cookbooks/y-1.0.0
+ Created /data_bags/x/z.json
+ Created /data_bags/y
+ Created /data_bags/y/zz.json
+ Created /environments/y.json
+ Created /nodes/y.json
+ Created /roles/y.json
+ Created /users/y.json
+ EOM
+ knife("diff --name-status /").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "is empty" do
+ it "knife upload does nothing" do
+ knife("upload /").should_succeed ""
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+
+ it "knife upload --purge deletes nothing" do
+ knife("upload --purge /").should_fail <<~EOM
+ ERROR: /clients cannot be deleted.
+ ERROR: /cookbooks cannot be deleted.
+ ERROR: /data_bags cannot be deleted.
+ ERROR: /environments cannot be deleted.
+ ERROR: /nodes cannot be deleted.
+ ERROR: /roles cannot be deleted.
+ ERROR: /users cannot be deleted.
+ EOM
+ knife("diff --name-status /").should_succeed <<~EOM
+ D\t/clients
+ D\t/cookbooks
+ D\t/data_bags
+ D\t/environments
+ D\t/nodes
+ D\t/roles
+ D\t/users
+ EOM
+ end
+
+ context "when current directory is top level" do
+ before do
+ cwd "."
+ end
+ it "knife upload with no parameters reports an error" do
+ knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
+ end
+ end
+ end
+ end
+
+ # Test upload of an item when the other end doesn't even have the container
+ when_the_chef_server "is empty" do
+ when_the_repository "has two data bag items" do
+ before do
+ file "data_bags/x/y.json", {}
+ file "data_bags/x/z.json", {}
+ end
+
+ it "knife upload of one data bag item itself succeeds" do
+ knife("upload /data_bags/x/y.json").should_succeed <<~EOM
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ A\t/data_bags/x/z.json
+ EOM
+ end
+ end
+ end
+
+ when_the_chef_server "has three data bag items" do
+ before do
+ data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} }
+ end
+ when_the_repository "has a modified, unmodified, added and deleted data bag item" do
+ before do
+ file "data_bags/x/added.json", {}
+ file "data_bags/x/modified.json", { "foo" => "bar" }
+ file "data_bags/x/unmodified.json", {}
+ end
+
+ it "knife upload of the modified file succeeds" do
+ knife("upload /data_bags/x/modified.json").should_succeed <<~EOM
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the unmodified file does nothing" do
+ knife("upload /data_bags/x/unmodified.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the added file succeeds" do
+ knife("upload /data_bags/x/added.json").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ EOM
+ end
+ it "knife upload of the deleted file does nothing" do
+ knife("upload /data_bags/x/deleted.json").should_succeed ""
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload --purge of the deleted file deletes it" do
+ knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ M\t/data_bags/x/modified.json
+ A\t/data_bags/x/added.json
+ EOM
+ end
+ it "knife upload of the entire data bag uploads everything" do
+ knife("upload /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ EOM
+ knife("diff --name-status /data_bags").should_succeed <<~EOM
+ D\t/data_bags/x/deleted.json
+ EOM
+ end
+ it "knife upload --purge of the entire data bag uploads everything" do
+ knife("upload --purge /data_bags/x").should_succeed <<~EOM
+ Created /data_bags/x/added.json
+ Updated /data_bags/x/modified.json
+ Deleted extra entry /data_bags/x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ context "when cwd is the /data_bags directory" do
+ before do
+ cwd "data_bags"
+ end
+ it "knife upload fails" do
+ knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
+ end
+ it "knife upload --purge . uploads everything" do
+ knife("upload --purge .").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ it "knife upload --purge * uploads everything" do
+ knife("upload --purge *").should_succeed <<~EOM
+ Created x/added.json
+ Updated x/modified.json
+ Deleted extra entry x/deleted.json (purge is on)
+ EOM
+ knife("diff --name-status /data_bags").should_succeed ""
+ end
+ end
+ end
+ end
+
+ # Cookbook upload is a funny thing ... direct cookbook upload works, but
+ # upload of a file is designed not to work at present. Make sure that is the
+ # case.
+ when_the_chef_server "has a cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "z.rb" => "" }
+ end
+
+ when_the_repository "has a modified, extra and missing file for the cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "#modified")
+ file "cookbooks/x-1.0.0/y.rb", "hi"
+ end
+
+ it "knife upload of any individual file fails" do
+ knife("upload /cookbooks/x-1.0.0/metadata.rb").should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n"
+ knife("upload /cookbooks/x-1.0.0/y.rb").should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n"
+ knife("upload --purge /cookbooks/x-1.0.0/z.rb").should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n"
+ end
+
+ # TODO this is a bit of an inconsistency: if we didn't specify --purge,
+ # technically we shouldn't have deleted missing files. But ... cookbooks
+ # are a special case.
+ it "knife upload of the cookbook itself succeeds" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+
+ it "knife upload --purge of the cookbook itself succeeds" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_repository "has a missing file for the cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ end
+
+ it "knife upload of the cookbook succeeds" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_repository "has an extra file for the cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x-1.0.0/z.rb", ""
+ file "cookbooks/x-1.0.0/blah.rb", ""
+ end
+
+ it "knife upload of the cookbook succeeds" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "has a cookbook" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text"
+ end
+
+ when_the_chef_server "has a later version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks uploads the local version" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ M\t/cookbooks/x-1.0.0/onlyin1.0.0.rb
+ D\t/cookbooks/x-1.0.1
+ EOM
+ knife("upload --purge /cookbooks").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook" do
+ before do
+ cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+ it "knife upload /cookbooks uploads the local version" do
+ knife("upload --purge /cookbooks").should_succeed <<~EOM
+ Updated /cookbooks/x-1.0.0
+ Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has a later version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the local version" do
+ knife("diff --name-status /cookbooks").should_succeed <<~EOM
+ D\t/cookbooks/x-1.0.1
+ A\t/cookbooks/x-1.0.0
+ EOM
+ knife("upload --purge /cookbooks").should_succeed <<~EOM
+ Created /cookbooks/x-1.0.0
+ Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+
+ when_the_chef_server "has an earlier version for the cookbook, and no current version" do
+ before do
+ cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
+ end
+
+ it "knife upload /cookbooks/x uploads the new version" do
+ knife("upload --purge /cookbooks").should_succeed <<~EOM
+ Created /cookbooks/x-1.0.0
+ Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "has an environment" do
+ before do
+ environment "x", {}
+ end
+
+ when_the_repository "has the same environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+ it "knife upload fails" do
+ knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
+ knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n"
+ end
+ end
+
+ when_the_repository "has the same environment with no name in the file" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "is empty" do
+
+ when_the_repository "has an environment with the wrong name in the file" do
+ before do
+ file "environments/x.json", { "name" => "y" }
+ end
+ it "knife upload fails" do
+ knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
+ knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n"
+ end
+ end
+
+ when_the_repository "has an environment with no name in the file" do
+ before do
+ file "environments/x.json", { "description" => "hi" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n"
+ knife("diff --name-status /environments/x.json").should_succeed ""
+ end
+ end
+
+ when_the_repository "has a data bag with no id in the file" do
+ before do
+ file "data_bags/bag/x.json", { "foo" => "bar" }
+ end
+ it "knife upload succeeds" do
+ knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
+ knife("diff --name-status /data_bags/bag/x.json").should_succeed ""
+ end
+ end
+ end
+
+ when_the_chef_server "is empty" do
+ when_the_repository "has a cookbook with an invalid chef_version constraint in it" do
+ before do
+ file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'")
+ end
+ it "knife upload succeeds" do
+ knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
+ Created /cookbooks/x-1.0.0
+ EOM
+ knife("diff --name-status /cookbooks").should_succeed ""
+ end
+ end
+ end
+ end # with versioned cookbooks
+
+ when_the_chef_server "has a user" do
+ before do
+ user "x", {}
+ end
+
+ when_the_repository "has the same user with json_class in it" do
+ before do
+ file "users/x.json", { "admin" => true, "json_class" => "Chef::WebUIUser" }
+ end
+ it "knife upload /users/x.json succeeds" do
+ knife("upload /users/x.json").should_succeed "Updated /users/x.json\n"
+ end
+ end
+ end
+
+ when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
+ before do
+ user "foo", {}
+ user "bar", {}
+ user "foobar", {}
+ organization "foo", { "full_name" => "Something" }
+ end
+
+ before :each do
+ Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
+ end
+
+ context "and has nothing but a single group named blah" do
+ group "blah", {}
+
+ when_the_repository "has at least one of each thing" do
+
+ before do
+ # TODO We have to upload acls for an existing group due to a lack of
+ # dependency detection during upload. Fix that!
+ file "acls/groups/blah.json", {}
+ file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
+ file "containers/x.json", {}
+ file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
+ file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0")
+ file "data_bags/x/y.json", {}
+ file "environments/x.json", {}
+ file "groups/x.json", {}
+ file "invitations.json", [ "foo" ]
+ file "members.json", [ "bar" ]
+ file "org.json", { "full_name" => "wootles" }
+ file "nodes/x.json", { "normal" => { "tags" => [] } }
+ file "policies/x-1.0.0.json", {}
+ file "policies/blah-1.0.0.json", {}
+ file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } }
+ file "roles/x.json", {}
+ end
+
+ it "knife upload / uploads everything" do
+ knife("upload /").should_succeed <<~EOM
+ Updated /acls/groups/blah.json
+ Created /clients/x.json
+ Created /containers/x.json
+ Created /cookbook_artifacts/x-1x1
+ Created /cookbooks/x
+ Created /data_bags/x
+ Created /data_bags/x/y.json
+ Created /environments/x.json
+ Created /groups/x.json
+ Updated /invitations.json
+ Updated /members.json
+ Created /nodes/x.json
+ Updated /org.json
+ Created /policies/blah-1.0.0.json
+ Created /policies/x-1.0.0.json
+ Created /policy_groups/x.json
+ Created /roles/x.json
+ EOM
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq([ "foo" ])
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
+ knife("diff --name-status --diff-filter=AMT /").should_succeed ""
+ end
+
+ context "When the chef server has an identical copy of each thing" do
+ before do
+ file "invitations.json", [ "foo" ]
+ file "members.json", [ "bar" ]
+ file "org.json", { "full_name" => "Something" }
+
+ # acl_for %w(organizations foo groups blah)
+ client "x", {}
+ cookbook "x", "1.0.0"
+ cookbook_artifact "x", "1x1", "metadata.rb" => cb_metadata("x", "1.0.0")
+ container "x", {}
+ data_bag "x", { "y" => {} }
+ environment "x", {}
+ group "x", {}
+ org_invite "foo"
+ org_member "bar"
+ node "x", {}
+ policy "x", "1.0.0", {}
+ policy "blah", "1.0.0", {}
+ policy_group "x", {
+ "policies" => {
+ "x" => { "revision_id" => "1.0.0" },
+ "blah" => { "revision_id" => "1.0.0" },
+ },
+ }
+ role "x", {}
+ end
+
+ it "knife upload makes no changes" do
+ knife("upload /").should_succeed <<~EOM
+ Updated /acls/groups/blah.json
+ EOM
+ end
+ end
+
+ context "When the chef server has a slightly different copy of the policy revision" do
+ before do
+ policy "x", "1.0.0", { "run_list" => [ "blah" ] }
+ end
+
+ it "should fail because policies are not updateable" do
+ knife("upload /policies/x-1.0.0.json").should_fail <<~EOM
+ ERROR: /policies/x-1.0.0.json cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes.
+ EOM
+ end
+ end
+
+ context "When the chef server has a slightly different copy of the cookbook artifact" do
+ before do
+ cookbook_artifact "x", "1x1", { "recipes" => { "default.rb" => "" } }
+ end
+
+ it "should fail because cookbook_artifacts cannot be updated" do
+ knife("upload /cookbook_artifacts/x-1x1").should_fail <<~EOM
+ ERROR: /cookbook_artifacts/x-1x1 cannot be updated: cookbook artifacts are immutable once uploaded.
+ EOM
+ end
+ end
+
+ context "When the chef server has a slightly different copy of each thing (except policy revisions)" do
+ before do
+ # acl_for %w(organizations foo groups blah)
+ client "x", { "validator" => true }
+ container "x", {}
+ cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "" } }
+ cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") }
+ data_bag "x", { "y" => { "a" => "b" } }
+ environment "x", { "description" => "foo" }
+ group "x", { "groups" => [ "admin" ] }
+ node "x", { "run_list" => [ "blah" ] }
+ policy "x", "1.0.0", {}
+ policy "x", "1.0.1", {}
+ policy "y", "1.0.0", {}
+ policy_group "x", {
+ "policies" => {
+ "x" => { "revision_id" => "1.0.1" },
+ "y" => { "revision_id" => "1.0.0" },
+ },
+ }
+ role "x", { "run_list" => [ "blah" ] }
+ end
+
+ it "knife upload updates everything" do
+ knife("upload /").should_succeed <<~EOM
+ Updated /acls/groups/blah.json
+ Updated /clients/x.json
+ Updated /cookbooks/x
+ Updated /data_bags/x/y.json
+ Updated /environments/x.json
+ Updated /groups/x.json
+ Updated /invitations.json
+ Updated /members.json
+ Updated /nodes/x.json
+ Updated /org.json
+ Created /policies/blah-1.0.0.json
+ Updated /policy_groups/x.json
+ Updated /roles/x.json
+ EOM
+ knife("diff --name-status --diff-filter=AMT /").should_succeed ""
+ end
+ end
+ end
+
+ when_the_repository "has an org.json that does not change full_name" do
+ before do
+ file "org.json", { "full_name" => "Something" }
+ end
+
+ it "knife upload / emits a warning for bar and adds foo and foobar" do
+ knife("upload /").should_succeed ""
+ expect(api.get("/")["full_name"]).to eq("Something")
+ end
+ end
+
+ when_the_repository "has an org.json that changes full_name" do
+ before do
+ file "org.json", { "full_name" => "Something Else" }
+ end
+
+ it "knife upload / emits a warning for bar and adds foo and foobar" do
+ knife("upload /").should_succeed "Updated /org.json\n"
+ expect(api.get("/")["full_name"]).to eq("Something Else")
+ end
+ end
+
+ context "and has invited foo and bar is already a member" do
+ org_invite "foo"
+ org_member "bar"
+
+ when_the_repository "wants to invite foo, bar and foobar" do
+ before do
+ file "invitations.json", %w{foo bar foobar}
+ end
+
+ it "knife upload / emits a warning for bar and invites foobar" do
+ knife("upload /").should_succeed "Updated /invitations.json\n", stderr: "WARN: Could not invite bar to organization foo: User bar is already in organization foo\n"
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{foo foobar})
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
+ end
+ end
+
+ when_the_repository "wants to make foo, bar and foobar members" do
+ before do
+ file "members.json", %w{foo bar foobar}
+ end
+
+ it "knife upload / emits a warning for bar and adds foo and foobar" do
+ knife("upload /").should_succeed "Updated /members.json\n"
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ])
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo foobar})
+ end
+ end
+
+ when_the_repository "wants to invite foo and have bar as a member" do
+ before do
+ file "invitations.json", [ "foo" ]
+ file "members.json", [ "bar" ]
+ end
+
+ it "knife upload / does nothing" do
+ knife("upload /").should_succeed ""
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq([ "foo" ])
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
+ end
+ end
+ end
+
+ context "and has invited bar and foo" do
+ org_invite "bar", "foo"
+
+ when_the_repository "wants to invite foo and bar (different order)" do
+ before do
+ file "invitations.json", %w{foo bar}
+ end
+
+ it "knife upload / does nothing" do
+ knife("upload /").should_succeed ""
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{bar foo})
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ ])
+ end
+ end
+ end
+
+ context "and has already added bar and foo as members of the org" do
+ org_member "bar", "foo"
+
+ when_the_repository "wants to add foo and bar (different order)" do
+ before do
+ file "members.json", %w{foo bar}
+ end
+
+ it "knife upload / does nothing" do
+ knife("upload /").should_succeed ""
+ expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ])
+ expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo})
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/knife_spec_helper.rb b/knife/spec/knife_spec_helper.rb
new file mode 100644
index 0000000000..7395d2759b
--- /dev/null
+++ b/knife/spec/knife_spec_helper.rb
@@ -0,0 +1,241 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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.
+
+# If you need to add anything in here, don't.
+# Add it to one of the files in spec/support
+
+# Abuse ruby's constant lookup to avoid undefined constant errors
+
+$LOAD_PATH.unshift File.expand_path("..", __dir__)
+$LOAD_PATH.unshift File.expand_path("../../chef-config/lib", __dir__)
+$LOAD_PATH.unshift File.expand_path("../../chef-utils/lib", __dir__)
+
+require "rubygems"
+require "rspec/mocks"
+require "rexml/document"
+require "webmock/rspec"
+
+require "chef/knife"
+
+# cwd is knife/
+Dir["lib/chef/knife/**/*.rb"]
+ .map { |f| f.gsub("lib/", "") }
+ .map { |f| f.gsub(/\.rb$/, "") }
+ .each { |f| require f }
+
+require "chef/resource_resolver"
+require "chef/provider_resolver"
+
+require "chef/mixins"
+require "chef/dsl"
+
+require "chef/shell"
+require "chef/util/file_edit"
+
+require "chef/config"
+
+require "chef/chef_fs/file_system_cache"
+
+require "chef/api_client_v1"
+
+require "chef/mixin/versioned_api"
+require "chef/server_api_versions"
+
+if ENV["CHEF_FIPS"] == "1"
+ Chef::Config.init_openssl
+end
+
+# If you want to load anything into the testing environment
+# without versioning it, add it to spec/support/local_gems.rb
+require "spec/support/local_gems" if File.exist?(File.join(File.dirname(__FILE__), "support", "local_gems.rb"))
+
+# Explicitly require spec helpers that need to load first
+require "spec/support/platform_helpers"
+require "spec/support/shared/unit/mock_shellout"
+require "spec/support/recipe_dsl_helper"
+require "spec/support/key_helpers"
+require "spec/support/shared/unit/knife_shared"
+require "spec/support/shared/functional/knife"
+require "spec/support/shared/integration/knife_support"
+require "spec/support/shared/matchers/exit_with_code"
+require "spec/support/shared/matchers/match_environment_variable"
+
+# Autoloads support files
+# Excludes support/platforms by default
+# Do not change the gsub.
+Dir["spec/support/**/*.rb"]
+ .reject { |f| f =~ %r{^spec/support/platforms} }
+ .reject { |f| f =~ %r{^spec/support/pedant} }
+ .map { |f| f.gsub(/.rb$/, "") }
+ .map { |f| f.gsub(%r{spec/}, "") }
+ .each { |f| require f }
+
+OHAI_SYSTEM = Ohai::System.new
+OHAI_SYSTEM.all_plugins(["platform", "hostname", "languages/powershell", "uptime"])
+
+test_node = Chef::Node.new
+test_node.automatic["os"] = (OHAI_SYSTEM["os"] || "unknown_os").dup.freeze
+test_node.automatic["platform_family"] = (OHAI_SYSTEM["platform_family"] || "unknown_platform_family").dup.freeze
+test_node.automatic["platform"] = (OHAI_SYSTEM["platform"] || "unknown_platform").dup.freeze
+test_node.automatic["platform_version"] = (OHAI_SYSTEM["platform_version"] || "unknown_platform_version").dup.freeze
+TEST_NODE = test_node.freeze
+TEST_OS = TEST_NODE["os"]
+TEST_PLATFORM = TEST_NODE["platform"]
+TEST_PLATFORM_VERSION = TEST_NODE["platform_version"]
+TEST_PLATFORM_FAMILY = TEST_NODE["platform_family"]
+
+provider_priority_map ||= nil
+resource_priority_map ||= nil
+provider_handler_map ||= nil
+resource_handler_map ||= nil
+
+class UnexpectedSystemExit < RuntimeError
+ def self.from(system_exit)
+ new(system_exit.message).tap { |e| e.set_backtrace(system_exit.backtrace) }
+ end
+end
+
+RSpec.configure do |config|
+ config.include(RSpec::Matchers)
+ config.include(MockShellout::RSpec)
+ config.filter_run focus: true
+ config.filter_run_excluding external: true
+ config.raise_on_warning = true
+
+ # Explicitly disable :should syntax
+ # And set max_formatted_output_length to nil to prevent RSpec from doing truncation.
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+ c.max_formatted_output_length = nil
+ end
+ config.mock_with :rspec do |c|
+ c.syntax = :expect
+ c.allow_message_expectations_on_nil = false
+ end
+
+ # TODO - which if any of these filters apply to knife tests?
+ #
+ # Only run these tests on platforms that are also chef workstations
+ config.filter_run_excluding :workstation if solaris? || aix?
+
+ # Tests that randomly fail, but may have value.
+ config.filter_run_excluding volatile: true
+ config.filter_run_excluding volatile_on_solaris: true if solaris?
+ config.filter_run_excluding volatile_from_verify: false
+
+ config.filter_run_excluding skip_buildkite: true if ENV["BUILDKITE"]
+
+ config.filter_run_excluding windows_only: true unless windows?
+ config.filter_run_excluding unix_only: true unless unix?
+
+ # check for particular binaries we need
+
+ running_platform_arch = `uname -m`.strip unless windows?
+
+ config.filter_run_excluding arch: lambda { |target_arch|
+ running_platform_arch != target_arch
+ }
+
+ config.run_all_when_everything_filtered = true
+
+ config.before(:each) do
+ # it'd be nice to run this with connections blocked or only to localhost, but we do make lots
+ # of real connections, so cannot. we reset it to allow connections every time to avoid
+ # tests setting connections to be disabled and that state leaking into other tests.
+ WebMock.allow_net_connect!
+ Chef.reset!
+ Chef::ChefFS::FileSystemCache.instance.reset!
+ Chef::Config.reset
+ Chef::Log.setup!
+ Chef::ServerAPIVersions.instance.reset!
+ Chef::Config[:log_level] = :fatal
+ Chef::Log.level(Chef::Config[:log_level])
+
+ # By default, treat deprecation warnings as errors in tests.
+ # and set environment variable so the setting persists in child processes
+ Chef::Config.treat_deprecation_warnings_as_errors(true)
+ ENV["CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS"] = "1"
+ end
+
+ # This bit of jankiness guards against specs which accidentally drop privs when running as
+ # root -- which are nearly impossible to debug and so we bail out very hard if this
+ # condition ever happens. If a spec stubs Process.[e]uid this can throw a false positive
+ # which the spec must work around by unmocking Process.[e]uid to and_call_original in its
+ # after block.
+ # Should not be a problem with knife which does not escalate local privs, but
+ # it seems wise to continue to guard against.
+ if Process.euid == 0 && Process.uid == 0
+ config.after(:each) do
+ if Process.uid != 0
+ RSpec.configure { |c| c.fail_fast = true }
+ raise "rspec was invoked as root, but the last test dropped real uid to #{Process.uid}"
+ end
+ if Process.euid != 0
+ RSpec.configure { |c| c.fail_fast = true }
+ raise "rspec was invoked as root, but the last test dropped effective uid to #{Process.euid}"
+ end
+ end
+ end
+
+ # raise if anyone commits any test to CI with :focus set on it
+ if ENV["CI"]
+ config.before(:example, :focus) do
+ raise "This example was committed with `:focus` and should not have been"
+ end
+ end
+
+ config.before(:suite) do
+ ARGV.clear
+ end
+
+ # Protect Rspec from accidental exit(0) causing rspec to terminate without error
+ config.around(:example) do |ex|
+ ex.run
+ rescue SystemExit => e
+ raise UnexpectedSystemExit.from(e)
+
+ end
+end
+
+require "webrick/utils"
+# Webrick uses a centralized/synchronized timeout manager. It works by
+# starting a thread to check for timeouts on an interval. The timeout
+# checker thread cannot be stopped or canceled in any easy way, and it
+# makes calls to Time.new, which fail when rspec is in the process of
+# creating a method stub for that method. Since our tests don't rely on
+# any timeout behavior enforced by webrick, disable the timeout manager
+# via a monkey patch.
+#
+# Hopefully this fails loudly if the webrick code should change. As of this
+# writing, the relevant code is in webrick/utils, which can be located on
+# your system with:
+#
+# $ gem which webrick/utils
+module WEBrick
+ module Utils
+ class TimeoutHandler
+ def initialize; end
+
+ def register(*args); end
+
+ def cancel(*args); end
+ end
+ end
+end
+
+# Enough stuff needs json serialization that I'm just adding it here for equality asserts
+require "chef/json_compat"
diff --git a/knife/spec/support/chef_helpers.rb b/knife/spec/support/chef_helpers.rb
new file mode 100644
index 0000000000..f11dee47b6
--- /dev/null
+++ b/knife/spec/support/chef_helpers.rb
@@ -0,0 +1,79 @@
+# Copyright:: Copyright (c) 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.
+#
+
+CHEF_SPEC_DATA = File.expand_path(__dir__ + "/../data/")
+CHEF_SPEC_ASSETS = File.expand_path(__dir__ + "/../functional/assets/")
+CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, "test-backup-path")
+
+def sha256_checksum(path)
+ OpenSSL::Digest.hexdigest("SHA256", File.read(path))
+end
+
+# extracted from Ruby < 2.5 to return a unique temp file name without creating it
+def make_tmpname(prefix_suffix, n = nil)
+ case prefix_suffix
+ when String
+ prefix = prefix_suffix
+ suffix = ""
+ when Array
+ prefix = prefix_suffix[0]
+ suffix = prefix_suffix[1]
+ else
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
+ end
+ t = Time.now.strftime("%Y%m%d")
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
+ path << "-#{n}" if n
+ path << suffix
+end
+
+# This is a helper to canonicalize paths that we're using in the file
+# tests.
+def canonicalize_path(path)
+ windows? ? path.tr("/", "\\") : path
+end
+
+# Makes a temp directory with a canonical path on any platform.
+# Only really needed to work around an issue on Windows where
+# Ruby's temp library generates paths with short names.
+def make_canonical_temp_directory
+ temp_directory = Dir.mktmpdir
+ if windows?
+ # On Windows, temporary file / directory path names may have shortened
+ # subdirectory names due to reliance on the TMP and TEMP environment variables
+ # in some Windows APIs and duplicated logic in Ruby's temp file implementation.
+ # To work around this in the unit test context, we obtain the long (canonical)
+ # path name via a Windows system call so that this path name can be used
+ # in expectations that assume the ability to canonically name paths in comparisons.
+ # Note that this was not an issue prior to Ruby 2.2 -- with Ruby 2.2,
+ # some Chef code started to use long file names, while Ruby's temp file implementation
+ # continued to return the shortened names -- this would cause these particular tests to
+ # fail if the username happened to be longer than 8 characters.
+ Chef::ReservedNames::Win32::File.get_long_path_name(temp_directory)
+ else
+ temp_directory
+ end
+end
+
+# Check if a cmd exists on the PATH
+def which(cmd)
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
+ paths.each do |path|
+ filename = File.join(path, cmd)
+ return filename if File.executable?(filename)
+ end
+ false
+end
diff --git a/knife/spec/support/key_helpers.rb b/knife/spec/support/key_helpers.rb
new file mode 100644
index 0000000000..2a27834050
--- /dev/null
+++ b/knife/spec/support/key_helpers.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+shared_examples_for "a knife key command" do
+ let(:stderr) { StringIO.new }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args (missing actor)" do
+ let(:params) { [] }
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.actor_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+
+ context "after apply_params! is called with valid args" do
+ before do
+ command.apply_params!(params)
+ end
+
+ it "properly defines the actor" do
+ expect(command.actor).to eq("charmander")
+ end
+ end # after apply_params! is called with valid args
+
+ context "when the command is run" do
+ before do
+ allow(command).to receive(:service_object).and_return(service_object)
+ allow(command).to receive(:name_args).and_return(["charmander"])
+ end
+
+ context "when the command is successful" do
+ before do
+ expect(service_object).to receive(:run)
+ end
+ end
+ end
+end # a knife key command
+
+shared_examples_for "a knife key command with a keyname as the second arg" do
+ let(:stderr) { StringIO.new }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args (missing keyname)" do
+ let(:params) { ["charmander"] }
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.keyname_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+end
diff --git a/knife/spec/support/platform_helpers.rb b/knife/spec/support/platform_helpers.rb
new file mode 100644
index 0000000000..a3fb95e069
--- /dev/null
+++ b/knife/spec/support/platform_helpers.rb
@@ -0,0 +1,251 @@
+require "fcntl"
+require "chef/mixin/shell_out"
+require "ohai/mixin/http_helper"
+require "ohai/mixin/gce_metadata"
+require "spec/support/chef_helpers"
+
+class ShellHelpers
+ extend Chef::Mixin::ShellOut
+end
+
+# magic stolen from bundler/spec/support/less_than_proc.rb
+class DependencyProc < Proc
+ attr_accessor :present
+
+ def self.with(present)
+ provided = Gem::Version.new(present.dup)
+ new do |required|
+ !Gem::Requirement.new(required).satisfied_by?(provided)
+ end.tap { |l| l.present = present }
+ end
+
+ def inspect
+ "\"#{present}\""
+ end
+end
+
+def ruby_64bit?
+ RbConfig::CONFIG["host_cpu"].include?("x86_64")
+end
+
+def ruby_32bit?
+ RbConfig::CONFIG["host_cpu"].include?("i686")
+end
+
+def windows?
+ # NOTE this deliberately does not use ChefUtils.windows? because otherwise it would
+ # pick up the node out of tests, while this tests the hosts running the specs.
+ !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
+end
+
+def ohai
+ # This is defined in spec_helper; it has the `platform` populated.
+ OHAI_SYSTEM
+end
+
+require "wmi-lite/wmi" if windows?
+
+def windows_domain_joined?
+ return false unless windows?
+
+ wmi = WmiLite::Wmi.new
+ computer_system = wmi.first_of("Win32_ComputerSystem")
+ computer_system["partofdomain"]
+end
+
+def windows_2012r2?
+ return false unless windows?
+
+ (win32_os_version && win32_os_version.start_with?("6.3"))
+end
+
+def windows_gte_10?
+ return false unless windows?
+
+ Gem::Requirement.new(">= 10").satisfied_by?(Gem::Version.new(win32_os_version))
+end
+
+def win32_os_version
+ @win32_os_version ||= begin
+ wmi = WmiLite::Wmi.new
+ host = wmi.first_of("Win32_OperatingSystem")
+ host["version"]
+ end
+end
+
+def windows_powershell_dsc?
+ return false unless windows?
+
+ supports_dsc = false
+ begin
+ wmi = WmiLite::Wmi.new("root/microsoft/windows/desiredstateconfiguration")
+ lcm = wmi.query("SELECT * FROM meta_class WHERE __this ISA 'MSFT_DSCLocalConfigurationManager'")
+ supports_dsc = !! lcm
+ rescue WmiLite::WmiException
+ end
+ supports_dsc
+end
+
+def windows_user_right?(right)
+ return false unless windows?
+
+ require "chef/win32/security"
+ Chef::ReservedNames::Win32::Security.get_account_right(ENV["USERNAME"]).include?(right)
+end
+
+# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system)
+def windows64?
+ windows? && ( ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" || ENV["PROCESSOR_ARCHITEW6432"] == "AMD64" )
+end
+
+# detects if the hardware is 32-bit
+def windows32?
+ windows? && !windows64?
+end
+
+def unix?
+ !windows?
+end
+
+def linux?
+ RUBY_PLATFORM.include?("linux")
+end
+
+def macos?
+ RUBY_PLATFORM.include?("darwin")
+end
+
+def macos_gte_11?
+ macos? && !!(ohai[:platform_version].to_i >= 11)
+end
+
+def solaris?
+ RUBY_PLATFORM.include?("solaris")
+end
+
+def freebsd?
+ RUBY_PLATFORM.include?("freebsd")
+end
+
+def intel_64bit?
+ !!(ohai[:kernel][:machine] == "x86_64")
+end
+
+def rhel?
+ !!(ohai[:platform_family] == "rhel")
+end
+
+def rhel6?
+ rhel? && !!(ohai[:platform_version].to_i == 6)
+end
+
+def opensuse?
+ suse? && !!(ohai[:platform_version].to_i >= 15)
+end
+
+def rhel7?
+ rhel? && !!(ohai[:platform_version].to_i == 7)
+end
+
+def rhel8?
+ rhel? && !!(ohai[:platform_version].to_i == 8)
+end
+
+def rhel_gte_8?
+ rhel? && !!(ohai[:platform_version].to_i >= 8)
+end
+
+def debian_family?
+ !!(ohai[:platform_family] == "debian")
+end
+
+def aix?
+ RUBY_PLATFORM.include?("aix")
+end
+
+def wpar?
+ !((ohai[:virtualization] || {})[:wpar_no].nil?)
+end
+
+def supports_cloexec?
+ Fcntl.const_defined?("F_SETFD") && Fcntl.const_defined?("FD_CLOEXEC")
+end
+
+def selinux_enabled?
+ # This code is currently copied from lib/chef/util/selinux to make
+ # specs independent of product.
+ selinuxenabled_path = which("selinuxenabled")
+ if selinuxenabled_path
+ cmd = Mixlib::ShellOut.new(selinuxenabled_path, returns: [0, 1])
+ cmd_result = cmd.run_command
+ case cmd_result.exitstatus
+ when 1
+ false
+ when 0
+ true
+ else
+ raise "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}"
+ end
+ else
+ # We assume selinux is not enabled if selinux utils are not
+ # installed.
+ false
+ end
+end
+
+def suse?
+ !!(ohai[:platform_family] == "suse")
+end
+
+def root?
+ return false if windows?
+
+ Process.euid == 0
+end
+
+def openssl_gte_101?
+ OpenSSL::OPENSSL_VERSION_NUMBER >= 10001000
+end
+
+def openssl_lt_101?
+ !openssl_gte_101?
+end
+
+def aes_256_gcm?
+ OpenSSL::Cipher.ciphers.include?("aes-256-gcm")
+end
+
+def fips?
+ ENV["CHEF_FIPS"] == "1"
+end
+
+class HttpHelper
+ extend Ohai::Mixin::HttpHelper
+ def self.logger
+ Chef::Log
+ end
+end
+
+def gce?
+ HttpHelper.can_socket_connect?(Ohai::Mixin::GCEMetadata::GCE_METADATA_ADDR, 80)
+rescue SocketError
+ false
+end
+
+def ifconfig?
+ which("ifconfig")
+end
+
+def choco_installed?
+ result = ShellHelpers.shell_out("choco --version")
+ result.stderr.empty?
+rescue
+ false
+end
+
+def pwsh_installed?
+ result = ShellHelpers.shell_out("pwsh.exe --version")
+ result.stderr.empty?
+rescue
+ false
+end
diff --git a/knife/spec/support/platforms/prof/gc.rb b/knife/spec/support/platforms/prof/gc.rb
new file mode 100644
index 0000000000..87e50758f3
--- /dev/null
+++ b/knife/spec/support/platforms/prof/gc.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+module RSpec
+ module Prof
+ module GC
+ class Profiler
+
+ # GC 1 invokes.
+ # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
+ # 1 0.012 159240 212940 10647 0.00000000000001530000
+ LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/.freeze
+
+ def start
+ ::GC::Profiler.enable unless ::GC::Profiler.enabled?
+ end
+
+ def stop
+ ::GC::Profiler.disable
+ end
+
+ def working_set_size
+ ::GC.start
+ ::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled?
+ ensure
+ ::GC::Profiler.clear
+ end
+
+ def handle_count
+ 0
+ end
+
+ end
+ end
+ end
+end
diff --git a/knife/spec/support/platforms/prof/win32.rb b/knife/spec/support/platforms/prof/win32.rb
new file mode 100644
index 0000000000..da95f16a3d
--- /dev/null
+++ b/knife/spec/support/platforms/prof/win32.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/win32/process"
+
+module RSpec
+ module Prof
+ module Win32
+ class Profiler
+
+ def start
+ GC.start
+ end
+
+ def stop
+ GC.start
+ end
+
+ def working_set_size
+ Chef::ReservedNames::Win32::Process.get_current_process.memory_info[:WorkingSetSize]
+ end
+
+ def handle_count
+ Chef::ReservedNames::Win32::Process.get_current_process.handle_count
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/support/platforms/win32/spec_service.rb b/knife/spec/support/platforms/win32/spec_service.rb
new file mode 100644
index 0000000000..27f71a8bf0
--- /dev/null
+++ b/knife/spec/support/platforms/win32/spec_service.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Serdar Sutay (<serdar@lambda.local>)
+# Copyright:: Copyright (c) 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.
+#
+
+if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
+ require "win32/daemon"
+
+ class SpecService < ::Win32::Daemon
+ def service_init
+ @test_service_file = "#{ENV["TMP"]}/spec_service_file"
+ end
+
+ def service_main(*startup_parameters)
+ while running?
+ unless File.exist?(@test_service_file)
+ File.open(@test_service_file, "wb") do |f|
+ f.write("This file is created by SpecService")
+ end
+ end
+
+ sleep 1
+ end
+ end
+
+ ################################################################################
+ # Control Signal Callback Methods
+ ################################################################################
+
+ def service_stop; end
+
+ def service_pause; end
+
+ def service_resume; end
+
+ def service_shutdown; end
+ end
+
+ # To run this file as a service, it must be called as a script from within
+ # the Windows Service framework. In that case, kick off the main loop!
+ if __FILE__ == $0
+ SpecService.mainloop
+ end
+end
diff --git a/knife/spec/support/recipe_dsl_helper.rb b/knife/spec/support/recipe_dsl_helper.rb
new file mode 100644
index 0000000000..2542345ed4
--- /dev/null
+++ b/knife/spec/support/recipe_dsl_helper.rb
@@ -0,0 +1,83 @@
+#
+# This is a helper for functional tests to embed the recipe DSL directly into the rspec example blocks using
+# unified mode.
+#
+# If you wind up wanting to stub/expect on internal details of the resource/provider you are not testing the
+# public API and are trying to write a unit test, which this is not designed for.
+#
+# If you want to start writing full recipes and testing them, doing notifies/subscribes/etc then you are writing
+# an integration test, and not a functional single-resource test, which this is not designed for.
+#
+# Examples:
+#
+# it "creates a file" do
+# FileUtils.rm_f("/tmp/foo.xyz")
+# file "/tmp/foo.xyz" do # please use proper tmpdir though
+# content "whatever"
+# end.should_be_updated
+# expect(IO.read("/tmp/foo.xyz").to eql("content")
+# end
+#
+# it "is idempotent" do
+# FileUtils.rm_f("/tmp/foo.xyz")
+# file "/tmp/foo.xyz" do # please use proper tmpdir though
+# content "whatever"
+# end.should_be_updated
+# file "/tmp/foo.xyz" do # please use proper tmpdir though
+# content "whatever"
+# end.should_not_be_updated
+# expect(IO.read("/tmp/foo.xyz").to eql("content")
+# end
+#
+# it "has a failure" do
+# FileUtils.rm_f("/tmp/foo.xyz")
+# expect { file "/tmp/lksjdflksjdf/foo.xyz" do
+# content "whatever"
+# end }.to raise_error(Chef::Exception::EnclosingDirectoryDoesNotExist)
+# end
+#
+module RecipeDSLHelper
+ include Chef::DSL::Recipe
+ def event_dispatch
+ @event_dispatch ||= Chef::EventDispatch::Dispatcher.new
+ end
+
+ def node
+ @node ||= Chef::Node.new.tap do |n|
+ # clone the global ohai data to keep tests fast but reasonably isolated
+ n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
+ end
+ end
+
+ def run_context
+ @run_context ||= Chef::RunContext.new(node, {}, event_dispatch).tap do |rc|
+ rc.resource_collection.unified_mode = true
+ Chef::Runner.new(rc)
+ end
+ end
+
+ def cookbook_name
+ "rspec"
+ end
+
+ def recipe_name
+ "default"
+ end
+
+ def declare_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
+ created_at = caller[0]
+ rspec_context = self
+ # we slightly abuse the "enclosing_provider" method_missing magic to send methods to the rspec example block so that
+ # rspec `let` methods work as arguments to resource properties
+ resource = super(type, name, created_at: created_at, run_context: run_context, enclosing_provider: rspec_context, &resource_attrs_block)
+ # we also inject these methods to make terse expression of checking the updated status (so it is more readiable and
+ # therefore should get used more -- even though it is "should" vs. "expect")
+ resource.define_singleton_method(:should_be_updated) do
+ rspec_context.expect(self).to be_updated
+ end
+ resource.define_singleton_method(:should_not_be_updated) do
+ rspec_context.expect(self).not_to be_updated
+ end
+ resource
+ end
+end
diff --git a/knife/spec/support/shared/context/config.rb b/knife/spec/support/shared/context/config.rb
new file mode 100644
index 0000000000..7c4ae8dca6
--- /dev/null
+++ b/knife/spec/support/shared/context/config.rb
@@ -0,0 +1,18 @@
+
+#
+# Define config file setups for spec tests here.
+# https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
+#
+
+# Required chef files here:
+require "chef/config"
+
+# Basic config. Nothing fancy.
+shared_context "default config options" do
+ before do
+ Chef::Config[:cache_path] = windows? ? 'C:\chef' : "/var/chef"
+ end
+
+ # Don't need to have an after block to reset the config...
+ # The spec_helper.rb takes care of resetting the config state.
+end
diff --git a/knife/spec/support/shared/functional/knife.rb b/knife/spec/support/shared/functional/knife.rb
new file mode 100644
index 0000000000..89207e76fc
--- /dev/null
+++ b/knife/spec/support/shared/functional/knife.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+module SpecHelpers
+ module Knife
+ def redefine_argv(value)
+ Object.send(:remove_const, :ARGV)
+ Object.send(:const_set, :ARGV, value)
+ end
+
+ def with_argv(*argv)
+ original_argv = ARGV
+ redefine_argv(argv.flatten)
+ begin
+ yield
+ ensure
+ redefine_argv(original_argv)
+ end
+ end
+ end
+end
diff --git a/knife/spec/support/shared/integration/integration_helper.rb b/knife/spec/support/shared/integration/integration_helper.rb
new file mode 100644
index 0000000000..c42a04004a
--- /dev/null
+++ b/knife/spec/support/shared/integration/integration_helper.rb
@@ -0,0 +1,122 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Author:: Ho-Sheng Hsiao (<hosh@chef.io>)
+# Copyright:: Copyright (c) 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 "tmpdir"
+require "fileutils"
+require "chef/config"
+require "chef/json_compat"
+require "chef/server_api"
+require "cheffish/rspec/chef_run_support"
+
+module Cheffish
+ class BasicChefClient
+ def_delegators :@run_context, :before_notifications
+ end
+end
+
+module IntegrationSupport
+ include ChefZero::RSpec
+
+ def self.included(includer_class)
+ includer_class.extend(Cheffish::RSpec::ChefRunSupport)
+ includer_class.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ include ChefZero::RSpec
+
+ def when_the_repository(desc, *tags, &block)
+ context("when the chef repo #{desc}", *tags) do
+ before :each do
+ raise "Can only create one directory per test" if @repository_dir
+
+ @repository_dir = Dir.mktmpdir("chef_repo")
+ Chef::Config.chef_repo_path = @repository_dir
+ %w{client cookbook data_bag environment node role user}.each do |object_name|
+ Chef::Config.delete("#{object_name}_path".to_sym)
+ end
+ end
+
+ after :each do
+ if @repository_dir
+ begin
+ # TODO: "force" actually means "silence all exceptions". this
+ # silences a weird permissions error on Windows that we should track
+ # down, but for now there's no reason for it to blow up our CI.
+ FileUtils.remove_entry_secure(@repository_dir, force = ChefUtils.windows?)
+ ensure
+ @repository_dir = nil
+ end
+ end
+ Dir.chdir(@old_cwd) if @old_cwd
+ end
+
+ module_eval(&block)
+ end
+ end
+ end
+
+ def api
+ Chef::ServerAPI.new
+ end
+
+ def directory(relative_path, &block)
+ old_parent_path = @parent_path
+ @parent_path = path_to(relative_path)
+ FileUtils.mkdir_p(@parent_path)
+ instance_eval(&block) if block
+ @parent_path = old_parent_path
+ end
+
+ def file(relative_path, contents)
+ filename = path_to(relative_path)
+ dir = File.dirname(filename)
+ FileUtils.mkdir_p(dir) unless dir == "."
+ File.open(filename, "w") do |file|
+ raw = case contents
+ when Hash, Array
+ Chef::JSONCompat.to_json_pretty(contents)
+ else
+ contents
+ end
+ file.write(raw)
+ end
+ end
+
+ def symlink(relative_path, relative_dest)
+ filename = path_to(relative_path)
+ dir = File.dirname(filename)
+ FileUtils.mkdir_p(dir) unless dir == "."
+ dest_filename = path_to(relative_dest)
+ File.symlink(dest_filename, filename)
+ end
+
+ def path_to(relative_path)
+ File.expand_path(relative_path, (@parent_path || @repository_dir))
+ end
+
+ def cb_metadata(name, version, extra_text = "")
+ "name #{name.inspect}; version #{version.inspect}#{extra_text}"
+ end
+
+ def cwd(relative_path)
+ @old_cwd = Dir.pwd
+ Dir.chdir(path_to(relative_path))
+ end
+end
diff --git a/knife/spec/support/shared/integration/knife_support.rb b/knife/spec/support/shared/integration/knife_support.rb
new file mode 100644
index 0000000000..af7b503d16
--- /dev/null
+++ b/knife/spec/support/shared/integration/knife_support.rb
@@ -0,0 +1,192 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "chef/config"
+require "chef/knife"
+require "chef/application/knife"
+require "logger"
+require "chef/log"
+require "chef/chef_fs/file_system_cache"
+
+module KnifeSupport
+ DEBUG = ENV["DEBUG"]
+ def knife(*args, input: nil, instance_filter: nil)
+ # Allow knife('role from file roles/blah.json') rather than requiring the
+ # arguments to be split like knife('role', 'from', 'file', 'roles/blah.json')
+ # If any argument will have actual spaces in it, the long form is required.
+ # (Since knife commands always start with the command name, and command
+ # names with spaces are always multiple args, this is safe.)
+ if args.length == 1
+ args = args[0].split(/\s+/)
+ end
+
+ # Make output stable
+ Chef::Config[:concurrency] = 1
+
+ # Work on machines where we can't access /var
+ Dir.mktmpdir("checksums") do |checksums_cache_dir|
+ Chef::Config[:syntax_check_cache_path] = checksums_cache_dir
+
+ # This is Chef::Knife.run without load_commands--we'll load stuff
+ # ourselves, thank you very much
+ stdout = StringIO.new
+ stderr = StringIO.new
+
+ stdin = if input
+ StringIO.new(input)
+ else
+ STDIN
+ end
+
+ begin
+ puts "knife: #{args.join(" ")}" if DEBUG
+ subcommand_class = Chef::Knife.subcommand_class_from(args)
+ subcommand_class.options = Chef::Application::Knife.options.merge(subcommand_class.options)
+ subcommand_class.load_deps
+ instance = subcommand_class.new(args)
+
+ # Load configs
+ instance.merge_configs
+
+ # Capture stdout/stderr
+ instance.ui = Chef::Knife::UI.new(stdout, stderr, stdin, instance.config.merge(disable_editing: true))
+
+ # Don't print stuff
+ Chef::Config[:verbosity] = ( DEBUG ? 2 : 0 )
+ instance.config[:config_file] = File.join(CHEF_SPEC_DATA, "null_config.rb")
+
+ # Ensure the ChefFS cache is empty
+ Chef::ChefFS::FileSystemCache.instance.reset!
+
+ # Configure chef with a (mostly) blank knife.rb
+ # We set a global and then mutate it in our stub knife.rb so we can be
+ # extra sure that we're not loading someone's real knife.rb and then
+ # running test scenarios against a real chef server. If things don't
+ # smell right, abort.
+
+ # To ensure that we don't pick up a user's credentials file we lie through our teeth about
+ # it's existence.
+ allow(File).to receive(:file?).and_call_original
+ allow(File).to receive(:file?).with(File.expand_path("~/.chef/credentials")).and_return(false)
+
+ # Set a canary that is modified by the default null_config.rb config file.
+ $__KNIFE_INTEGRATION_FAILSAFE_CHECK = "ole"
+
+ # Allow tweaking the knife instance before configuration.
+ instance_filter.call(instance) if instance_filter
+
+ instance.configure_chef
+
+ # The canary is incorrect, meaning the normal null_config.rb didn't run. Something is wrong.
+ unless $__KNIFE_INTEGRATION_FAILSAFE_CHECK == "ole ole"
+ raise Exception, "Potential misconfiguration of integration tests detected. Aborting test."
+ end
+
+ logger = Logger.new(stderr)
+ logger.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{msg}\n" }
+ Chef::Log.use_log_devices([logger])
+ Chef::Log.level = ( DEBUG ? :debug : :warn )
+ Chef::Log::Formatter.show_time = false
+
+ instance.run_with_pretty_exceptions(true)
+
+ exit_code = 0
+
+ # This is how rspec catches exit()
+ rescue SystemExit => e
+ exit_code = e.status
+ ensure
+ Chef::Config.delete(:syntax_check_cache_path)
+ Chef::Config.delete(:concurrency)
+ end
+
+ KnifeResult.new(stdout.string, stderr.string, exit_code)
+ end
+ end
+
+ class KnifeResult
+
+ include ::RSpec::Matchers
+
+ def initialize(stdout, stderr, exit_code)
+ @stdout = stdout
+ @stderr = stderr
+ @exit_code = exit_code
+ end
+
+ attr_reader :stdout
+ attr_reader :stderr
+ attr_reader :exit_code
+
+ def should_fail(*args)
+ expected = {}
+ args.each do |arg|
+ if arg.is_a?(Hash)
+ expected.merge!(arg)
+ elsif arg.is_a?(Integer)
+ expected[:exit_code] = arg
+ else
+ expected[:stderr] = arg
+ end
+ end
+ expected[:exit_code] = 1 unless expected[:exit_code]
+ should_result_in(expected)
+ end
+
+ def should_succeed(*args)
+ expected = {}
+ args.each do |arg|
+ if arg.is_a?(Hash)
+ expected.merge!(arg)
+ else
+ expected[:stdout] = arg
+ end
+ end
+ should_result_in(expected)
+ end
+
+ private
+
+ def should_result_in(expected)
+ expected[:stdout] = "" unless expected[:stdout]
+ expected[:stdout] = expected[:stdout].is_a?(String) ? expected[:stdout].gsub(/[ \t\f\v]+$/, "") : expected[:stdout]
+ expected[:stderr] = "" unless expected[:stderr]
+ expected[:stderr] = expected[:stderr].is_a?(String) ? expected[:stderr].gsub(/[ \t\f\v]+$/, "") : expected[:stderr]
+ expected[:exit_code] = 0 unless expected[:exit_code]
+ # TODO make this go away
+ stderr_actual = @stderr.sub(/^WARNING: No knife configuration file found\n/, "")
+ stderr_actual = stderr_actual.gsub(/[ \t\f\v]+$/, "")
+ stdout_actual = @stdout
+ stdout_actual = stdout_actual.gsub(/[ \t\f\v]+$/, "")
+ if ChefUtils.windows?
+ stderr_actual = stderr_actual.gsub("\r\n", "\n")
+ stdout_actual = stdout_actual.gsub("\r\n", "\n")
+ end
+ if expected[:stderr].is_a?(Regexp)
+ expect(stderr_actual).to match(expected[:stderr])
+ else
+ expect(stderr_actual).to eq(expected[:stderr])
+ end
+ expect(@exit_code).to eq(expected[:exit_code])
+ if expected[:stdout].is_a?(Regexp)
+ expect(stdout_actual).to match(expected[:stdout])
+ else
+ expect(stdout_actual).to eq(expected[:stdout])
+ end
+ end
+ end
+end
diff --git a/knife/spec/support/shared/matchers/exit_with_code.rb b/knife/spec/support/shared/matchers/exit_with_code.rb
new file mode 100644
index 0000000000..a9f1af81ce
--- /dev/null
+++ b/knife/spec/support/shared/matchers/exit_with_code.rb
@@ -0,0 +1,32 @@
+require "rspec/expectations"
+
+# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec
+RSpec::Matchers.define :exit_with_code do |exp_code|
+ actual = nil
+ match do |block|
+ begin
+ block.call
+ rescue SystemExit => e
+ actual = e.status
+ end
+ actual && actual == exp_code
+ end
+
+ failure_message do |block|
+ "expected block to call exit(#{exp_code}) but exit" +
+ (actual.nil? ? " not called" : "(#{actual}) was called")
+ end
+
+ failure_message_when_negated do |block|
+ "expected block not to call exit(#{exp_code})"
+ end
+
+ description do
+ "expect block to call exit(#{exp_code})"
+ end
+
+ def supports_block_expectations?
+ true
+ end
+
+end
diff --git a/knife/spec/support/shared/matchers/match_environment_variable.rb b/knife/spec/support/shared/matchers/match_environment_variable.rb
new file mode 100644
index 0000000000..393775ea29
--- /dev/null
+++ b/knife/spec/support/shared/matchers/match_environment_variable.rb
@@ -0,0 +1,17 @@
+
+require "rspec/expectations"
+require "spec/support/platform_helpers"
+
+RSpec::Matchers.define :match_environment_variable do |varname|
+ match do |actual|
+ expected = if windows? && ENV[varname].nil?
+ # On Windows, if an environment variable is not set, the command
+ # `echo %VARNAME%` outputs %VARNAME%
+ "%#{varname}%"
+ else
+ ENV[varname].to_s
+ end
+
+ actual == expected
+ end
+end
diff --git a/knife/spec/support/shared/unit/knife_shared.rb b/knife/spec/support/shared/unit/knife_shared.rb
new file mode 100644
index 0000000000..3c7459cfcc
--- /dev/null
+++ b/knife/spec/support/shared/unit/knife_shared.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+shared_examples_for "mandatory field missing" do
+ context "when field is nil" do
+ before do
+ knife.name_args = name_args
+ end
+
+ it "exits 1" do
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/You must specify a #{fieldname}/)
+ end
+ end
+end
diff --git a/knife/spec/support/shared/unit/mock_shellout.rb b/knife/spec/support/shared/unit/mock_shellout.rb
new file mode 100644
index 0000000000..0ea8f64c4d
--- /dev/null
+++ b/knife/spec/support/shared/unit/mock_shellout.rb
@@ -0,0 +1,49 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright 2015-2016, John Keiser.
+# 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.
+#
+
+#
+# Mocks shellout results. Examples:
+# mock_shellout_command("systemctl --all", exitstatus: 1)
+#
+class MockShellout
+ module RSpec
+ def mock_shellout_command(command, **result)
+ allow(::Mixlib::ShellOut).to receive(:new).with(command, anything).and_return MockShellout.new(**result)
+ end
+ end
+
+ def initialize(**properties)
+ @properties = {
+ stdout: "",
+ stderr: "",
+ exitstatus: 0,
+ }.merge(properties)
+ end
+
+ def method_missing(name, *args)
+ @properties[name.to_sym]
+ end
+
+ def error?
+ exitstatus != 0
+ end
+
+ def error!
+ raise Mixlib::ShellOut::ShellCommandFailed, "Expected process to exit with 0, but received #{exitstatus}" if error?
+ end
+end
diff --git a/knife/spec/tiny_server.rb b/knife/spec/tiny_server.rb
new file mode 100644
index 0000000000..786130d0d5
--- /dev/null
+++ b/knife/spec/tiny_server.rb
@@ -0,0 +1,190 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "webrick"
+require "webrick/https"
+require "rack"
+require "singleton"
+require "open-uri"
+require "chef/config"
+
+module TinyServer
+
+ class Manager
+
+ # 5 == debug, 3 == warning
+ LOGGER = WEBrick::Log.new(STDOUT, 3)
+ DEFAULT_OPTIONS = {
+ Port: 9000,
+ Host: "localhost",
+ Logger: LOGGER,
+ # SSLEnable: options[:ssl],
+ # SSLCertName: [ [ 'CN', WEBrick::Utils::getservername ] ],
+ AccessLog: [], # Remove this option to enable the access log when debugging.
+ }.freeze
+
+ def initialize(**options)
+ @options = DEFAULT_OPTIONS.merge(options)
+ @creator = caller.first
+ end
+
+ attr_reader :options
+ attr_reader :creator
+ attr_reader :server
+
+ def start(timeout = 5)
+ raise "Server already started!" if server
+
+ # Create the server (but don't start yet)
+ start_queue = Queue.new
+ @server = create_server(StartCallback: proc { start_queue << true })
+
+ @server_thread = Thread.new do
+ # Ensure any exceptions will cause the main rspec thread to fail too
+ Thread.current.abort_on_exception = true
+ server.start
+ end
+
+ # Wait for the StartCallback to tell us we've started
+ Timeout.timeout(timeout) do
+ start_queue.pop
+ end
+ end
+
+ def stop(timeout = 5)
+ if server
+ server.shutdown
+ @server = nil
+ end
+
+ if server_thread
+ begin
+ # Wait for a normal shutdown
+ server_thread.join(timeout)
+ rescue
+ # If it wouldn't shut down normally, kill it.
+ server_thread.kill
+ server_thread.join(timeout)
+ end
+ @server_thread = nil
+ end
+ end
+
+ private
+
+ attr_reader :server_thread
+
+ def create_server(**extra_options)
+ server = WEBrick::HTTPServer.new(**options, **extra_options)
+ server.mount("/", Rack::Handler::WEBrick, API.instance)
+ server
+ end
+ end
+
+ class API
+ include Singleton
+
+ GET = "GET".freeze
+ PUT = "PUT".freeze
+ POST = "POST".freeze
+ DELETE = "DELETE".freeze
+
+ attr_reader :routes
+
+ def initialize
+ clear
+ end
+
+ def clear
+ @routes = { GET => [], PUT => [], POST => [], DELETE => [] }
+ end
+
+ def get(path, response_code, data = nil, headers = nil, &block)
+ @routes[GET] << Route.new(path, Response.new(response_code, data, headers, &block))
+ end
+
+ def put(path, response_code, data = nil, headers = nil, &block)
+ @routes[PUT] << Route.new(path, Response.new(response_code, data, headers, &block))
+ end
+
+ def post(path, response_code, data = nil, headers = nil, &block)
+ @routes[POST] << Route.new(path, Response.new(response_code, data, headers, &block))
+ end
+
+ def delete(path, response_code, data = nil, headers = nil, &block)
+ @routes[DELETE] << Route.new(path, Response.new(response_code, data, headers, &block))
+ end
+
+ def call(env)
+ if response = response_for_request(env)
+ response.call
+ else
+ debug_info = { message: "no data matches the request for #{env["REQUEST_URI"]}",
+ available_routes: @routes, request: env }
+ # Uncomment me for glorious debugging
+ # pp :not_found => debug_info
+ [404, { "Content-Type" => "application/json" }, [ Chef::JSONCompat.to_json(debug_info) ]]
+ end
+ end
+
+ def response_for_request(env)
+ if route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) }
+ route.response
+ end
+ end
+ end
+
+ class Route
+ attr_reader :response
+
+ def initialize(path_spec, response)
+ @path_spec, @response = path_spec, response
+ end
+
+ def matches_request?(uri)
+ uri = URI.parse(uri).request_uri
+ @path_spec === uri
+ end
+
+ def to_s
+ "#{@path_spec} => (#{@response})"
+ end
+
+ end
+
+ class Response
+ HEADERS = { "Content-Type" => "application/json" }.freeze
+
+ def initialize(response_code = 200, data = nil, headers = nil, &block)
+ @response_code, @data = response_code, data
+ @response_headers = headers ? HEADERS.merge(headers) : HEADERS
+ @block = block_given? ? block : nil
+ end
+
+ def call
+ data = @data || @block.call
+ [@response_code, @response_headers, Array(data)]
+ end
+
+ def to_s
+ "#{@response_code} => #{(@data || @block)}"
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/application/knife_spec.rb b/knife/spec/unit/application/knife_spec.rb
new file mode 100644
index 0000000000..300f2b2f83
--- /dev/null
+++ b/knife/spec/unit/application/knife_spec.rb
@@ -0,0 +1,241 @@
+#
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "#{CHEF_SPEC_DATA}/knife_subcommand/test_yourself"
+
+describe Chef::Application::Knife do
+ include SpecHelpers::Knife
+
+ before(:all) do
+ class NoopKnifeCommand < Chef::Knife
+ option :opt_with_default,
+ short: "-D VALUE",
+ long: "-optwithdefault VALUE",
+ default: "default-value"
+
+ def run; end
+ end
+ end
+
+ after(:each) do
+ # reset some really nasty global state
+ NoopKnifeCommand.reset_config_loader!
+ end
+
+ before(:each) do
+ # Prevent code from getting loaded on every test invocation.
+ allow(Chef::Knife).to receive(:load_commands)
+
+ @knife = Chef::Application::Knife.new
+ allow(@knife).to receive(:puts)
+ allow(@knife).to receive(:trap)
+ allow(Chef::Knife).to receive(:list_commands)
+ end
+
+ it "should exit 1 and print the options if no arguments are given at all" do
+ with_argv([]) do
+ expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
+ end
+ end
+
+ it "should exit 2 if run without a sub command" do
+ with_argv("--user", "adam") do
+ expect(Chef::Log).to receive(:error).with(/you need to pass a sub\-command/i)
+ expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) }
+ end
+ end
+
+ it "should run a sub command with the applications command line option prototype" do
+ with_argv(*%w{noop knife command with some args}) do
+ knife = double(Chef::Knife)
+ expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife)
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ end
+
+ it "should set the colored output to true by default on windows and true on all other platforms as well" do
+ with_argv(*%w{noop knife command}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:color]).to be_truthy
+ end
+
+ context "validate --format option" do
+ it "should set the default format summary" do
+ with_argv(*%w{noop knife command}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ expect(@knife.default_config[:format]).to eq("summary")
+ end
+ end
+
+ it "should raise the error for invalid value" do
+ with_argv(*%w{noop knife command -F abc}) do
+ expect(STDOUT).to receive(:puts).at_least(2).times
+ expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) }
+ end
+ end
+ end
+
+ context "when given fips flags" do
+ context "when Chef::Config[:fips]=false" do
+ before do
+ # This is required because the chef-fips pipeline does
+ # has a default value of true for fips
+ Chef::Config[:fips] = false
+ end
+
+ it "does not initialize fips mode when no flags are passed" do
+ with_argv(*%w{noop knife command}) do
+ expect(@knife).to receive(:exit).with(0)
+ expect(Chef::Config).not_to receive(:enable_fips_mode)
+ @knife.run
+ expect(Chef::Config[:fips]).to eq(false)
+ end
+ end
+
+ it "overwrites the Chef::Config value when passed --fips" do
+ with_argv(*%w{noop knife command --fips}) do
+ expect(@knife).to receive(:exit).with(0)
+ expect(Chef::Config).to receive(:enable_fips_mode)
+ @knife.run
+ expect(Chef::Config[:fips]).to eq(true)
+ end
+ end
+ end
+
+ context "when Chef::Config[:fips]=true" do
+ before do
+ Chef::Config[:fips] = true
+ end
+
+ it "initializes fips mode when passed --fips" do
+ with_argv(*%w{noop knife command --fips}) do
+ expect(@knife).to receive(:exit).with(0)
+ expect(Chef::Config).to receive(:enable_fips_mode)
+ @knife.run
+ expect(Chef::Config[:fips]).to eq(true)
+ end
+ end
+
+ it "overwrites the Chef::Config value when passed --no-fips" do
+ with_argv(*%w{noop knife command --no-fips}) do
+ expect(@knife).to receive(:exit).with(0)
+ expect(Chef::Config).not_to receive(:enable_fips_mode)
+ @knife.run
+ expect(Chef::Config[:fips]).to eq(false)
+ end
+ end
+ end
+ end
+
+ describe "when given a path to the client key" do
+ it "expands a relative path relative to the CWD" do
+ relative_path = ".chef/client.pem"
+ allow(Dir).to receive(:pwd).and_return(CHEF_SPEC_DATA)
+ with_argv(*%W{noop knife command -k #{relative_path}}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:client_key]).to eq(File.join(CHEF_SPEC_DATA, relative_path))
+ end
+
+ it "expands a ~/home/path to the correct full path" do
+ home_path = "~/.chef/client.pem"
+ with_argv(*%W{noop knife command -k #{home_path}}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:client_key]).to eq(File.join(ENV["HOME"], ".chef/client.pem").gsub((File::ALT_SEPARATOR || "\\"), File::SEPARATOR))
+ end
+
+ it "does not expand a full path" do
+ full_path = if windows?
+ "C:/chef/client.pem"
+ else
+ "/etc/chef/client.pem"
+ end
+ with_argv(*%W{noop knife command -k #{full_path}}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:client_key]).to eq(full_path)
+ end
+ end
+
+ describe "with environment configuration" do
+ before do
+ Chef::Config[:environment] = nil
+ end
+
+ it "should default to no environment" do
+ with_argv(*%w{noop knife command}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:environment]).to eq(nil)
+ end
+
+ it "should load the environment from the config file" do
+ config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
+ with_argv(*%W{noop knife command -c #{config_file}}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:environment]).to eq("production")
+ end
+
+ it "should load the environment from the CLI options" do
+ with_argv(*%w{noop knife command -E development}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:environment]).to eq("development")
+ end
+
+ it "should override the config file environment with the CLI environment" do
+ config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
+ with_argv(*%W{noop knife command -c #{config_file} -E override}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:environment]).to eq("override")
+ end
+
+ it "should override the config file environment with the CLI environment regardless of order" do
+ config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
+ with_argv(*%W{noop knife command -E override -c #{config_file}}) do
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ expect(Chef::Config[:environment]).to eq("override")
+ end
+
+ it "should run a sub command with the applications command line option prototype" do
+ with_argv(*%w{noop knife command with some args}) do
+ knife = double(Chef::Knife)
+ expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife)
+ expect(@knife).to receive(:exit).with(0)
+ @knife.run
+ end
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb b/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
new file mode 100644
index 0000000000..a4d007611e
--- /dev/null
+++ b/knife/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Lamont Granquist <lamont@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::Bootstrap::ChefVaultHandler do
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:stdin) { StringIO.new }
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
+
+ let(:config) { {} }
+
+ let(:client) { Chef::ApiClient.new }
+
+ let(:chef_vault_handler) do
+ chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(config: config, ui: ui)
+ chef_vault_handler
+ end
+
+ context "when there's no vault option" do
+ it "should report its not doing anything" do
+ expect(chef_vault_handler.doing_chef_vault?).to be false
+ end
+
+ it "shouldn't do anything" do
+ expect(chef_vault_handler).to_not receive(:sanity_check)
+ expect(chef_vault_handler).to_not receive(:update_bootstrap_vault_json!)
+ chef_vault_handler
+ end
+ end
+
+ context "when setting chef vault items" do
+ let(:bootstrap_vault_item) { double("ChefVault::Item") }
+
+ before do
+ expect(chef_vault_handler).to receive(:require_chef_vault!).at_least(:once)
+ expect(bootstrap_vault_item).to receive(:clients).with(client).at_least(:once)
+ expect(bootstrap_vault_item).to receive(:save).at_least(:once)
+ end
+
+ context "from config[:bootstrap_vault_item]" do
+ it "sets a single item as a scalar" do
+ config[:bootstrap_vault_item] = { "vault" => "item1" }
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets a single item as an array" do
+ config[:bootstrap_vault_item] = { "vault" => [ "item1" ] }
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two items as an array" do
+ config[:bootstrap_vault_item] = { "vault" => %w{item1 item2} }
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two vaults from different hash keys" do
+ config[:bootstrap_vault_item] = { "vault" => %w{item1 item2}, "vault2" => [ "item3" ] }
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+ end
+
+ context "from config[:bootstrap_vault_json]" do
+ it "sets a single item as a scalar" do
+ config[:bootstrap_vault_json] = '{ "vault": "item1" }'
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets a single item as an array" do
+ config[:bootstrap_vault_json] = '{ "vault": [ "item1" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two items as an array" do
+ config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two vaults from different hash keys" do
+ config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+ end
+
+ context "from config[:bootstrap_vault_file]" do
+
+ def setup_file_contents(json)
+ stringio = StringIO.new(json)
+ config[:bootstrap_vault_file] = "/foo/bar/baz"
+ expect(File).to receive(:read).with(config[:bootstrap_vault_file]).and_return(stringio)
+ end
+
+ it "sets a single item as a scalar" do
+ setup_file_contents('{ "vault": "item1" }')
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets a single item as an array" do
+ setup_file_contents('{ "vault": [ "item1" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two items as an array" do
+ setup_file_contents('{ "vault": [ "item1", "item2" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+
+ it "sets two vaults from different hash keys" do
+ setup_file_contents('{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
+ chef_vault_handler.run(client)
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/bootstrap/client_builder_spec.rb b/knife/spec/unit/knife/bootstrap/client_builder_spec.rb
new file mode 100644
index 0000000000..cf6999b093
--- /dev/null
+++ b/knife/spec/unit/knife/bootstrap/client_builder_spec.rb
@@ -0,0 +1,207 @@
+#
+# Author:: Lamont Granquist <lamont@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::Bootstrap::ClientBuilder do
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:stdin) { StringIO.new }
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
+
+ let(:config) { {} }
+
+ let(:chef_config) { {} }
+
+ let(:node_name) { "bevell.wat" }
+
+ let(:rest) { double("Chef::ServerAPI") }
+
+ let(:client_builder) do
+ client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(config: config, chef_config: chef_config, ui: ui)
+ allow(client_builder).to receive(:rest).and_return(rest)
+ allow(client_builder).to receive(:node_name).and_return(node_name)
+ client_builder
+ end
+
+ context "#sanity_check!" do
+ let(:response_404) { OpenStruct.new(code: "404") }
+ let(:exception_404) { Net::HTTPClientException.new("404 not found", response_404) }
+
+ context "in cases where the prompting fails" do
+ before do
+ # should fail early in #run
+ expect(client_builder).to_not receive(:create_client!)
+ expect(client_builder).to_not receive(:create_node!)
+ end
+
+ it "exits when the node exists and the user does not want to delete" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return("n")
+ expect { client_builder.run }.to raise_error(SystemExit)
+ end
+
+ it "exits when the client exists and the user does not want to delete" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return("n")
+ expect { client_builder.run }.to raise_error(SystemExit)
+ end
+ end
+
+ context "in cases where the prompting succeeds" do
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:create_client!)
+ expect(client_builder).to receive(:create_node!)
+ end
+
+ it "when both the client and node do not exist it succeeds" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404)
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are allowed to delete an old node" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return("y")
+ expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:delete).with("nodes/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are allowed to delete an old client" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return("y")
+ expect(rest).to receive(:delete).with("clients/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are are allowed to delete both an old client and node" do
+ expect(rest).to receive(:get).with("nodes/#{node_name}")
+ expect(rest).to receive(:get).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).twice.and_return("y")
+ expect(rest).to receive(:delete).with("nodes/#{node_name}")
+ expect(rest).to receive(:delete).with("clients/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+ end
+ end
+
+ context "#create_client!" do
+ let(:client) { Chef::ApiClient.new }
+
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:sanity_check)
+ expect(client_builder).to receive(:create_node!)
+ end
+
+ it "delegates everything to Chef::ApiClient::Registration and sets client" do
+ reg_double = double("Chef::ApiClient::Registration")
+ expect(Chef::ApiClient::Registration).to receive(:new).with(node_name, client_builder.client_path, http_api: rest).and_return(reg_double)
+ expect(reg_double).to receive(:run).and_return(client)
+ client_builder.run
+ expect(client_builder.client).to eq(client)
+ end
+
+ end
+
+ context "#client_path" do
+ it "has a public API for the temporary client.pem file" do
+ expect(client_builder.client_path).to match(/#{node_name}.pem/)
+ end
+ end
+
+ context "#create_node!" do
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:sanity_check)
+ expect(client_builder).to receive(:create_client!)
+ # mock out default node building steps
+ expect(client_builder).to receive(:client_rest).and_return(client_rest)
+ expect(Chef::Node).to receive(:new).with(chef_server_rest: client_rest).and_return(node)
+ expect(node).to receive(:name).with(node_name)
+ expect(node).to receive(:save)
+ end
+
+ let(:client_rest) { double("Chef::ServerAPI (client)") }
+
+ let(:node) { double("Chef::Node") }
+
+ it "builds a node with a default run_list of []" do
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+
+ it "does not add tags by default" do
+ allow(node).to receive(:run_list).with([])
+ expect(node).to_not receive(:tags)
+ client_builder.run
+ end
+
+ it "adds tags to the node when given" do
+ tag_receiver = []
+
+ config[:tags] = %w{foo bar}
+ allow(node).to receive(:run_list).with([])
+ allow(node).to receive(:tags).and_return(tag_receiver)
+ client_builder.run
+ expect(tag_receiver).to eq %w{foo bar}
+ end
+
+ it "builds a node when the run_list is a string" do
+ config[:run_list] = "role[base],role[app]"
+ expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
+ client_builder.run
+ end
+
+ it "builds a node when the run_list is an Array" do
+ config[:run_list] = ["role[base]", "role[app]"]
+ expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
+ client_builder.run
+ end
+
+ it "builds a node with first_boot_attributes if they're given" do
+ config[:first_boot_attributes] = { baz: :quux }
+ expect(node).to receive(:normal_attrs=).with({ baz: :quux })
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+
+ it "builds a node with an environment if its given" do
+ config[:environment] = "production"
+ expect(node).to receive(:environment).with("production")
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+
+ it "builds a node with policy_name and policy_group when given" do
+ config[:policy_name] = "my-app"
+ config[:policy_group] = "staging"
+
+ expect(node).to receive(:run_list).with([])
+ expect(node).to receive(:policy_name=).with("my-app")
+ expect(node).to receive(:policy_group=).with("staging")
+
+ client_builder.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/bootstrap/train_connector_spec.rb b/knife/spec/unit/knife/bootstrap/train_connector_spec.rb
new file mode 100644
index 0000000000..0a1091fa8d
--- /dev/null
+++ b/knife/spec/unit/knife/bootstrap/train_connector_spec.rb
@@ -0,0 +1,244 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "ostruct"
+require "chef/knife/bootstrap/train_connector"
+
+describe Chef::Knife::Bootstrap::TrainConnector do
+ let(:protocol) { "mock" }
+ let(:family) { "unknown" }
+ let(:release) { "unknown" } # version
+ let(:name) { "unknown" }
+ let(:arch) { "x86_64" }
+ let(:connection_opts) { {} } # connection opts
+ let(:host_url) { "mock://user1@example.com" }
+ let(:mock_connection) { true }
+
+ subject do
+ # Example groups can still override by setting explicitly it in 'connection_opts'
+ tc = Chef::Knife::Bootstrap::TrainConnector.new(host_url, protocol, connection_opts)
+ tc
+ end
+
+ before(:each) do
+ if mock_connection
+ subject.connect!
+ subject.connection.mock_os(
+ family: family,
+ name: name,
+ release: release,
+ arch: arch
+ )
+ end
+ end
+
+ describe "platform helpers" do
+ context "on linux" do
+ let(:family) { "debian" }
+ let(:name) { "ubuntu" }
+ it "reports that it is linux and unix, because that is how train classifies it" do
+ expect(subject.unix?).to eq true
+ expect(subject.linux?).to eq true
+ expect(subject.windows?).to eq false
+ end
+ end
+ context "on unix" do
+ let(:family) { "os" }
+ let(:name) { "mac_os_x" }
+ it "reports only a unix OS" do
+ expect(subject.unix?).to eq true
+ expect(subject.linux?).to eq false
+ expect(subject.windows?).to eq false
+ end
+ end
+ context "on windows" do
+ let(:family) { "windows" }
+ let(:name) { "windows" }
+ it "reports only a windows OS" do
+ expect(subject.unix?).to eq false
+ expect(subject.linux?).to eq false
+ expect(subject.windows?).to eq true
+ end
+ end
+ end
+
+ describe "#connect!" do
+ it "establishes the connection to the remote host by waiting for it" do
+ expect(subject.connection).to receive(:wait_until_ready)
+ subject.connect!
+ end
+ end
+
+ describe "#initialize" do
+ let(:mock_connection) { false }
+
+ context "when provided target is a proper URL" do
+ let(:protocol) { "ssh" }
+ let(:host_url) { "mock://user1@localhost:2200" }
+ it "correctly configures the instance from the URL" do
+ expect(subject.config[:backend]).to eq "mock"
+ expect(subject.config[:port]).to eq 2200
+ expect(subject.config[:host]).to eq "localhost"
+ expect(subject.config[:user]).to eq "user1"
+ end
+
+ context "and conflicting options are given" do
+ let(:connection_opts) { { user: "user2", host: "example.com", port: 15 } }
+ it "resolves them from the URI" do
+ expect(subject.config[:backend]).to eq "mock"
+ expect(subject.config[:port]).to eq 2200
+ expect(subject.config[:host]).to eq "localhost"
+ expect(subject.config[:user]).to eq "user1"
+ end
+ end
+ end
+
+ context "when provided target is just a hostname" do
+ let(:host_url) { "localhost" }
+ let(:protocol) { "mock" }
+ it "correctly sets backend protocol from the default" do
+ expect(subject.config[:backend]).to eq "mock"
+ end
+
+ context "and options have been provided that are supported by the transport" do
+ let(:protocol) { "ssh" }
+ let(:connection_opts) { { port: 15, user: "user2" } }
+
+ it "sets hostname and transport from arguments and provided fields from options" do
+ expect(subject.config[:backend]).to eq "ssh"
+ expect(subject.config[:host]).to eq "localhost"
+ expect(subject.config[:user]).to eq "user2"
+ expect(subject.config[:port]).to eq 15
+ end
+
+ end
+
+ end
+
+ context "when provided target is just a an IP address" do
+ let(:host_url) { "127.0.0.1" }
+ let(:protocol) { "mock" }
+ it "correctly sets backend protocol from the default" do
+ expect(subject.config[:backend]).to eq "mock"
+ end
+ end
+ end
+
+ describe "#temp_dir" do
+ context "under windows" do
+ let(:family) { "windows" }
+ let(:name) { "windows" }
+
+ it "uses the windows command to create the temp dir" do
+ expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND
+ expect(subject).to receive(:run_command!).with(expected_command)
+ .and_return double("result", stdout: "C:/a/path")
+ expect(subject.temp_dir).to eq "C:/a/path"
+ end
+
+ end
+ context "under linux and unix-like" do
+ let(:family) { "debian" }
+ let(:name) { "ubuntu" }
+ let(:random) { "wScHX6" }
+ let(:dir) { "/tmp/chef_#{random}" }
+
+ before do
+ allow(SecureRandom).to receive(:alphanumeric).with(6).and_return(random)
+ end
+
+ context "uses the *nix command to create the temp dir and sets ownership to logged-in" do
+ it "with sudo privilege" do
+ subject.config[:sudo] = true
+ expected_command1 = "mkdir -p '#{dir}'"
+ expected_command2 = "chown user1 '#{dir}'"
+ expect(subject).to receive(:run_command!).with(expected_command1)
+ .and_return double("result", stdout: "\r\n")
+ expect(subject).to receive(:run_command!).with(expected_command2)
+ .and_return double("result", stdout: "\r\n")
+ expect(subject.temp_dir).to eq(dir)
+ end
+
+ it "without sudo privilege" do
+ expected_command = "mkdir -p '#{dir}'"
+ expect(subject).to receive(:run_command!).with(expected_command)
+ .and_return double("result", stdout: "\r\n")
+ expect(subject.temp_dir).to eq(dir)
+ end
+ end
+
+ context "with noise in stderr" do
+ it "uses the *nix command to create the temp dir" do
+ expected_command = "mkdir -p '#{dir}'"
+ expect(subject).to receive(:run_command!).with(expected_command)
+ .and_return double("result", stdout: "sudo: unable to resolve host hostname.localhost\r\n" + "#{dir}\r\n")
+ expect(subject.temp_dir).to eq(dir)
+ end
+ end
+ end
+ end
+ context "#upload_file_content!" do
+ it "creates a local file with expected content and uploads it" do
+ expect(subject).to receive(:upload_file!) do |local_path, remote_path|
+ expect(File.read(local_path)).to eq "test data"
+ expect(remote_path).to eq "/target/path"
+ end
+ expect_any_instance_of(Tempfile).to receive(:binmode)
+ subject.upload_file_content!("test data", "/target/path")
+ end
+ end
+
+ context "del_file" do
+ context "on windows" do
+ let(:family) { "windows" }
+ let(:name) { "windows" }
+ it "deletes the file with a windows command" do
+ expect(subject).to receive(:run_command!) do |cmd, &_handler|
+ expect(cmd).to match(/Test-Path "deleteme\.txt".*/)
+ end
+ subject.del_file!("deleteme.txt")
+ end
+ end
+ context "on unix-like" do
+ let(:family) { "debian" }
+ let(:name) { "ubuntu" }
+ it "deletes the file with a windows command" do
+ expect(subject).to receive(:run_command!) do |cmd, &_handler|
+ expect(cmd).to match(/rm -f "deleteme\.txt".*/)
+ end
+ subject.del_file!("deleteme.txt")
+ end
+ end
+ end
+
+ context "#run_command!" do
+ it "raises a RemoteExecutionFailed when the remote execution failed" do
+ command_result = double("results", stdout: "", stderr: "failed", exit_status: 1)
+ expect(subject).to receive(:run_command).and_return command_result
+
+ expect { subject.run_command!("test") }.to raise_error do |e|
+ expect(e.hostname).to eq subject.hostname
+ expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed
+ expect(e.stderr).to eq "failed"
+ expect(e.stdout).to eq ""
+ expect(e.exit_status).to eq 1
+ end
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/bootstrap_spec.rb b/knife/spec/unit/knife/bootstrap_spec.rb
new file mode 100644
index 0000000000..30878edf39
--- /dev/null
+++ b/knife/spec/unit/knife/bootstrap_spec.rb
@@ -0,0 +1,2220 @@
+#
+# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
+# Copyright:: Copyright 2010-2016, Ian Meyer
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::Bootstrap.load_deps
+
+describe Chef::Knife::Bootstrap do
+ let(:bootstrap_template) { nil }
+ let(:stderr) { StringIO.new }
+ let(:bootstrap_cli_options) { [ ] }
+ let(:linux_test) { true }
+ let(:windows_test) { false }
+ let(:linux_test) { false }
+ let(:unix_test) { false }
+ let(:ssh_test) { false }
+
+ let(:connection) do
+ double("TrainConnector",
+ windows?: windows_test,
+ linux?: linux_test,
+ unix?: unix_test)
+ end
+
+ let(:knife) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+ Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
+
+ k = Chef::Knife::Bootstrap.new(bootstrap_cli_options)
+ allow(k.ui).to receive(:stderr).and_return(stderr)
+ allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ allow(k).to receive(:connection).and_return connection
+ k.merge_configs
+ k
+ end
+
+ context "#check_license" do
+ let(:acceptor) { instance_double(LicenseAcceptance::Acceptor) }
+
+ before do
+ expect(LicenseAcceptance::Acceptor).to receive(:new).and_return(acceptor)
+ end
+
+ describe "when a license is not required" do
+ it "does not set the chef_license" do
+ expect(acceptor).to receive(:license_required?).and_return(false)
+ knife.check_license
+ expect(Chef::Config[:chef_license]).to eq(nil)
+ end
+ end
+
+ describe "when a license is required" do
+ it "sets the chef_license" do
+ expect(acceptor).to receive(:license_required?).and_return(true)
+ expect(acceptor).to receive(:id_from_mixlib).and_return("id")
+ expect(acceptor).to receive(:check_and_persist)
+ expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist")
+ knife.check_license
+ expect(Chef::Config[:chef_license]).to eq("accept-no-persist")
+ end
+ end
+ end
+
+ context "#bootstrap_template" do
+ it "should default to chef-full" do
+ expect(knife.bootstrap_template).to be_a_kind_of(String)
+ expect(File.basename(knife.bootstrap_template)).to eq("chef-full")
+ end
+ end
+
+ context "#render_template - when using the chef-full default template" do
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ it "should render client.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.rb <<'EOP'")
+ expect(rendered_template).to match("chef_server_url \"https://localhost:443\"")
+ expect(rendered_template).to match("validation_client_name \"chef-validator\"")
+ expect(rendered_template).to match("log_location STDOUT")
+ end
+
+ it "should render first-boot.json" do
+ expect(rendered_template).to match("cat > /etc/chef/first-boot.json <<'EOP'")
+ expect(rendered_template).to match('{"run_list":\[\]}')
+ end
+
+ context "and encrypted_data_bag_secret was provided" do
+ it "should render encrypted_data_bag_secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return("secrets")
+ expect(rendered_template).to match("cat > /etc/chef/encrypted_data_bag_secret <<'EOP'")
+ expect(rendered_template).to match('{"run_list":\[\]}')
+ expect(rendered_template).to match(/secrets/)
+ end
+ end
+ end
+
+ context "with --bootstrap-vault-item" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] }
+ it "sets the knife config cli option correctly" do
+ expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] })
+ end
+ end
+
+ context "with --bootstrap-preinstall-command" do
+ command = "while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do\n echo 'waiting for dpkg lock';\n sleep 1;\n done;"
+ let(:bootstrap_cli_options) { [ "--bootstrap-preinstall-command", command ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the preinstall command in the bootstrap template correctly" do
+ expect(rendered_template).to match(/command/)
+ end
+ end
+
+ context "with --bootstrap-proxy" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-proxy", "1.1.1.1" ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the https_proxy environment variable in the bootstrap template correctly" do
+ expect(rendered_template).to match(/https_proxy="1.1.1.1" export https_proxy/)
+ end
+ end
+
+ context "with --bootstrap-no-proxy" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-no-proxy", "localserver" ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the https_proxy environment variable in the bootstrap template correctly" do
+ expect(rendered_template).to match(/no_proxy="localserver" export no_proxy/)
+ end
+ end
+
+ context "with :bootstrap_template and :template_file cli options" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "other-template" ] }
+
+ it "should select bootstrap template" do
+ expect(File.basename(knife.bootstrap_template)).to eq("my-template")
+ end
+ end
+
+ context "when finding templates" do
+ context "when :bootstrap_template config is set to a file" do
+ context "that doesn't exist" do
+ let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" }
+
+ it "raises an error" do
+ expect { knife.find_template }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ context "that exists" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
+
+ it "loads the given file as the template" do
+ expect(Chef::Log).to receive(:trace)
+ expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")))
+ end
+ end
+ end
+
+ context "when :bootstrap_template config is set to a template name" do
+ let(:bootstrap_template) { "example" }
+
+ let(:builtin_template_path) { File.expand_path(File.join(__dir__, "../../../lib/chef/knife/bootstrap/templates", "example.erb")) }
+
+ let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" }
+
+ let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" }
+
+ let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" }
+
+ def configure_chef_config_dir
+ allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config")
+ end
+
+ def configure_env_home
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path)
+ end
+
+ def configure_gem_files
+ allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ])
+ end
+
+ before(:each) do
+ expect(File).to receive(:exist?).with(bootstrap_template).and_return(false)
+ end
+
+ context "when file is available everywhere" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(true)
+ end
+
+ it "should load the template from built-in templates" do
+ expect(knife.find_template).to eq(builtin_template_path)
+ end
+ end
+
+ context "when file is available in chef_config_dir" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(true)
+
+ it "should load the template from chef_config_dir" do
+ knife.find_template.should eq(chef_config_dir_template_path)
+ end
+ end
+ end
+
+ context "when file is available in home directory" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(env_home_template_path).and_return(true)
+ end
+
+ it "should load the template from chef_config_dir" do
+ expect(knife.find_template).to eq(env_home_template_path)
+ end
+ end
+
+ context "when file is available in Gem files" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(env_home_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
+ end
+
+ it "should load the template from Gem files" do
+ expect(knife.find_template).to eq(gem_files_template_path)
+ end
+ end
+
+ context "when file is available in Gem files and home dir doesn't exist" do
+ before do
+ configure_chef_config_dir
+ configure_gem_files
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil)
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
+ end
+
+ it "should load the template from Gem files" do
+ expect(knife.find_template).to eq(gem_files_template_path)
+ end
+ end
+ end
+ end
+
+ ["-t", "--bootstrap-template"].each do |t|
+ context "when #{t} option is given in the command line" do
+ it "sets the knife :bootstrap_template config" do
+ knife.parse_options([t, "blahblah"])
+ knife.merge_configs
+ expect(knife.bootstrap_template).to eq("blahblah")
+ end
+ end
+ end
+
+ context "with run_list template" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
+
+ it "should return an empty run_list" do
+ expect(knife.render_template).to eq('{"run_list":[]}')
+ end
+
+ it "should have role[base] in the run_list" do
+ knife.parse_options(["-r", "role[base]"])
+ knife.merge_configs
+ expect(knife.render_template).to eq('{"run_list":["role[base]"]}')
+ end
+
+ it "should have role[base] and recipe[cupcakes] in the run_list" do
+ knife.parse_options(["-r", "role[base],recipe[cupcakes]"])
+ knife.merge_configs
+ expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}')
+ end
+
+ context "with bootstrap_attribute options" do
+ let(:jsonfile) do
+ file = Tempfile.new(["node", ".json"])
+ File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' }
+ file
+ end
+
+ it "should have foo => {bar => baz} in the first_boot from cli" do
+ knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+ knife.merge_configs
+ expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+ actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+ expect(actual_hash).to eq(expected_hash)
+ end
+
+ it "should have foo => {bar => baz} in the first_boot from file" do
+ knife.parse_options(["--json-attribute-file", jsonfile.path])
+ knife.merge_configs
+ expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+ actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+ expect(actual_hash).to eq(expected_hash)
+ jsonfile.close
+ end
+
+ it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do
+ knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+ knife.parse_options(["--json-attribute-file", jsonfile.path])
+ knife.merge_configs
+ allow(knife).to receive(:validate_name_args!)
+ expect(knife).to receive(:check_license)
+
+ expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
+ jsonfile.close
+ end
+ end
+ end
+
+ context "with hints template" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) }
+
+ it "should create a hint file when told to" do
+ knife.parse_options(["--hint", "openstack"])
+ knife.merge_configs
+ expect(knife.render_template).to match(%r{/etc/chef/ohai/hints/openstack.json})
+ end
+
+ it "should populate a hint file with JSON when given a file to read" do
+ allow(::File).to receive(:read).and_return('{ "foo" : "bar" }')
+ knife.parse_options(["--hint", "openstack=hints/openstack.json"])
+ knife.merge_configs
+ expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/)
+ end
+ end
+
+ describe "specifying no_proxy with various entries" do
+ subject(:knife) do
+ k = described_class.new
+ Chef::Config[:knife][:bootstrap_template] = template_file
+ allow(k).to receive(:connection).and_return connection
+ k.parse_options(options)
+ k.merge_configs
+ k
+ end
+
+ let(:options) { ["--bootstrap-no-proxy", setting] }
+
+ let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
+
+ let(:rendered_template) do
+ knife.render_template
+ end
+
+ context "via --bootstrap-no-proxy" do
+ let(:setting) { "api.opscode.com" }
+
+ it "renders the client.rb with a single FQDN no_proxy entry" do
+ expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com".*/)
+ end
+ end
+
+ context "via --bootstrap-no-proxy multiple" do
+ let(:setting) { "api.opscode.com,172.16.10.*" }
+
+ it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
+ expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com,172.16.10.\*".*/)
+ end
+ end
+
+ context "via --ssl-verify-mode none" do
+ let(:options) { ["--node-ssl-verify-mode", "none"] }
+
+ it "renders the client.rb with ssl_verify_mode set to :verify_none" do
+ expect(rendered_template).to match(/ssl_verify_mode :verify_none/)
+ end
+ end
+
+ context "via --node-ssl-verify-mode peer" do
+ let(:options) { ["--node-ssl-verify-mode", "peer"] }
+
+ it "renders the client.rb with ssl_verify_mode set to :verify_peer" do
+ expect(rendered_template).to match(/ssl_verify_mode :verify_peer/)
+ end
+ end
+
+ context "via --node-ssl-verify-mode all" do
+ let(:options) { ["--node-ssl-verify-mode", "all"] }
+
+ it "raises error" do
+ expect { rendered_template }.to raise_error(RuntimeError)
+ end
+ end
+
+ context "via --node-verify-api-cert" do
+ let(:options) { ["--node-verify-api-cert"] }
+
+ it "renders the client.rb with verify_api_cert set to true" do
+ expect(rendered_template).to match(/verify_api_cert true/)
+ end
+ end
+
+ context "via --no-node-verify-api-cert" do
+ let(:options) { ["--no-node-verify-api-cert"] }
+
+ it "renders the client.rb with verify_api_cert set to false" do
+ expect(rendered_template).to match(/verify_api_cert false/)
+ end
+ end
+ end
+
+ describe "specifying the encrypted data bag secret key" do
+ let(:secret) { "supersekret" }
+ let(:options) { [] }
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
+ let(:rendered_template) do
+ knife.parse_options(options)
+ knife.merge_configs
+ knife.render_template
+ end
+
+ it "creates a secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(rendered_template).to match(/#{secret}/)
+ end
+
+ it "renders the client.rb with an encrypted_data_bag_secret entry" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
+ end
+
+ end
+
+ describe "when transferring trusted certificates" do
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ before do
+ Chef::Config[:trusted_certs_dir] = Chef::Util::PathHelper.cleanpath(File.join(CHEF_SPEC_DATA, "trusted_certs"))
+ end
+
+ it "creates /etc/chef/trusted_certs" do
+ expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs})
+ end
+
+ it "copies the certificates in the directory" do
+ certificates = Dir[File.join(Chef::Config[:trusted_certs_dir], "*.{crt,pem}")]
+
+ certificates.each do |cert|
+ expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'})
+ end
+ end
+
+ it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do
+ Dir.mktmpdir do |dir|
+ Chef::Config[:trusted_certs_dir] = dir
+ expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs})
+ end
+ end
+ end
+
+ context "when doing fips things" do
+ let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
+
+ before do
+ Chef::Config[:knife][:bootstrap_template] = template_file
+ knife.merge_configs
+ end
+
+ let(:rendered_template) do
+ knife.render_template
+ end
+
+ context "when knife is in fips mode" do
+ before do
+ Chef::Config[:fips] = true
+ end
+
+ it "renders 'fips true'" do
+ expect(rendered_template).to match("fips")
+ end
+ end
+
+ context "when knife is not in fips mode" do
+ before do
+ # This is required because the chef-fips pipeline does
+ # has a default value of true for fips
+ Chef::Config[:fips] = false
+ end
+
+ it "does not render anything about fips" do
+ expect(rendered_template).not_to match("fips")
+ end
+ end
+ end
+
+ describe "when transferring client.d" do
+
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ before do
+ Chef::Config[:client_d_dir] = client_d_dir
+ end
+
+ context "when client_d_dir is nil" do
+ let(:client_d_dir) { nil }
+
+ it "does not create /etc/chef/client.d" do
+ expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d})
+ end
+ end
+
+ context "when client_d_dir is set" do
+ let(:client_d_dir) do
+ Chef::Util::PathHelper.cleanpath(
+ File.join(__dir__, "../../data/client.d_00")
+ )
+ end
+
+ it "creates /etc/chef/client.d" do
+ expect(rendered_template).to match("mkdir -p /etc/chef/client\.d")
+ end
+
+ context "a flat directory structure" do
+ it "escapes single-quotes" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/02-strings.rb <<'EOP'")
+ expect(rendered_template).to match("something '\\\\''/foo/bar'\\\\''")
+ end
+
+ it "creates a file 00-foo.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'")
+ expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228")
+ end
+ it "creates a file bar" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'")
+ expect(rendered_template).to match("1 / 0")
+ end
+ end
+
+ context "a nested directory structure" do
+ let(:client_d_dir) do
+ Chef::Util::PathHelper.cleanpath(
+ File.join(__dir__, "../../data/client.d_01")
+ )
+ end
+ it "creates a file foo/bar.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'")
+ expect(rendered_template).to match("1 / 0")
+ end
+ end
+ end
+ end
+
+ describe "#connection_protocol" do
+ let(:host_descriptor) { "example.com" }
+ let(:config) { {} }
+ let(:knife_connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:config).and_return config
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ if knife_connection_protocol
+ Chef::Config[:knife][:connection_protocol] = knife_connection_protocol
+ knife.merge_configs
+ end
+ end
+
+ context "when protocol is part of the host argument" do
+ let(:host_descriptor) { "winrm://myhost" }
+
+ it "returns the value provided by the host argument" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+ end
+
+ context "when protocol is provided via the CLI flag" do
+ let(:config) { { connection_protocol: "winrm" } }
+ it "returns that value" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+
+ end
+ context "when protocol is provided via the host argument and the CLI flag" do
+ let(:host_descriptor) { "ssh://example.com" }
+ let(:config) { { connection_protocol: "winrm" } }
+
+ it "returns the value provided by the host argument" do
+ expect(knife.connection_protocol).to eq "ssh"
+ end
+ end
+
+ context "when no explicit protocol is provided" do
+ let(:config) { {} }
+ let(:host_descriptor) { "example.com" }
+ let(:knife_connection_protocol) { "winrm" }
+ it "falls back to knife config" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+ context "and there is no knife bootstrap_protocol" do
+ let(:knife_connection_protocol) { nil }
+ it "falls back to 'ssh'" do
+ expect(knife.connection_protocol).to eq "ssh"
+ end
+ end
+ end
+
+ end
+
+ describe "#validate_protocol!" do
+ let(:host_descriptor) { "example.com" }
+ let(:config) { {} }
+ let(:connection_protocol) { "ssh" }
+ before do
+ allow(knife).to receive(:config).and_return config
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ end
+
+ context "when protocol is provided both in the URL and via --protocol" do
+
+ context "and they do not match" do
+ let(:connection_protocol) { "ssh" }
+ let(:config) { { connection_protocol: "winrm" } }
+ it "outputs an error and exits" do
+ expect(knife.ui).to receive(:error)
+ expect { knife.validate_protocol! }.to raise_error SystemExit
+ end
+ end
+
+ context "and they do match" do
+ let(:connection_protocol) { "winrm" }
+ let(:config) { { connection_protocol: "winrm" } }
+ it "returns true" do
+ expect(knife.validate_protocol!).to eq true
+ end
+ end
+ end
+
+ context "and the protocol is supported" do
+
+ Chef::Knife::Bootstrap::SUPPORTED_CONNECTION_PROTOCOLS.each do |proto|
+ let(:connection_protocol) { proto }
+ it "returns true for #{proto}" do
+ expect(knife.validate_protocol!).to eq true
+ end
+ end
+ end
+
+ context "and the protocol is not supported" do
+ let(:connection_protocol) { "invalid" }
+ it "outputs an error and exits" do
+ expect(knife.ui).to receive(:error).with(/Unsupported protocol '#{connection_protocol}'/)
+ expect { knife.validate_protocol! }.to raise_error SystemExit
+ end
+ end
+ end
+
+ describe "#validate_policy_options!" do
+
+ context "when only policy_name is given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } }
+
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+ end
+
+ end
+
+ context "when only policy_group is given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-group staging } }
+
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+ end
+
+ end
+
+ context "when both policy_name and policy_group are given, but run list is also given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } }
+
+ it "returns an error stating that policyfile and run_list are exclusive" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive")
+ end
+
+ end
+
+ context "when policy_name and policy_group are given with no conflicting options" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } }
+
+ it "passes options validation" do
+ expect { knife.validate_policy_options! }.to_not raise_error
+ end
+
+ it "passes them into the bootstrap context" do
+ expect(knife.bootstrap_context.first_boot).to have_key(:policy_name)
+ expect(knife.bootstrap_context.first_boot).to have_key(:policy_group)
+ end
+
+ it "ensures that run_list is not set in the bootstrap context" do
+ expect(knife.bootstrap_context.first_boot).to_not have_key(:run_list)
+ end
+
+ end
+
+ # https://github.com/chef/chef/issues/4131
+ # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it
+ # worked before, so make it work now.
+ context "when a plugin sets the run list option to nil" do
+ before do
+ knife.config[:run_list] = nil
+ end
+
+ it "passes options validation" do
+ expect { knife.validate_policy_options! }.to_not raise_error
+ end
+ end
+ end
+
+ # TODO - this is the only cli option we validate the _option_ itself -
+ # so we'll know if someone accidentally deletes or renames use_sudo_password
+ # Is this worht keeping? If so, then it seems we should expand it
+ # to cover all options.
+ context "validating use_sudo_password option" do
+ it "use_sudo_password contains description and long params for help" do
+ expect(knife.options).to(have_key(:use_sudo_password)) \
+ && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\
+ && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq(""))
+ end
+ end
+
+ context "#connection_opts" do
+ let(:connection_protocol) { "ssh" }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+ context "behavioral test: " do
+ let(:expected_connection_opts) do
+ { base_opts: true,
+ ssh_identity_opts: true,
+ ssh_opts: true,
+ gateway_opts: true,
+ host_verify_opts: true,
+ sudo_opts: true,
+ winrm_opts: true }
+ end
+
+ it "queries and merges only expected configurations" do
+ expect(knife).to receive(:base_opts).and_return({ base_opts: true })
+ expect(knife).to receive(:host_verify_opts).and_return({ host_verify_opts: true })
+ expect(knife).to receive(:gateway_opts).and_return({ gateway_opts: true })
+ expect(knife).to receive(:sudo_opts).and_return({ sudo_opts: true })
+ expect(knife).to receive(:winrm_opts).and_return({ winrm_opts: true })
+ expect(knife).to receive(:ssh_opts).and_return({ ssh_opts: true })
+ expect(knife).to receive(:ssh_identity_opts).and_return({ ssh_identity_opts: true })
+ expect(knife.connection_opts).to match expected_connection_opts
+ end
+ end
+
+ context "functional test: " do
+ context "when protocol is winrm" do
+ let(:connection_protocol) { "winrm" }
+ # context "and neither CLI nor Chef::Config config entries have been provided"
+ # end
+ context "and all supported values are provided as Chef::Config entries" do
+ before do
+ # Set everything to easily identifiable and obviously fake values
+ # to verify that Chef::Config is being sourced instead of knife.config
+ knife.config = {}
+ Chef::Config[:knife][:max_wait] = 9999
+ Chef::Config[:knife][:winrm_user] = "winbob"
+ Chef::Config[:knife][:winrm_port] = 9999
+ Chef::Config[:knife][:ca_trust_file] = "trust.me"
+ Chef::Config[:knife][:kerberos_realm] = "realm"
+ Chef::Config[:knife][:kerberos_service] = "service"
+ Chef::Config[:knife][:winrm_auth_method] = "kerberos" # default is negotiate
+ Chef::Config[:knife][:winrm_basic_auth_only] = true
+ Chef::Config[:knife][:winrm_no_verify_cert] = true
+ Chef::Config[:knife][:session_timeout] = 9999
+ Chef::Config[:knife][:winrm_ssl] = true
+ Chef::Config[:knife][:winrm_ssl_peer_fingerprint] = "ABCDEF"
+ end
+
+ context "and no CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "trust.me",
+ max_wait_until_ready: 9999,
+ operation_timeout: 9999,
+ ssl_peer_fingerprint: "ABCDEF",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: true,
+ user: "winbob",
+ port: 9999,
+ self_signed: true,
+ ssl: true,
+ kerberos_realm: "realm",
+ kerberos_service: "service",
+ }
+ end
+
+ it "generates a config hash using the Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+
+ end
+
+ context "and some CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "no trust",
+ max_wait_until_ready: 9999,
+ operation_timeout: 9999,
+ ssl_peer_fingerprint: "ABCDEF",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: true,
+ user: "microsoftbob",
+ port: 12,
+ self_signed: true,
+ ssl: true,
+ kerberos_realm: "realm",
+ kerberos_service: "service",
+ password: "lobster",
+ }
+ end
+
+ before do
+ knife.config[:ca_trust_file] = "no trust"
+ knife.config[:connection_user] = "microsoftbob"
+ knife.config[:connection_port] = 12
+ knife.config[:winrm_port] = "13" # indirectly verify we're not looking for the wrong CLI flag
+ knife.config[:connection_password] = "lobster"
+ end
+
+ it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and all CLI options have been given" do
+ before do
+ # We'll force kerberos vi knife.config because it
+ # causes additional options to populate - make sure
+ # Chef::Config is different so we can be sure that we didn't
+ # pull in the Chef::Config value
+ Chef::Config[:knife][:winrm_auth_method] = "negotiate"
+ knife.config[:connection_password] = "blue"
+ knife.config[:max_wait] = 1000
+ knife.config[:connection_user] = "clippy"
+ knife.config[:connection_port] = 1000
+ knife.config[:winrm_port] = 1001 # We should not see this value get used
+
+ knife.config[:ca_trust_file] = "trust.the.internet"
+ knife.config[:kerberos_realm] = "otherrealm"
+ knife.config[:kerberos_service] = "otherservice"
+ knife.config[:winrm_auth_method] = "kerberos" # default is negotiate
+ knife.config[:winrm_basic_auth_only] = false
+ knife.config[:winrm_no_verify_cert] = false
+ knife.config[:session_timeout] = 1000
+ knife.config[:winrm_ssl] = false
+ knife.config[:winrm_ssl_peer_fingerprint] = "FEDCBA"
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "trust.the.internet",
+ max_wait_until_ready: 1000,
+ operation_timeout: 1000,
+ ssl_peer_fingerprint: "FEDCBA",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: false,
+ user: "clippy",
+ port: 1000,
+ self_signed: false,
+ ssl: false,
+ kerberos_realm: "otherrealm",
+ kerberos_service: "otherservice",
+ password: "blue",
+ }
+ end
+ it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end # with underlying Chef::Config values
+
+ context "and no values are provided from Chef::Config or CLI" do
+ before do
+ # We will use knife's actual config since these tests
+ # have assumptions based on CLI default values
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log,
+ operation_timeout: 60,
+ self_signed: false,
+ ssl: false,
+ ssl_peer_fingerprint: nil,
+ winrm_basic_auth_only: false,
+ winrm_transport: "negotiate",
+ }
+ end
+ it "populates appropriate defaults" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end # winrm
+
+ context "when protocol is ssh" do
+ let(:connection_protocol) { "ssh" }
+ # context "and neither CLI nor Chef::Config config entries have been provided"
+ # end
+ context "and all supported values are provided as Chef::Config entries" do
+ before do
+ # Set everything to easily identifiable and obviously fake values
+ # to verify that Chef::Config is being sourced instead of knife.config
+ knife.config = {}
+ Chef::Config[:knife][:max_wait] = 9999
+ Chef::Config[:knife][:session_timeout] = 9999
+ Chef::Config[:knife][:ssh_user] = "sshbob"
+ Chef::Config[:knife][:ssh_port] = 9999
+ Chef::Config[:knife][:host_key_verify] = false
+ Chef::Config[:knife][:ssh_gateway_identity] = "/gateway.pem"
+ Chef::Config[:knife][:ssh_gateway] = "admin@mygateway.local:1234"
+ Chef::Config[:knife][:ssh_identity_file] = "/identity.pem"
+ Chef::Config[:knife][:use_sudo_password] = false # We have no password.
+ end
+
+ context "and no CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 9999.0,
+ connection_timeout: 9999,
+ user: "sshbob",
+ bastion_host: "mygateway.local",
+ bastion_port: 1234,
+ bastion_user: "admin",
+ forward_agent: false,
+ keys_only: true,
+ key_files: ["/identity.pem", "/gateway.pem"],
+ sudo: false,
+ verify_host_key: "always",
+ port: 9999,
+ non_interactive: true,
+ }
+ end
+
+ it "generates a correct config hash using the Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and unsupported Chef::Config options are given in Chef::Config, not in CLI" do
+ before do
+ Chef::Config[:knife][:password] = "blah"
+ Chef::Config[:knife][:ssh_password] = "blah"
+ Chef::Config[:knife][:preserve_home] = true
+ Chef::Config[:knife][:use_sudo] = true
+ Chef::Config[:knife][:ssh_forward_agent] = "blah"
+ end
+ it "does not include the corresponding option in the connection options" do
+ knife.merge_configs
+ expect(knife.connection_opts.key?(:password)).to eq false
+ expect(knife.connection_opts.key?(:ssh_forward_agent)).to eq false
+ expect(knife.connection_opts.key?(:use_sudo)).to eq false
+ expect(knife.connection_opts.key?(:preserve_home)).to eq false
+ end
+ end
+
+ context "and some CLI options have been given" do
+ before do
+ knife.config = {}
+ knife.config[:connection_user] = "sshalice"
+ knife.config[:connection_port] = 12
+ knife.config[:ssh_port] = "13" # canary to indirectly verify we're not looking for the wrong CLI flag
+ knife.config[:connection_password] = "feta cheese"
+ knife.config[:max_wait] = 150
+ knife.config[:session_timeout] = 120
+ knife.config[:use_sudo] = true
+ knife.config[:use_sudo_pasword] = true
+ knife.config[:ssh_forward_agent] = true
+ end
+
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 150.0, # cli
+ connection_timeout: 120, # cli
+ user: "sshalice", # cli
+ password: "feta cheese", # cli
+ bastion_host: "mygateway.local", # Config
+ bastion_port: 1234, # Config
+ bastion_user: "admin", # Config
+ forward_agent: true, # cli
+ keys_only: false, # implied false from config password present
+ key_files: ["/identity.pem", "/gateway.pem"], # Config
+ sudo: true, # ccli
+ verify_host_key: "always", # Config
+ port: 12, # cli
+ non_interactive: true,
+ }
+ end
+
+ it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and all CLI options have been given" do
+ before do
+ knife.config = {}
+ knife.config[:max_wait] = 150
+ knife.config[:session_timeout] = 120
+ knife.config[:connection_user] = "sshroot"
+ knife.config[:connection_port] = 1000
+ knife.config[:connection_password] = "blah"
+ knife.config[:forward_agent] = true
+ knife.config[:use_sudo] = true
+ knife.config[:use_sudo_password] = true
+ knife.config[:preserve_home] = true
+ knife.config[:use_sudo_pasword] = true
+ knife.config[:ssh_forward_agent] = true
+ knife.config[:ssh_verify_host_key] = true
+ knife.config[:ssh_gateway_identity] = "/gateway-identity.pem"
+ knife.config[:ssh_gateway] = "me@example.com:10"
+ knife.config[:ssh_identity_file] = "/my-identity.pem"
+
+ # We'll set these as canaries - if one of these values shows up
+ # in a failed test, then the behavior of not pulling from these keys
+ # out of knife.config is broken:
+ knife.config[:ssh_user] = "do not use"
+ knife.config[:ssh_port] = 1001
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 150,
+ connection_timeout: 120,
+ user: "sshroot",
+ password: "blah",
+ port: 1000,
+ bastion_host: "example.com",
+ bastion_port: 10,
+ bastion_user: "me",
+ forward_agent: true,
+ keys_only: false,
+ key_files: ["/my-identity.pem", "/gateway-identity.pem"],
+ sudo: true,
+ sudo_options: "-H",
+ sudo_password: "blah",
+ verify_host_key: true,
+ non_interactive: true,
+ }
+ end
+ it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end
+ context "and no values are provided from Chef::Config or CLI" do
+ before do
+ # We will use knife's actual config since these tests
+ # have assumptions based on CLI default values
+ config = {}
+ end
+
+ let(:expected_result) do
+ {
+ forward_agent: false,
+ key_files: [],
+ logger: Chef::Log,
+ keys_only: false,
+ sudo: false,
+ verify_host_key: "always",
+ non_interactive: true,
+ connection_timeout: 60,
+ }
+ end
+ it "populates appropriate defaults" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ end # ssh
+ end # functional tests
+
+ end # connection_opts
+
+ context "#base_opts" do
+ let(:connection_protocol) { nil }
+
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for all protocols" do
+ context "when password is provided" do
+ before do
+ knife.config[:connection_port] = 250
+ knife.config[:connection_user] = "test"
+ knife.config[:connection_password] = "opscode"
+ end
+
+ let(:expected_opts) do
+ {
+ port: 250,
+ user: "test",
+ logger: Chef::Log,
+ password: "opscode",
+ }
+ end
+ it "generates the correct options" do
+ expect(knife.base_opts).to eq expected_opts
+ end
+
+ end
+
+ context "when password is not provided" do
+ before do
+ knife.config[:connection_port] = 250
+ knife.config[:connection_user] = "test"
+ end
+
+ let(:expected_opts) do
+ {
+ port: 250,
+ user: "test",
+ logger: Chef::Log,
+ }
+ end
+ it "generates the correct options" do
+ expect(knife.base_opts).to eq expected_opts
+ end
+ end
+ end
+ end
+
+ context "#host_verify_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns the expected configuration" do
+ knife.config[:winrm_no_verify_cert] = true
+ expect(knife.host_verify_opts).to eq( { self_signed: true } )
+ end
+ it "provides a correct default when no option given" do
+ expect(knife.host_verify_opts).to eq( { self_signed: false } )
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns the expected configuration" do
+ knife.config[:ssh_verify_host_key] = false
+ expect(knife.host_verify_opts).to eq( { verify_host_key: false } )
+ end
+ it "provides a correct default when no option given" do
+ expect(knife.host_verify_opts).to eq( { verify_host_key: "always" } )
+ end
+ end
+ end
+
+ # TODO - test keys_only, password, config source behavior
+ context "#ssh_identity_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.ssh_identity_opts).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "when an identity file is specified" do
+ before do
+ knife.config[:ssh_identity_file] = "/identity.pem"
+ end
+ it "generates the expected configuration" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: true,
+ })
+ end
+ context "and a password is also specified" do
+ before do
+ knife.config[:connection_password] = "blah"
+ end
+ it "generates the expected configuration (key, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: false,
+ })
+ end
+ end
+
+ context "and a gateway is not specified" do
+ context "but a gateway identity file is specified" do
+ it "does not include the gateway identity file in keys" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: ["/identity.pem"],
+ keys_only: true,
+ })
+ end
+
+ end
+
+ end
+
+ context "and a gatway is specified" do
+ before do
+ knife.config[:ssh_gateway] = "example.com"
+ end
+ context "and a gateway identity file is not specified" do
+ it "config includes only identity file and not gateway identity" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: true,
+ })
+ end
+ end
+
+ context "and a gateway identity file is also specified" do
+ before do
+ knife.config[:ssh_gateway_identity] = "/gateway.pem"
+ end
+
+ it "generates the expected configuration (both keys, keys_only true)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem", "/gateway.pem" ],
+ keys_only: true,
+ })
+ end
+ end
+ end
+ end
+
+ context "when no identity file is specified" do
+ it "generates the expected configuration (no keys, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq( {
+ key_files: [ ],
+ keys_only: false,
+ })
+ end
+ context "and a gateway with gateway identity file is specified" do
+ before do
+ knife.config[:ssh_gateway] = "host"
+ knife.config[:ssh_gateway_identity] = "/gateway.pem"
+ end
+
+ it "generates the expected configuration (gateway key, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/gateway.pem" ],
+ keys_only: false,
+ })
+ end
+ end
+ end
+ end
+ end
+
+ context "#gateway_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.gateway_opts).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "and ssh_gateway with hostname, user and port provided" do
+ before do
+ knife.config[:ssh_gateway] = "testuser@gateway:9021"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: "testuser",
+ bastion_host: "gateway",
+ bastion_port: 9021,
+ })
+ end
+ end
+ context "and ssh_gateway with only hostname is given" do
+ before do
+ knife.config[:ssh_gateway] = "gateway"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: nil,
+ bastion_host: "gateway",
+ bastion_port: nil,
+ })
+ end
+ end
+ context "and ssh_gateway with hostname and user is is given" do
+ before do
+ knife.config[:ssh_gateway] = "testuser@gateway"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: "testuser",
+ bastion_host: "gateway",
+ bastion_port: nil,
+ })
+ end
+ end
+
+ context "and ssh_gateway with hostname and port is is given" do
+ before do
+ knife.config[:ssh_gateway] = "gateway:11234"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: nil,
+ bastion_host: "gateway",
+ bastion_port: 11234,
+ })
+ end
+ end
+
+ context "and ssh_gateway is not provided" do
+ it "returns an empty hash" do
+ expect(knife.gateway_opts).to eq({})
+ end
+ end
+ end
+ end
+
+ context "#sudo_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.sudo_opts).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "when use_sudo is set" do
+ before do
+ knife.config[:use_sudo] = true
+ end
+
+ it "returns a config that enables sudo" do
+ expect(knife.sudo_opts).to eq( { sudo: true } )
+ end
+
+ context "when use_sudo_password is also set" do
+ before do
+ knife.config[:use_sudo_password] = true
+ knife.config[:connection_password] = "opscode"
+ end
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts).to eq({
+ sudo: true,
+ sudo_password: "opscode",
+ })
+ end
+ end
+
+ context "when preserve_home is set" do
+ before do
+ knife.config[:preserve_home] = true
+ end
+ it "enables sudo with sudo_option to preserve home" do
+ expect(knife.sudo_opts).to eq({
+ sudo_options: "-H",
+ sudo: true,
+ })
+ end
+ end
+ end
+
+ context "when use_sudo is not set" do
+ before do
+ knife.config[:use_sudo_password] = true
+ knife.config[:preserve_home] = true
+ end
+ it "returns configuration for sudo off, ignoring other related options" do
+ expect(knife.sudo_opts).to eq( { sudo: false } )
+ end
+ end
+ end
+ end
+
+ context "#ssh_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ let(:default_opts) do
+ {
+ non_interactive: true,
+ forward_agent: false,
+ connection_timeout: 60,
+ }
+ end
+
+ context "by default" do
+ it "returns a configuration hash with appropriate defaults" do
+ expect(knife.ssh_opts).to eq default_opts
+ end
+ end
+
+ context "when ssh_forward_agent has a value" do
+ before do
+ knife.config[:ssh_forward_agent] = true
+ end
+ it "returns a default configuration hash with forward_agent set to true" do
+ expect(knife.ssh_opts).to eq(default_opts.merge(forward_agent: true))
+ end
+ end
+ context "when session_timeout has a value" do
+ before do
+ knife.config[:session_timeout] = 120
+ end
+ it "returns a default configuration hash with updated timeout value." do
+ expect(knife.ssh_opts).to eq(default_opts.merge(connection_timeout: 120))
+ end
+ end
+
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty has because ssh is not winrm" do
+ expect(knife.ssh_opts).to eq({})
+ end
+ end
+
+ end
+
+ context "#winrm_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ let(:expected) do
+ {
+ winrm_transport: "negotiate",
+ winrm_basic_auth_only: false,
+ ssl: false,
+ ssl_peer_fingerprint: nil,
+ operation_timeout: 60,
+ }
+ end
+
+ it "generates a correct configuration hash with expected defaults" do
+ expect(knife.winrm_opts).to eq expected
+ end
+
+ context "with ssl_peer_fingerprint" do
+ let(:ssl_peer_fingerprint_expected) do
+ expected.merge({ ssl_peer_fingerprint: "ABCD" })
+ end
+
+ before do
+ knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
+ end
+
+ it "generates a correct options hash with ssl_peer_fingerprint from the config provided" do
+ expect(knife.winrm_opts).to eq ssl_peer_fingerprint_expected
+ end
+ end
+
+ context "with winrm_ssl" do
+ let(:ssl_expected) do
+ expected.merge({ ssl: true })
+ end
+ before do
+ knife.config[:winrm_ssl] = true
+ end
+
+ it "generates a correct options hash with ssl from the config provided" do
+ expect(knife.winrm_opts).to eq ssl_expected
+ end
+ end
+
+ context "with winrm_auth_method" do
+ let(:winrm_auth_method_expected) do
+ expected.merge({ winrm_transport: "freeaccess" })
+ end
+
+ before do
+ knife.config[:winrm_auth_method] = "freeaccess"
+ end
+
+ it "generates a correct options hash with winrm_transport from the config provided" do
+ expect(knife.winrm_opts).to eq winrm_auth_method_expected
+ end
+ end
+
+ context "with ca_trust_file" do
+ let(:ca_trust_expected) do
+ expected.merge({ ca_trust_path: "/trust.me" })
+ end
+ before do
+ knife.config[:ca_trust_file] = "/trust.me"
+ end
+
+ it "generates a correct options hash with ca_trust_file from the config provided" do
+ expect(knife.winrm_opts).to eq ca_trust_expected
+ end
+ end
+
+ context "with kerberos auth" do
+ let(:kerberos_expected) do
+ expected.merge({
+ kerberos_service: "testsvc",
+ kerberos_realm: "TESTREALM",
+ winrm_transport: "kerberos",
+ })
+ end
+
+ before do
+ knife.config[:winrm_auth_method] = "kerberos"
+ knife.config[:kerberos_service] = "testsvc"
+ knife.config[:kerberos_realm] = "TESTREALM"
+ end
+
+ it "generates a correct options hash containing kerberos auth configuration from the config provided" do
+ expect(knife.winrm_opts).to eq kerberos_expected
+ end
+ end
+
+ context "with winrm_basic_auth_only" do
+ before do
+ knife.config[:winrm_basic_auth_only] = true
+ end
+ let(:basic_auth_expected) do
+ expected.merge( { winrm_basic_auth_only: true } )
+ end
+ it "generates a correct options hash containing winrm_basic_auth_only from the config provided" do
+ expect(knife.winrm_opts).to eq basic_auth_expected
+ end
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns an empty hash because ssh is not winrm" do
+ expect(knife.winrm_opts).to eq({})
+ end
+ end
+ end
+ describe "#run" do
+ it "performs the steps we expect to run a bootstrap" do
+ expect(knife).to receive(:check_license)
+ expect(knife).to receive(:validate_name_args!).ordered
+ expect(knife).to receive(:validate_protocol!).ordered
+ expect(knife).to receive(:validate_first_boot_attributes!).ordered
+ expect(knife).to receive(:validate_winrm_transport_opts!).ordered
+ expect(knife).to receive(:validate_policy_options!).ordered
+ expect(knife).to receive(:winrm_warn_no_ssl_verification).ordered
+ expect(knife).to receive(:warn_on_short_session_timeout).ordered
+ expect(knife).to receive(:connect!).ordered
+ expect(knife).to receive(:register_client).ordered
+ expect(knife).to receive(:render_template).and_return "content"
+ expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh"
+ expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh")
+ expect(connection).to receive(:del_file!) # Make sure cleanup happens
+
+ knife.run
+
+ # Post-run verify expected state changes (not many directly in #run)
+ expect($stdout.sync).to eq true
+ end
+ end
+
+ describe "#register_client" do
+ let(:vault_handler_mock) { double("ChefVaultHandler") }
+ let(:client_builder_mock) { double("ClientBuilder") }
+ let(:node_name) { nil }
+ before do
+ allow(knife).to receive(:chef_vault_handler).and_return vault_handler_mock
+ allow(knife).to receive(:client_builder).and_return client_builder_mock
+ knife.config[:chef_node_name] = node_name
+ end
+
+ shared_examples_for "creating the client locally" do
+ context "when a valid node name is present" do
+ let(:node_name) { "test" }
+ before do
+ allow(client_builder_mock).to receive(:client).and_return "client"
+ allow(client_builder_mock).to receive(:client_path).and_return "/key.pem"
+ end
+
+ it "runs client_builder and vault_handler" do
+ expect(client_builder_mock).to receive(:run)
+ expect(vault_handler_mock).to receive(:run).with("client")
+ knife.register_client
+ end
+
+ it "sets the path to the client key in the bootstrap context" do
+ allow(client_builder_mock).to receive(:run)
+ allow(vault_handler_mock).to receive(:run).with("client")
+ knife.register_client
+ expect(knife.bootstrap_context.client_pem).to eq "/key.pem"
+ end
+ end
+
+ context "when no valid node name is present" do
+ let(:node_name) { nil }
+ it "shows an error and exits" do
+ expect(knife.ui).to receive(:error)
+ expect { knife.register_client }.to raise_error(SystemExit)
+ end
+ end
+ end
+ context "when chef_vault_handler says we're using vault" do
+ let(:vault_handler_mock) { double("ChefVaultHandler") }
+ before do
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return true
+ end
+ it_behaves_like "creating the client locally"
+ end
+
+ context "when an non-existant validation key is specified in chef config" do
+ before do
+ Chef::Config[:validation_key] = "/blah"
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
+ allow(File).to receive(:exist?).with(%r{/blah}).and_return false
+ end
+ it_behaves_like "creating the client locally"
+ end
+
+ context "when a valid validation key is given and we're doing old-style client creation" do
+ before do
+ Chef::Config[:validation_key] = "/blah"
+ allow(File).to receive(:exist?).with(%r{/blah}).and_return true
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
+ end
+
+ it "shows a warning message" do
+ expect(knife.ui).to receive(:warn).twice
+ knife.register_client
+ end
+ end
+ end
+
+ describe "#perform_bootstrap" do
+ let(:exit_status) { 0 }
+ let(:stdout) { "" }
+ let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message", stdout: stdout) }
+
+ before do
+ allow(connection).to receive(:hostname).and_return "testhost"
+ end
+ it "runs the remote script and logs the output" do
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("sh /path.sh")
+ expect(connection)
+ .to receive(:run_command)
+ .with("sh /path.sh")
+ .and_yield("output here", nil)
+ .and_return result_mock
+
+ expect(knife.ui).to receive(:msg).with(/testhost/)
+ knife.perform_bootstrap("/path.sh")
+ end
+
+ context "when the remote command fails" do
+ let(:exit_status) { 1 }
+ it "shows an error and exits" do
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("sh /path.sh")
+ expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock
+ expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit)
+ end
+ end
+
+ context "when the remote command failed due to su auth error" do
+ let(:exit_status) { 1 }
+ let(:stdout) { "su: Authentication failure" }
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ it "shows an error and exits" do
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("su - USER -c 'sh /path.sh'")
+ expect(connection)
+ .to receive(:run_command)
+ .with("su - USER -c 'sh /path.sh'")
+ .and_yield("output here", nil)
+ .and_raise(Train::UserError)
+ expect { knife.perform_bootstrap("/path.sh") }.to raise_error(Train::UserError)
+ end
+ end
+ end
+
+ describe "#connect!" do
+ before do
+ # These are not required at run-time because train will handle its own
+ # protocol loading. In this case, we're simulating train failures and have to load
+ # them ourselves.
+ require "net/ssh"
+ require "train/transports/ssh"
+ end
+
+ context "in the normal case" do
+ it "connects using the connection_opts and notifies the operator of progress" do
+ expect(knife.ui).to receive(:info).with(/Connecting to.*/)
+ expect(knife).to receive(:connection_opts).and_return( { opts: "here" })
+ expect(knife).to receive(:do_connect).with( { opts: "here" } )
+ knife.connect!
+ end
+ end
+
+ context "when a general non-auth-failure occurs" do
+ let(:expected_error) { RuntimeError.new }
+ before do
+ allow(knife).to receive(:do_connect).and_raise(expected_error)
+ end
+ it "re-raises the exception" do
+ expect { knife.connect! }.to raise_error(expected_error)
+ end
+ end
+
+ context "when ssh fingerprint is invalid" do
+ let(:expected_error) { Train::Error.new("fingerprint AA:BB is unknown for \"blah,127.0.0.1\"") }
+ before do
+ allow(knife).to receive(:do_connect).and_raise(expected_error)
+ end
+ it "warns, prompts to accept, then connects with verify_host_key of accept_new" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error)
+ expect(knife.ui).to receive(:confirm)
+ .with(/.*host 'blah \(127.0.0.1\)'.*AA:BB.*Are you sure you want to continue.*/m)
+ .and_return(true)
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:verify_host_key]).to eq :accept_new
+ end
+ knife.connect!
+ end
+ end
+
+ context "when an auth failure occurs" do
+ let(:expected_error) do
+ e = Train::Error.new
+ actual = Net::SSH::AuthenticationFailed.new
+ # Simulate train's nested error - they wrap
+ # ssh/network errors in a TrainError.
+ allow(e).to receive(:cause).and_return(actual)
+ e
+ end
+
+ let(:expected_error_password_prompt) do
+ e = Train::ClientError.new
+ reason = :no_ssh_password_or_key_available
+ allow(e).to receive(:reason).and_return(reason)
+ e
+ end
+
+ let(:expected_error_password_prompt_winrm) do
+ e = RuntimeError.new
+ message = "password is a required option"
+ allow(e).to receive(:message).and_return(message)
+ e
+ end
+
+ context "and password auth was used" do
+ before do
+ allow(connection).to receive(:password_auth?).and_return true
+ end
+
+ it "re-raises the error so as not to resubmit the same failing password" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error)
+ expect { knife.connect! }.to raise_error(expected_error)
+ end
+ end
+
+ context "and password auth was not used" do
+ before do
+ allow(connection).to receive(:password_auth?).and_return false
+ allow(connection).to receive(:user).and_return "testuser"
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+
+ it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt)
+ expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
+ expect(knife.ui).to receive(:ask).and_return("newpassword")
+ # Ensure that we set echo off to prevent showing password on the screen
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:password]).to eq "newpassword"
+ end
+ knife.connect!
+ end
+ end
+
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+
+ it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password for" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt_winrm)
+ expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
+ expect(knife.ui).to receive(:ask).and_return("newpassword")
+ # Ensure that we set echo off to prevent showing password on the screen
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:password]).to eq "newpassword"
+ end
+ knife.connect!
+ end
+ end
+ end
+ end
+ end
+
+ it "verifies that a server to bootstrap was given as a command line arg" do
+ knife.name_args = nil
+ expect(knife).to receive(:check_license)
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
+ end
+
+ describe "#bootstrap_context" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "creates a WindowsBootstrapContext" do
+ require "chef/knife/core/windows_bootstrap_context"
+ expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext
+ end
+ end
+
+ context "under linux" do
+ let(:linux_test) { true }
+ it "creates a BootstrapContext" do
+ require "chef/knife/core/bootstrap_context"
+ expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext
+ end
+ end
+ end
+
+ describe "#config_value" do
+ before do
+ knife.config[:test_key_a] = "a from cli"
+ knife.config[:test_key_b] = "b from cli"
+ Chef::Config[:knife][:test_key_a] = "a from Chef::Config"
+ Chef::Config[:knife][:test_key_c] = "c from Chef::Config"
+ Chef::Config[:knife][:alt_test_key_c] = "alt c from Chef::Config"
+ knife.merge_configs
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ it "returns the Chef::Config value from the cli when the CLI key is set" do
+ expect(knife.config_value(:test_key_a, :alt_test_key_c)).to eq "a from cli"
+ end
+
+ it "returns the Chef::Config value from the alternative key when the CLI key is not set" do
+ expect(knife.config_value(:test_key_d, :alt_test_key_c)).to eq "alt c from Chef::Config"
+ end
+
+ it "returns the default value when the key is not provided by CLI or Chef::Config" do
+ expect(knife.config_value(:missing_key, :missing_key, "found")).to eq "found"
+ end
+ end
+
+ describe "#upload_bootstrap" do
+ before do
+ allow(connection).to receive(:temp_dir).and_return(temp_dir)
+ allow(connection).to receive(:normalize_path) { |a| a }
+ end
+
+ let(:content) { "bootstrap script content" }
+ context "under Windows" do
+ let(:windows_test) { true }
+ let(:temp_dir) { "C:/Temp/bootstrap" }
+ it "creates a bat file in the temp dir provided by connection, using given content" do
+ expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat")
+ expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat"
+ end
+ end
+
+ context "under Linux" do
+ let(:linux_test) { true }
+ let(:temp_dir) { "/tmp/bootstrap" }
+ it "creates a 'sh file in the temp dir provided by connection, using given content" do
+ expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh")
+ expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh"
+ end
+ end
+ end
+
+ describe "#bootstrap_command" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "prefixes the command to run under cmd.exe" do
+ expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat"
+ end
+
+ end
+ context "under Linux" do
+ let(:linux_test) { true }
+ it "prefixes the command to run under sh" do
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sh bootstrap.sh"
+ end
+
+ context "with --su-user option" do
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ before do
+ knife.config[:su_user] = "root"
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ end
+ it "prefixes the command to run using su -USER -c" do
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
+
+ it "sudo appended if --sudo option enabled" do
+ knife.config[:use_sudo] = true
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sudo su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
+ end
+ end
+ end
+
+ describe "#default_bootstrap_template" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "is windows-chef-client-msi" do
+ expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi"
+ end
+
+ end
+ context "under Linux" do
+ let(:linux_test) { true }
+ it "is chef-full" do
+ expect(knife.default_bootstrap_template).to eq "chef-full"
+ end
+ end
+ end
+
+ describe "#do_connect" do
+ let(:host_descriptor) { "example.com" }
+ let(:connection) { double("TrainConnector") }
+ let(:connector_mock) { double("TargetResolver", targets: [ connection ]) }
+ before do
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ end
+
+ it "creates a TrainConnector and connects it" do
+ expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection
+ expect(connection).to receive(:connect!)
+ knife.do_connect({})
+ end
+
+ context "when sshd configured with requiretty" do
+ let(:pty_err_msg) { "Sudo requires a TTY. Please see the README on how to configure sudo to allow for non-interactive usage." }
+ let(:expected_error) { Train::UserError.new(pty_err_msg, :sudo_no_tty) }
+ before do
+ allow(connection).to receive(:connect!).and_raise(expected_error)
+ end
+ it "retry with pty true request option" do
+ expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return(connection).exactly(2).times
+ expect(knife.ui).to receive(:warn).with("#{pty_err_msg} - trying with pty request")
+ expect { knife.do_connect({}) }.to raise_error(expected_error)
+ end
+ end
+ end
+
+ describe "validate_winrm_transport_opts!" do
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns true" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+ context "with plaintext auth" do
+ before do
+ knife.config[:winrm_auth_method] = "plaintext"
+ end
+ context "with ssl" do
+ before do
+ knife.config[:winrm_ssl] = true
+ end
+ it "will not error because we won't send anything in plaintext regardless" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "without ssl" do
+ before do
+ knife.config[:winrm_ssl] = false
+ end
+ context "and no validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
+ end
+
+ it "will error because we will generate and send a client key over the wire in plaintext" do
+ expect { knife.validate_winrm_transport_opts! }.to raise_error(SystemExit)
+ end
+
+ end
+ context "and a validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
+ end
+ # TODO - don't we still send validation key?
+ it "will not error because we don not send client key over the wire" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ end
+ end
+
+ context "with other auth" do
+ before do
+ knife.config[:winrm_auth_method] = "kerberos"
+ end
+
+ context "and no validation key exists" do
+ before do
+
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
+ end
+
+ it "will not error because we're not using plaintext auth" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "and a validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
+ end
+
+ it "will not error because a client key won't be sent over the wire in plaintext when a validation key is present" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+
+ end
+
+ end
+
+ end
+
+ describe "#winrm_warn_no_ssl_verification" do
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+ context "winrm_no_verify_cert is set" do
+ before do
+ knife.config[:winrm_no_verify_cert] = true
+ end
+
+ context "and ca_trust_file is present" do
+ before do
+ knife.config[:ca_trust_file] = "file"
+ end
+
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+
+ context "and winrm_ssl_peer_fingerprint is present" do
+ before do
+ knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
+ end
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ context "and neither ca_trust_file nor winrm_ssl_peer_fingerprint is present" do
+ it "issues a warning" do
+ expect(knife.ui).to receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ end
+ end
+ end
+
+ describe "#warn_on_short_session_timeout" do
+ let(:session_timeout) { 60 }
+
+ before do
+ allow(knife).to receive(:session_timeout).and_return(session_timeout)
+ end
+
+ context "timeout is not set at all" do
+ let(:session_timeout) { nil }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+
+ context "timeout is more than 15" do
+ let(:session_timeout) { 16 }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+ context "timeout is 15 or less" do
+ let(:session_timeout) { 15 }
+ it "issues a warning" do
+ expect(knife.ui).to receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_bulk_delete_spec.rb b/knife/spec/unit/knife/client_bulk_delete_spec.rb
new file mode 100644
index 0000000000..86d69ff4d6
--- /dev/null
+++ b/knife/spec/unit/knife/client_bulk_delete_spec.rb
@@ -0,0 +1,166 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::ClientBulkDelete do
+ let(:stdout_io) { StringIO.new }
+ let(:stdout) { stdout_io.string }
+ let(:stderr_io) { StringIO.new }
+ let(:stderr) { stderr_io.string }
+
+ let(:knife) do
+ k = Chef::Knife::ClientBulkDelete.new
+ k.name_args = name_args
+ k.config = option_args
+ allow(k.ui).to receive(:stdout).and_return(stdout_io)
+ allow(k.ui).to receive(:stderr).and_return(stderr_io)
+ allow(k.ui).to receive(:confirm).and_return(knife_confirm)
+ allow(k.ui).to receive(:confirm_without_exit).and_return(knife_confirm)
+ k
+ end
+
+ let(:name_args) { [ "." ] }
+ let(:option_args) { {} }
+
+ let(:knife_confirm) { true }
+
+ let(:nonvalidator_client_names) { %w{tim dan stephen} }
+ let(:nonvalidator_clients) do
+ clients = {}
+
+ nonvalidator_client_names.each do |client_name|
+ client = Chef::ApiClientV1.new
+ client.name(client_name)
+ allow(client).to receive(:destroy).and_return(true)
+ clients[client_name] = client
+ end
+
+ clients
+ end
+
+ let(:validator_client_names) { %w{myorg-validator} }
+ let(:validator_clients) do
+ clients = {}
+
+ validator_client_names.each do |validator_client_name|
+ validator_client = Chef::ApiClientV1.new
+ validator_client.name(validator_client_name)
+ allow(validator_client).to receive(:validator).and_return(true)
+ allow(validator_client).to receive(:destroy).and_return(true)
+ clients[validator_client_name] = validator_client
+ end
+
+ clients
+ end
+
+ let(:client_names) { nonvalidator_client_names + validator_client_names }
+ let(:clients) do
+ nonvalidator_clients.merge(validator_clients)
+ end
+
+ before(:each) do
+ allow(Chef::ApiClientV1).to receive(:list).and_return(clients)
+ end
+
+ describe "run" do
+ describe "without a regex" do
+ let(:name_args) { [ ] }
+
+ it "should exit if the regex is not provided" do
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with any clients" do
+ it "should get the list of the clients" do
+ expect(Chef::ApiClientV1).to receive(:list)
+ knife.run
+ end
+
+ it "should print the name of the clients" do
+ knife.run
+ client_names.each do |client_name|
+ expect(stdout).to include(client_name)
+ end
+ end
+
+ it "should confirm you really want to delete them" do
+ expect(knife.ui).to receive(:confirm)
+ knife.run
+ end
+
+ describe "without --delete-validators" do
+ it "should mention that validator clients wont be deleted" do
+ knife.run
+ expect(stdout).to include("The following clients are validators and will not be deleted:")
+ info = stdout.index "The following clients are validators and will not be deleted:"
+ val = stdout.index "myorg-validator"
+ expect(val > info).to be_truthy
+ end
+
+ it "should only delete nonvalidator clients" do
+ nonvalidator_clients.each_value do |c|
+ expect(c).to receive(:destroy)
+ end
+
+ validator_clients.each_value do |c|
+ expect(c).not_to receive(:destroy)
+ end
+
+ knife.run
+ end
+ end
+
+ describe "with --delete-validators" do
+ let(:option_args) { { delete_validators: true } }
+
+ it "should mention that validator clients will be deleted" do
+ knife.run
+ expect(stdout).to include("The following validators will be deleted")
+ end
+
+ it "should confirm twice" do
+ expect(knife.ui).to receive(:confirm).once
+ expect(knife.ui).to receive(:confirm_without_exit).once
+ knife.run
+ end
+
+ it "should delete all clients" do
+ clients.each_value do |c|
+ expect(c).to receive(:destroy)
+ end
+
+ knife.run
+ end
+ end
+ end
+
+ describe "with some clients" do
+ let(:name_args) { [ "^ti" ] }
+
+ it "should only delete clients that match the regex" do
+ expect(clients["tim"]).to receive(:destroy)
+ expect(clients["stephen"]).not_to receive(:destroy)
+ expect(clients["dan"]).not_to receive(:destroy)
+ expect(clients["myorg-validator"]).not_to receive(:destroy)
+ knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_create_spec.rb b/knife/spec/unit/knife/client_create_spec.rb
new file mode 100644
index 0000000000..48a7e71df5
--- /dev/null
+++ b/knife/spec/unit/knife/client_create_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::ClientCreate.load_deps
+
+describe Chef::Knife::ClientCreate do
+ let(:stderr) { StringIO.new }
+ let(:stdout) { StringIO.new }
+
+ let(:default_client_hash) do
+ {
+ "name" => "adam",
+ "validator" => false,
+ }
+ end
+
+ let(:client) do
+ Chef::ApiClientV1.new
+ end
+
+ let(:knife) do
+ k = Chef::Knife::ClientCreate.new
+ k.name_args = []
+ allow(k).to receive(:client).and_return(client)
+ allow(k).to receive(:edit_hash).with(client).and_return(client)
+ allow(k.ui).to receive(:stderr).and_return(stderr)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ before do
+ allow(client).to receive(:to_s).and_return("client[adam]")
+ allow(knife).to receive(:create_client).and_return(client)
+ end
+
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ end
+
+ describe "run" do
+ context "when nothing is passed" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { "client name" }
+ end
+ end
+
+ context "when clientname is passed" do
+ before do
+ knife.name_args = ["adam"]
+ end
+
+ context "when public_key and prevent_keygen are passed" do
+ before do
+ knife.config[:public_key] = "some_key"
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/You cannot pass --public-key and --prevent-keygen/)
+ end
+ end
+
+ it "should create the ApiClient" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ end
+
+ it "should print a message upon creation" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ expect(stderr.string).to match(/Created client.*adam/i)
+ end
+
+ it "should set the Client name" do
+ knife.run
+ expect(client.name).to eq("adam")
+ end
+
+ it "by default it is not a validator" do
+ knife.run
+ expect(client.validator).to be_falsey
+ end
+
+ it "by default it should set create_key to true" do
+ knife.run
+ expect(client.create_key).to be_truthy
+ end
+
+ it "should allow you to edit the data" do
+ expect(knife).to receive(:edit_hash).with(client).and_return(client)
+ knife.run
+ end
+
+ describe "with -f or --file" do
+ before do
+ client.private_key "woot"
+ end
+
+ it "should write the private key to a file" do
+ knife.config[:file] = "/tmp/monkeypants"
+ filehandle = double("Filehandle")
+ expect(filehandle).to receive(:print).with("woot")
+ expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ knife.run
+ end
+ end
+
+ describe "with -p or --public-key" do
+ before do
+ knife.config[:public_key] = "some_key"
+ allow(File).to receive(:read).and_return("some_key")
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets the public key" do
+ knife.run
+ expect(client.public_key).to eq("some_key")
+ end
+ end
+
+ describe "with -k or --prevent-keygen" do
+ before do
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "does not set create_key" do
+ knife.run
+ expect(client.create_key).to be_falsey
+ end
+ end
+
+ describe "with --validator" do
+ before do
+ knife.config[:validator] = true
+ end
+
+ it "should create an validator client" do
+ knife.run
+ expect(client.validator).to be_truthy
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_delete_spec.rb b/knife/spec/unit/knife/client_delete_spec.rb
new file mode 100644
index 0000000000..ec20878ade
--- /dev/null
+++ b/knife/spec/unit/knife/client_delete_spec.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::ClientDelete do
+ before(:each) do
+ @knife = Chef::Knife::ClientDelete.new
+ # defaults
+ @knife.config = {
+ delete_validators: false,
+ }
+ @knife.name_args = [ "adam" ]
+ end
+
+ describe "run" do
+ it "should delete the client" do
+ expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, "adam", "client")
+ @knife.run
+ end
+
+ context "receives multiple clients" do
+ let(:clients) { %w{ adam ben charlie } }
+
+ before(:each) do
+ @knife.name_args = clients
+ end
+
+ it "deletes all clients" do
+ clients.each do |client|
+ expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, client, "client")
+ end
+
+ @knife.run
+ end
+ end
+
+ it "should print usage and exit when a client name is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with a validator" do
+ before(:each) do
+ allow(Chef::Knife::UI).to receive(:confirm).and_return(true)
+ allow(@knife).to receive(:confirm).and_return(true)
+ @client = Chef::ApiClientV1.new
+ expect(Chef::ApiClientV1).to receive(:load).and_return(@client)
+ end
+
+ it "should delete non-validator client if --delete-validators is not set" do
+ @knife.config[:delete_validators] = false
+ expect(@client).to receive(:destroy).and_return(@client)
+ expect(@knife).to receive(:msg)
+
+ @knife.run
+ end
+
+ it "should delete non-validator client if --delete-validators is set" do
+ @knife.config[:delete_validators] = true
+ expect(@client).to receive(:destroy).and_return(@client)
+ expect(@knife).to receive(:msg)
+
+ @knife.run
+ end
+
+ it "should not delete validator client if --delete-validators is not set" do
+ @client.validator(true)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should delete validator client if --delete-validators is set" do
+ @knife.config[:delete_validators] = true
+ expect(@client).to receive(:destroy).and_return(@client)
+ expect(@knife).to receive(:msg)
+
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_edit_spec.rb b/knife/spec/unit/knife/client_edit_spec.rb
new file mode 100644
index 0000000000..03ac450b3e
--- /dev/null
+++ b/knife/spec/unit/knife/client_edit_spec.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+require "chef/api_client_v1"
+
+describe Chef::Knife::ClientEdit do
+ before(:each) do
+ @knife = Chef::Knife::ClientEdit.new
+ @knife.name_args = [ "adam" ]
+ @knife.config[:disable_editing] = true
+ end
+
+ describe "run" do
+ let(:data) do
+ {
+ "name" => "adam",
+ "validator" => false,
+ "admin" => false,
+ "chef_type" => "client",
+ "create_key" => true,
+ }
+ end
+
+ it "should edit the client" do
+ allow(Chef::ApiClientV1).to receive(:load).with("adam").and_return(data)
+ expect(@knife).to receive(:edit_hash).with(data).and_return(data)
+ @knife.run
+ end
+
+ it "should print usage and exit when a client name is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_list_spec.rb b/knife/spec/unit/knife/client_list_spec.rb
new file mode 100644
index 0000000000..b6a205e6b1
--- /dev/null
+++ b/knife/spec/unit/knife/client_list_spec.rb
@@ -0,0 +1,34 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::ClientList do
+ before(:each) do
+ @knife = Chef::Knife::ClientList.new
+ @knife.name_args = [ "adam" ]
+ end
+
+ describe "run" do
+ it "should list the clients" do
+ expect(Chef::ApiClientV1).to receive(:list)
+ expect(@knife).to receive(:format_list_for_display)
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/client_reregister_spec.rb b/knife/spec/unit/knife/client_reregister_spec.rb
new file mode 100644
index 0000000000..f1217edee4
--- /dev/null
+++ b/knife/spec/unit/knife/client_reregister_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::ClientReregister do
+ before(:each) do
+ @knife = Chef::Knife::ClientReregister.new
+ @knife.name_args = [ "adam" ]
+ @client_mock = double("client_mock", private_key: "foo_key")
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ context "when no client name is given on the command line" do
+ before do
+ @knife.name_args = []
+ end
+
+ it "should print usage and exit when a client name is not provided" do
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ context "when not configured for file output" do
+ it "reregisters the client and prints the key" do
+ expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock)
+ @knife.run
+ expect(@stdout.string).to match( /foo_key/ )
+ end
+ end
+
+ context "when configured for file output" do
+ it "should write the private key to a file" do
+ expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock)
+
+ @knife.config[:file] = "/tmp/monkeypants"
+ filehandle = StringIO.new
+ expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ @knife.run
+ expect(filehandle.string).to eq("foo_key")
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/client_show_spec.rb b/knife/spec/unit/knife/client_show_spec.rb
new file mode 100644
index 0000000000..39928a6289
--- /dev/null
+++ b/knife/spec/unit/knife/client_show_spec.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::ClientShow do
+ before(:each) do
+ @knife = Chef::Knife::ClientShow.new
+ @knife.name_args = [ "adam" ]
+ @client_mock = double("client_mock")
+ end
+
+ describe "run" do
+ it "should list the client" do
+ expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(@client_mock)
+ expect(@knife).to receive(:format_for_display).with(@client_mock)
+ @knife.run
+ end
+
+ it "should pretty print json" do
+ @knife.config[:format] = "json"
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ fake_client_contents = { "foo" => "bar", "baz" => "qux" }
+ expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(fake_client_contents)
+ @knife.run
+ expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n")
+ end
+
+ it "should print usage and exit when a client name is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/configure_client_spec.rb b/knife/spec/unit/knife/configure_client_spec.rb
new file mode 100644
index 0000000000..f88ffb31ed
--- /dev/null
+++ b/knife/spec/unit/knife/configure_client_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::ConfigureClient do
+ before do
+ @knife = Chef::Knife::ConfigureClient.new
+ Chef::Config[:chef_server_url] = "https://chef.example.com"
+ Chef::Config[:validation_client_name] = "chef-validator"
+ Chef::Config[:validation_key] = "/etc/chef/validation.pem"
+
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+ it "should print usage and exit when a directory is not provided" do
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with(/must provide the directory/)
+ expect do
+ @knife.run
+ end.to raise_error SystemExit
+ end
+
+ describe "when specifing a directory" do
+ before do
+ @knife.name_args = ["/home/bob/.chef"]
+ @client_file = StringIO.new
+ @validation_file = StringIO.new
+ expect(File).to receive(:open).with("/home/bob/.chef/client.rb", "w")
+ .and_yield(@client_file)
+ expect(File).to receive(:open).with("/home/bob/.chef/validation.pem", "w")
+ .and_yield(@validation_file)
+ expect(IO).to receive(:read).and_return("foo_bar_baz")
+ end
+
+ it "should recursively create the directory" do
+ expect(FileUtils).to receive(:mkdir_p).with("/home/bob/.chef")
+ @knife.run
+ end
+
+ it "should write out the config file" do
+ allow(FileUtils).to receive(:mkdir_p)
+ @knife.run
+ expect(@client_file.string).to match %r{chef_server_url\s+'https\://chef\.example\.com'}
+ expect(@client_file.string).to match(/validation_client_name\s+'chef-validator'/)
+ end
+
+ it "should write out the validation.pem file" do
+ allow(FileUtils).to receive(:mkdir_p)
+ @knife.run
+ expect(@validation_file.string).to match(/foo_bar_baz/)
+ end
+
+ it "should print information on what is being configured" do
+ allow(FileUtils).to receive(:mkdir_p)
+ @knife.run
+ expect(@stderr.string).to match(/creating client configuration/i)
+ expect(@stderr.string).to match(/writing client\.rb/i)
+ expect(@stderr.string).to match(/writing validation\.pem/i)
+ end
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/configure_spec.rb b/knife/spec/unit/knife/configure_spec.rb
new file mode 100644
index 0000000000..f9d1bea8fd
--- /dev/null
+++ b/knife/spec/unit/knife/configure_spec.rb
@@ -0,0 +1,190 @@
+require "knife_spec_helper"
+
+describe Chef::Knife::Configure do
+ before do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::Configure.new
+ @rest_client = double("null rest client", post: { result: :true })
+ allow(@knife).to receive(:rest).and_return(@rest_client)
+
+ @out = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@out)
+ @knife.config[:config_file] = "/home/you/.chef/knife.rb"
+
+ @in = StringIO.new("\n" * 7)
+ allow(@knife.ui).to receive(:stdin).and_return(@in)
+
+ @err = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@err)
+
+ allow(Ohai::System).to receive(:new).and_return(ohai)
+ end
+
+ let(:fqdn) { "foo.example.org" }
+
+ let(:ohai) do
+ o = {}
+ allow(o).to receive(:all_plugins).with(%w{ os hostname fqdn })
+ o[:fqdn] = fqdn
+ o
+ end
+
+ let(:default_admin_key) { "/etc/chef-server/admin.pem" }
+ let(:default_admin_key_win32) { File.expand_path(default_admin_key) }
+
+ let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" }
+ let(:default_validator_key_win32) { File.expand_path(default_validator_key) }
+
+ let(:default_server_url) { "https://#{fqdn}/organizations/myorg" }
+
+ it "asks the user for the URL of the chef server" do
+ @knife.ask_user_for_config
+ expect(@out.string).to match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]"))
+ expect(@knife.chef_server).to eq(default_server_url)
+ end
+
+ it "asks the user for the clientname they want for the new client if -i is specified" do
+ @knife.config[:initial] = true
+ allow(Etc).to receive(:getlogin).and_return("a-new-user")
+ @knife.ask_user_for_config
+ expect(@out.string).to match(Regexp.escape("Please enter a name for the new user: [a-new-user]"))
+ expect(@knife.new_client_name).to eq(Etc.getlogin)
+ end
+
+ it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do
+ @knife.config[:initial] = true
+ @knife.config[:node_name] = "testnode"
+ allow(Etc).to receive(:getlogin).and_return("a-new-user")
+ @knife.ask_user_for_config
+ expect(@out.string).not_to match(Regexp.escape("Please enter a name for the new user"))
+ expect(@knife.new_client_name).to eq("testnode")
+ end
+
+ it "asks the user for the existing API username or clientname if -i is not specified" do
+ allow(Etc).to receive(:getlogin).and_return("a-new-user")
+ @knife.ask_user_for_config
+ expect(@out.string).to match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]"))
+ expect(@knife.new_client_name).to eq(Etc.getlogin)
+ end
+
+ it "asks the user for the existing admin client's name if -i is specified" do
+ @knife.config[:initial] = true
+ @knife.ask_user_for_config
+ expect(@out.string).to match(Regexp.escape("Please enter the existing admin name: [admin]"))
+ expect(@knife.admin_client_name).to eq("admin")
+ end
+
+ it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do
+ @knife.config[:initial] = true
+ @knife.config[:admin_client_name] = "my-webui"
+ @knife.ask_user_for_config
+ expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin:"))
+ expect(@knife.admin_client_name).to eq("my-webui")
+ end
+
+ it "should not ask the user for the existing admin client's name if -i is not specified" do
+ @knife.ask_user_for_config
+ expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin: [admin]"))
+ expect(@knife.admin_client_name).not_to eq("admin")
+ end
+
+ it "asks the user for the location of the existing admin key if -i is specified" do
+ @knife.config[:initial] = true
+ @knife.ask_user_for_config
+ expect(@out.string).to match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]"))
+ if windows?
+ expect(@knife.admin_client_key.capitalize).to eq(default_admin_key_win32.capitalize)
+ else
+ expect(@knife.admin_client_key).to eq(default_admin_key)
+ end
+ end
+
+ it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do
+ @knife.config[:initial] = true
+ @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem"
+ @knife.ask_user_for_config
+ expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key:"))
+ if windows?
+ expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
+ else
+ expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem")
+ end
+ end
+
+ it "should not ask the user for the location of the existing admin key if -i is not specified" do
+ @knife.ask_user_for_config
+ expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]"))
+ if windows?
+ expect(@knife.admin_client_key).not_to eq(default_admin_key_win32)
+ else
+ expect(@knife.admin_client_key).not_to eq(default_admin_key)
+ end
+ end
+
+ it "should not ask the user for anything if -i and all other properties are specified" do
+ @knife.config[:initial] = true
+ @knife.config[:chef_server_url] = "http://localhost:5000"
+ @knife.config[:node_name] = "testnode"
+ @knife.config[:admin_client_name] = "my-webui"
+ @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem"
+ @knife.config[:client_key] = "/home/you/a-new-user.pem"
+ allow(Etc).to receive(:getlogin).and_return("a-new-user")
+
+ @knife.ask_user_for_config
+ expect(@out.string).to match(/\s*/)
+
+ expect(@knife.new_client_name).to eq("testnode")
+ expect(@knife.chef_server).to eq("http://localhost:5000")
+ expect(@knife.admin_client_name).to eq("my-webui")
+ if windows?
+ expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
+ expect(@knife.new_client_key).to match %r{^[A-Za-z]:/home/you/a-new-user\.pem$}
+ else
+ expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem")
+ expect(@knife.new_client_key).to eq("/home/you/a-new-user.pem")
+ end
+ end
+
+ it "writes the new data to a config file" do
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef")
+ allow(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials")
+ allow(File).to receive(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem")
+ allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key)
+ expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef")
+ config_file = StringIO.new
+ expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w").and_yield config_file
+ @knife.config[:repository] = "/home/you/chef-repo"
+ @knife.run
+ expect(config_file.string).to match(/^client_name\s+=\s+'#{Etc.getlogin}'$/)
+ expect(config_file.string).to match(%r{^client_key\s+=\s+'/home/you/.chef/#{Etc.getlogin}.pem'$})
+ expect(config_file.string).to match(/^chef_server_url\s+=\s+'#{default_server_url}'$/)
+ end
+
+ it "creates a new client when given the --initial option" do
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef")
+ expect(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials")
+ expect(File).to receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem")
+ allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key)
+ Chef::Config[:node_name] = "webmonkey.example.com"
+
+ user_command = Chef::Knife::UserCreate.new
+ expect(user_command).to receive(:run)
+
+ allow(Etc).to receive(:getlogin).and_return("a-new-user")
+
+ allow(Chef::Knife::UserCreate).to receive(:new).and_return(user_command)
+ expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef")
+ expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w")
+ @knife.config[:initial] = true
+ @knife.config[:user_password] = "blah"
+ @knife.run
+ expect(user_command.name_args).to eq(Array("a-new-user"))
+ expect(user_command.config[:user_password]).to eq("blah")
+ expect(user_command.config[:admin]).to be_truthy
+ expect(user_command.config[:file]).to eq("/home/you/.chef/a-new-user.pem")
+ expect(user_command.config[:yes]).to be_truthy
+ expect(user_command.config[:disable_editing]).to be_truthy
+ end
+end
diff --git a/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb b/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb
new file mode 100644
index 0000000000..52f9c1eeb9
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_bulk_delete_spec.rb
@@ -0,0 +1,87 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookBulkDelete do
+ before(:each) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::CookbookBulkDelete.new
+ @knife.config = { print_after: nil }
+ @knife.name_args = ["."]
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ @cookbooks = {}
+ %w{cheezburger pizza lasagna}.each do |cookbook_name|
+ cookbook = Chef::CookbookVersion.new(cookbook_name)
+ @cookbooks[cookbook_name] = cookbook
+ end
+ @rest = double("Chef::ServerAPI")
+ allow(@rest).to receive(:get).and_return(@cookbooks)
+ allow(@rest).to receive(:delete).and_return(true)
+ allow(@knife).to receive(:rest).and_return(@rest)
+ allow(Chef::CookbookVersion).to receive(:list).and_return(@cookbooks)
+
+ end
+
+ describe "when there are several cookbooks on the server" do
+ before do
+ @cheezburger = { "cheezburger" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-cheez", "version" => "1.0.0" }] } }
+ allow(@rest).to receive(:get).with("cookbooks/cheezburger").and_return(@cheezburger)
+ @pizza = { "pizza" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-pizza", "version" => "2.0.0" }] } }
+ allow(@rest).to receive(:get).with("cookbooks/pizza").and_return(@pizza)
+ @lasagna = { "lasagna" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-lasagna", "version" => "3.0.0" }] } }
+ allow(@rest).to receive(:get).with("cookbooks/lasagna").and_return(@lasagna)
+ end
+
+ it "should print the cookbooks you are about to delete" do
+ expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down)
+ @knife.run
+ expect(@stdout.string).to match(/#{expected}/)
+ end
+
+ it "should confirm you really want to delete them" do
+ expect(@knife.ui).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should delete each cookbook" do
+ { "cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => "3.0.0" }.each do |cookbook_name, version|
+ expect(@rest).to receive(:delete).with("cookbooks/#{cookbook_name}/#{version}")
+ end
+ @knife.run
+ end
+
+ it "should only delete cookbooks that match the regex" do
+ @knife.name_args = ["cheezburger"]
+ expect(@rest).to receive(:delete).with("cookbooks/cheezburger/1.0.0")
+ @knife.run
+ end
+ end
+
+ it "should exit if the regex is not provided" do
+ @knife.name_args = []
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+end
diff --git a/knife/spec/unit/knife/cookbook_delete_spec.rb b/knife/spec/unit/knife/cookbook_delete_spec.rb
new file mode 100644
index 0000000000..b05006f2d3
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_delete_spec.rb
@@ -0,0 +1,239 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookDelete do
+ before(:each) do
+ @knife = Chef::Knife::CookbookDelete.new
+ @knife.name_args = ["foobar"]
+ @knife.cookbook_name = "foobar"
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+ it "should print usage and exit when a cookbook name is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "when specifying a cookbook name" do
+ it "should delete the cookbook without a specific version" do
+ expect(@knife).to receive(:delete_without_explicit_version)
+ @knife.run
+ end
+
+ describe "and a version" do
+ it "should delete the specific version of the cookbook" do
+ @knife.name_args << "1.0.0"
+ expect(@knife).to receive(:delete_explicit_version)
+ @knife.run
+ end
+ end
+
+ describe "with -a or --all" do
+ it "should delete all versions of the cookbook" do
+ @knife.config[:all] = true
+ expect(@knife).to receive(:delete_all_versions)
+ @knife.run
+ end
+ end
+
+ describe "with -p or --purge" do
+ it "should prompt to purge the files" do
+ @knife.config[:purge] = true
+ expect(@knife).to receive(:confirm)
+ .with(/.+Are you sure you want to purge files.+/)
+ expect(@knife).to receive(:delete_without_explicit_version)
+ @knife.run
+ end
+ end
+ end
+ end
+
+ describe "delete_explicit_version" do
+ it "should delete the specific cookbook version" do
+ @knife.cookbook_name = "foobar"
+ @knife.version = "1.0.0"
+ expect(@knife).to receive(:delete_object).with(Chef::CookbookVersion,
+ "foobar version 1.0.0",
+ "cookbook").and_yield
+ expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0")
+ @knife.delete_explicit_version
+ end
+ end
+
+ describe "delete_all_versions" do
+ it "should prompt to delete all versions of the cookbook" do
+ @knife.cookbook_name = "foobar"
+ expect(@knife).to receive(:confirm).with("Do you really want to delete all versions of foobar")
+ expect(@knife).to receive(:delete_all_without_confirmation)
+ @knife.delete_all_versions
+ end
+ end
+
+ describe "delete_all_without_confirmation" do
+ it "should delete all versions without confirmation" do
+ versions = ["1.0.0", "1.1.0"]
+ expect(@knife).to receive(:available_versions).and_return(versions)
+ versions.each do |v|
+ expect(@knife).to receive(:delete_version_without_confirmation).with(v)
+ end
+ @knife.delete_all_without_confirmation
+ end
+ end
+
+ describe "delete_without_explicit_version" do
+ it "should exit if there are no available versions" do
+ expect(@knife).to receive(:available_versions).and_return(nil)
+ expect { @knife.delete_without_explicit_version }.to raise_error(SystemExit)
+ end
+
+ it "should delete the version if only one is found" do
+ expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"])
+ expect(@knife).to receive(:delete_explicit_version)
+ @knife.delete_without_explicit_version
+ end
+
+ it "should ask which version(s) to delete if multiple are found" do
+ expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "1.1.0"])
+ expect(@knife).to receive(:ask_which_versions_to_delete).and_return(["1.0.0", "1.1.0"])
+ expect(@knife).to receive(:delete_versions_without_confirmation).with(["1.0.0", "1.1.0"])
+ @knife.delete_without_explicit_version
+ end
+ end
+
+ describe "available_versions" do
+ before(:each) do
+ @rest_mock = double("rest")
+ expect(@knife).to receive(:rest).and_return(@rest_mock)
+ @cookbook_data = { "foobar" => { "versions" => [{ "version" => "1.0.0" },
+ { "version" => "1.1.0" },
+ { "version" => "2.0.0" } ] },
+ }
+ end
+
+ it "should return the list of versions of the cookbook" do
+ expect(@rest_mock).to receive(:get).with("cookbooks/foobar").and_return(@cookbook_data)
+ expect(@knife.available_versions).to eq(["1.0.0", "1.1.0", "2.0.0"])
+ end
+
+ it "should raise if an error other than HTTP 404 is returned" do
+ exception = Net::HTTPClientException.new("500 Internal Server Error", "500")
+ expect(@rest_mock).to receive(:get).and_raise(exception)
+ expect { @knife.available_versions }.to raise_error Net::HTTPClientException
+ end
+
+ describe "if the cookbook can't be found" do
+ before(:each) do
+ expect(@rest_mock).to receive(:get)
+ .and_raise(Net::HTTPClientException.new("404 Not Found", "404"))
+ end
+
+ it "should print an error" do
+ @knife.available_versions
+ expect(@stderr.string).to match(/error.+cannot find a cookbook named foobar/i)
+ end
+
+ it "should return nil" do
+ expect(@knife.available_versions).to eq(nil)
+ end
+ end
+ end
+
+ describe "ask_which_version_to_delete" do
+ before(:each) do
+ allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"])
+ end
+
+ it "should prompt the user to select a version" do
+ prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m
+ expect(@knife).to receive(:ask_question).with(prompt).and_return("1")
+ @knife.ask_which_versions_to_delete
+ end
+
+ it "should print an error and exit if a version wasn't specified" do
+ expect(@knife).to receive(:ask_question).and_return("")
+ expect(@knife.ui).to receive(:error).with(/no versions specified/i)
+ expect { @knife.ask_which_versions_to_delete }.to raise_error(SystemExit)
+ end
+
+ it "should print an error if an invalid choice was selected" do
+ expect(@knife).to receive(:ask_question).and_return("100")
+ expect(@knife.ui).to receive(:error).with(/100 is not a valid choice/i)
+ @knife.ask_which_versions_to_delete
+ end
+
+ it "should return the selected versions" do
+ expect(@knife).to receive(:ask_question).and_return("1, 3")
+ expect(@knife.ask_which_versions_to_delete).to eq(["1.0.0", "2.0.0"])
+ end
+
+ it "should return all of the versions if 'all' was selected" do
+ expect(@knife).to receive(:ask_question).and_return("4")
+ expect(@knife.ask_which_versions_to_delete).to eq([:all])
+ end
+ end
+
+ describe "delete_version_without_confirmation" do
+ it "should delete the cookbook version" do
+ expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0")
+ @knife.delete_version_without_confirmation("1.0.0")
+ end
+
+ it "should output that the cookbook was deleted" do
+ allow(@knife).to receive(:delete_request)
+ @knife.delete_version_without_confirmation("1.0.0")
+ expect(@stderr.string).to match(/deleted cookbook\[foobar\]\[1.0.0\]/im)
+ end
+
+ describe "with --print-after" do
+ it "should display the cookbook data" do
+ object = ""
+ @knife.config[:print_after] = true
+ allow(@knife).to receive(:delete_request).and_return(object)
+ expect(@knife).to receive(:format_for_display).with(object)
+ @knife.delete_version_without_confirmation("1.0.0")
+ end
+ end
+ end
+
+ describe "delete_versions_without_confirmation" do
+ it "should delete each version without confirmation" do
+ versions = ["1.0.0", "1.1.0"]
+ versions.each do |v|
+ expect(@knife).to receive(:delete_version_without_confirmation).with(v)
+ end
+ @knife.delete_versions_without_confirmation(versions)
+ end
+
+ describe "with -a or --all" do
+ it "should delete all versions without confirmation" do
+ versions = [:all]
+ expect(@knife).to receive(:delete_all_without_confirmation)
+ @knife.delete_versions_without_confirmation(versions)
+ end
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/cookbook_download_spec.rb b/knife/spec/unit/knife/cookbook_download_spec.rb
new file mode 100644
index 0000000000..b3dbc81205
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_download_spec.rb
@@ -0,0 +1,255 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookDownload do
+ before(:each) do
+ @knife = Chef::Knife::CookbookDownload.new
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+ it "should print usage and exit when a cookbook name is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with(/must specify a cookbook name/)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should exit with a fatal error when there is no cookbook on the server" do
+ @knife.name_args = ["foobar", nil]
+ expect(@knife).to receive(:determine_version).and_return(nil)
+ expect(@knife.ui).to receive(:fatal).with("No such cookbook found")
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "with a cookbook name" do
+ before(:each) do
+ @knife.name_args = ["foobar"]
+ @knife.config[:download_directory] = "/var/tmp/chef"
+ @rest_mock = double("rest")
+ allow(@knife).to receive(:rest).and_return(@rest_mock)
+
+ expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0")
+ .and_return(cookbook)
+ end
+
+ let(:manifest_data) do
+ {
+ all_files: [
+ {
+ "path" => "recipes/foo.rb",
+ "name" => "recipes/foo.rb",
+ "url" => "http://example.org/files/foo.rb",
+ },
+ {
+ "path" => "recipes/bar.rb",
+ "name" => "recipes/bar.rb",
+ "url" => "http://example.org/files/bar.rb",
+ },
+ {
+ "path" => "templates/default/foo.erb",
+ "name" => "templates/foo.erb",
+ "url" => "http://example.org/files/foo.erb",
+ },
+ {
+ "path" => "templates/default/bar.erb",
+ "name" => "templates/bar.erb",
+ "url" => "http://example.org/files/bar.erb",
+ },
+ {
+ "path" => "attributes/default.rb",
+ "name" => "attributes/default.rb",
+ "url" => "http://example.org/files/default.rb",
+ },
+ ],
+ }
+ end
+
+ let(:cookbook) do
+ cb = Chef::CookbookVersion.new("foobar")
+ cb.version = "1.0.0"
+ cb.manifest = manifest_data
+ cb
+ end
+
+ describe "and no version" do
+ let(:manifest_data) { { all_files: [] } }
+ it "should determine which version to download" do
+ expect(@knife).to receive(:determine_version).and_return("1.0.0")
+ expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false)
+ @knife.run
+ end
+ end
+
+ describe "and a version" do
+ before(:each) do
+ @knife.name_args << "1.0.0"
+ @files = manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq
+ @files_mocks = {}
+ @files.map { |f| File.basename(f) }.flatten.uniq.each do |f|
+ @files_mocks[f] = double("#{f}_mock")
+ allow(@files_mocks[f]).to receive(:path).and_return("/var/tmp/#{f}")
+ end
+ end
+
+ it "should print an error and exit if the cookbook download directory already exists" do
+ expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true)
+ expect(@knife.ui).to receive(:fatal).with(%r{/var/tmp/chef/foobar-1\.0\.0 exists}i)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "when downloading the cookbook" do
+ before(:each) do
+ @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir|
+ expect(FileUtils).to receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}")
+ .at_least(:once)
+ end
+
+ @files_mocks.each_pair do |file, mock|
+ expect(@rest_mock).to receive(:streaming_request).with("http://example.org/files/#{file}")
+ .and_return(mock)
+ end
+
+ @files.each do |f|
+ expect(FileUtils).to receive(:mv)
+ .with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}")
+ end
+ end
+
+ it "should download the cookbook when the cookbook download directory doesn't exist" do
+ expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false)
+ @knife.run
+ %w{attributes recipes templates}.each do |segment|
+ expect(@stderr.string).to match(/downloading #{segment}/im)
+ end
+ expect(@stderr.string).to match(/downloading foobar cookbook version 1\.0\.0/im)
+ expect(@stderr.string).to match %r{cookbook downloaded to /var/tmp/chef/foobar-1\.0\.0}im
+ end
+
+ describe "with -f or --force" do
+ it "should remove the existing the cookbook download directory if it exists" do
+ @knife.config[:force] = true
+ expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true)
+ expect(FileUtils).to receive(:rm_rf).with("/var/tmp/chef/foobar-1.0.0")
+ @knife.run
+ end
+ end
+ end
+
+ end
+ end
+
+ end
+
+ describe "determine_version" do
+
+ it "should return nil if there are no versions" do
+ expect(@knife).to receive(:available_versions).and_return(nil)
+ expect(@knife.determine_version).to eq(nil)
+ expect(@knife.version).to eq(nil)
+ end
+
+ it "should return and set the version if there is only one version" do
+ expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"])
+ expect(@knife.determine_version).to eq("1.0.0")
+ expect(@knife.version).to eq("1.0.0")
+ end
+
+ it "should ask which version to download and return it if there is more than one" do
+ expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "2.0.0"])
+ expect(@knife).to receive(:ask_which_version).and_return("1.0.0")
+ expect(@knife.determine_version).to eq("1.0.0")
+ end
+
+ describe "with -N or --latest" do
+ it "should return and set the version to the latest version" do
+ @knife.config[:latest] = true
+ expect(@knife).to receive(:available_versions).at_least(:once)
+ .and_return(["1.0.0", "1.1.0", "2.0.0"])
+ @knife.determine_version
+ expect(@knife.version.to_s).to eq("2.0.0")
+ end
+ end
+ end
+
+ describe "available_versions" do
+ before(:each) do
+ @knife.cookbook_name = "foobar"
+ end
+
+ it "should return nil if there are no versions" do
+ expect(Chef::CookbookVersion).to receive(:available_versions)
+ .with("foobar")
+ .and_return(nil)
+ expect(@knife.available_versions).to eq(nil)
+ end
+
+ it "should return the available versions" do
+ expect(Chef::CookbookVersion).to receive(:available_versions)
+ .with("foobar")
+ .and_return(["1.1.0", "2.0.0", "1.0.0"])
+ expect(@knife.available_versions).to eq([Chef::Version.new("1.0.0"),
+ Chef::Version.new("1.1.0"),
+ Chef::Version.new("2.0.0")])
+ end
+
+ it "should avoid multiple API calls to the server" do
+ expect(Chef::CookbookVersion).to receive(:available_versions)
+ .once
+ .with("foobar")
+ .and_return(["1.1.0", "2.0.0", "1.0.0"])
+ @knife.available_versions
+ @knife.available_versions
+ end
+ end
+
+ describe "ask_which_version" do
+ before(:each) do
+ @knife.cookbook_name = "foobar"
+ allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"])
+ end
+
+ it "should prompt the user to select a version" do
+ prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m
+ expect(@knife).to receive(:ask_question).with(prompt).and_return("1")
+ @knife.ask_which_version
+ end
+
+ it "should set the version to the user's selection" do
+ expect(@knife).to receive(:ask_question).and_return("1")
+ @knife.ask_which_version
+ expect(@knife.version).to eq("1.0.0")
+ end
+
+ it "should print an error and exit if a version wasn't specified" do
+ expect(@knife).to receive(:ask_question).and_return("")
+ expect(@knife.ui).to receive(:error).with(/is not a valid value/i)
+ expect { @knife.ask_which_version }.to raise_error(SystemExit)
+ end
+
+ it "should print an error if an invalid choice was selected" do
+ expect(@knife).to receive(:ask_question).and_return("100")
+ expect(@knife.ui).to receive(:error).with(/'100' is not a valid value/i)
+ expect { @knife.ask_which_version }.to raise_error(SystemExit)
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/cookbook_list_spec.rb b/knife/spec/unit/knife/cookbook_list_spec.rb
new file mode 100644
index 0000000000..42c3ef1bfd
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_list_spec.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookList do
+ before do
+ @knife = Chef::Knife::CookbookList.new
+ @rest_mock = double("rest")
+ allow(@knife).to receive(:rest).and_return(@rest_mock)
+ @cookbook_names = %w{apache2 mysql}
+ @base_url = "https://server.example.com/cookbooks"
+ @cookbook_data = {}
+ @cookbook_names.each do |item|
+ @cookbook_data[item] = { "url" => "#{@base_url}/#{item}",
+ "versions" => [{ "version" => "1.0.1",
+ "url" => "#{@base_url}/#{item}/1.0.1" }] }
+ end
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should display the latest version of the cookbooks" do
+ expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=1")
+ .and_return(@cookbook_data)
+ @knife.run
+ @cookbook_names.each do |item|
+ expect(@stdout.string).to match(/#{item}\s+1\.0\.1/)
+ end
+ end
+
+ it "should query cookbooks for the configured environment" do
+ @knife.config[:environment] = "production"
+ expect(@rest_mock).to receive(:get)
+ .with("/environments/production/cookbooks?num_versions=1")
+ .and_return(@cookbook_data)
+ @knife.run
+ end
+
+ describe "with -w or --with-uri" do
+ it "should display the cookbook uris" do
+ @knife.config[:with_uri] = true
+ allow(@rest_mock).to receive(:get).and_return(@cookbook_data)
+ @knife.run
+ @cookbook_names.each do |item|
+ pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/
+ expect(@stdout.string).to match pattern
+ end
+ end
+ end
+
+ describe "with -a or --all" do
+ before do
+ @cookbook_names.each do |item|
+ @cookbook_data[item]["versions"] << { "version" => "1.0.0",
+ "url" => "#{@base_url}/#{item}/1.0.0" }
+ end
+ end
+
+ it "should display all versions of the cookbooks" do
+ @knife.config[:all_versions] = true
+ expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=all")
+ .and_return(@cookbook_data)
+ @knife.run
+ @cookbook_names.each do |item|
+ expect(@stdout.string).to match(/#{item}\s+1\.0\.1\s+1\.0\.0/)
+ end
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb
new file mode 100644
index 0000000000..c595aef96f
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_metadata_from_file_spec.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookMetadataFromFile do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb"))
+ @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json"))
+ @knife = Chef::Knife::CookbookMetadataFromFile.new
+ @knife.name_args = [ @src ]
+ allow(@knife).to receive(:to_json_pretty).and_return(true)
+ @md = Chef::Cookbook::Metadata.new
+ allow(Chef::Cookbook::Metadata).to receive(:new).and_return(@md)
+ allow($stdout).to receive(:write)
+ end
+
+ after do
+ if File.exist?(@tgt)
+ File.unlink(@tgt)
+ end
+ end
+
+ describe "run" do
+ it "should print usage and exit when a FILE is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with(/You must specify the FILE./)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should determine cookbook name from path" do
+ expect(@md).to receive(:name).with(no_args)
+ expect(@md).to receive(:name).with("quick_start")
+ @knife.run
+ end
+
+ it "should load the metadata source" do
+ expect(@md).to receive(:from_file).with(@src)
+ @knife.run
+ end
+
+ it "should write out the metadata to the correct location" do
+ expect(File).to receive(:open).with(@tgt, "w")
+ @knife.run
+ end
+
+ it "should generate json from the metadata" do
+ expect(Chef::JSONCompat).to receive(:to_json_pretty).with(@md)
+ @knife.run
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/cookbook_metadata_spec.rb b/knife/spec/unit/knife/cookbook_metadata_spec.rb
new file mode 100644
index 0000000000..1a274cc6f4
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_metadata_spec.rb
@@ -0,0 +1,182 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2011-2016, Thomas Bishop
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookMetadata do
+ let(:knife) do
+ knife = Chef::Knife::CookbookMetadata.new
+ knife.name_args = ["foobar"]
+ knife
+ end
+
+ let(:cookbook_dir) { Dir.mktmpdir }
+
+ let(:stdout) { StringIO.new }
+
+ let(:stderr) { StringIO.new }
+
+ before(:each) do
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stderr)
+ end
+
+ def create_metadata_rb(**kwargs)
+ name = kwargs[:name]
+ Dir.mkdir("#{cookbook_dir}/#{name}")
+ File.open("#{cookbook_dir}/#{name}/metadata.rb", "w+") do |f|
+ kwargs.each do |key, value|
+ if value.is_a?(Array)
+ f.puts "#{key} #{value.map { |v| "\"#{v}\"" }.join(", ")}"
+ else
+ f.puts "#{key} \"#{value}\""
+ end
+ end
+ end
+ end
+
+ def create_metadata_json(**kwargs)
+ name = kwargs[:name]
+ Dir.mkdir("#{cookbook_dir}/#{name}")
+ File.open("#{cookbook_dir}/#{name}/metadata.json", "w+") do |f|
+ f.write(FFI_Yajl::Encoder.encode(kwargs))
+ end
+ end
+
+ def create_invalid_json
+ Dir.mkdir("#{cookbook_dir}/foobar")
+ File.open("#{cookbook_dir}/foobar/metadata.json", "w+") do |f|
+ f.write <<-EOH
+ { "version": "1.0.0", {ImInvalid}}
+ EOH
+ end
+ end
+
+ describe "run" do
+ it "should print an error and exit if a cookbook name was not provided" do
+ knife.name_args = []
+ expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should print an error and exit if an empty cookbook name was provided" do
+ knife.name_args = [""]
+ expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should generate the metadata for the cookbook" do
+ expect(knife).to receive(:generate_metadata).with("foobar")
+ knife.run
+ end
+
+ describe "with -a or --all" do
+ before(:each) do
+ Chef::Config[:cookbook_path] = cookbook_dir
+ knife.config[:all] = true
+ create_metadata_rb(name: "foo", version: "1.0.0")
+ create_metadata_rb(name: "bar", version: "2.0.0")
+ expect(knife).to receive(:generate_metadata).with("foo").and_call_original
+ expect(knife).to receive(:generate_metadata).with("bar").and_call_original
+ end
+
+ it "should generate the metadata for each cookbook" do
+ expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original
+ knife.run
+ expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im
+ expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im
+ end
+
+ it "with -o or --cookbook_path should look in the provided path and generate cookbook metadata" do
+ Chef::Config[:cookbook_path] = "/dev/null"
+ knife.config[:cookbook_path] = cookbook_dir
+ expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original
+ knife.run
+ expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im
+ expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im
+ end
+ end
+
+ end
+
+ describe "generate_metadata" do
+ before(:each) do
+ Chef::Config[:cookbook_path] = cookbook_dir
+ end
+
+ it "should generate the metadata from metadata.rb if it exists" do
+ create_metadata_rb(name: "foobar", version: "1.0.0")
+ expect(knife).to receive(:generate_metadata_from_file).with("foobar", "#{cookbook_dir}/foobar/metadata.rb").and_call_original
+ knife.run
+ expect(File.exist?("#{cookbook_dir}/foobar/metadata.json")).to be true
+ json = FFI_Yajl::Parser.parse(IO.read("#{cookbook_dir}/foobar/metadata.json"))
+ expect(json["name"]).to eql("foobar")
+ expect(json["version"]).to eql("1.0.0")
+ end
+
+ it "should validate the metadata json if metadata.rb does not exist" do
+ create_metadata_json(name: "foobar", version: "1.0.0")
+ expect(knife).to receive(:validate_metadata_json).with(cookbook_dir, "foobar").and_call_original
+ knife.run
+ end
+ end
+
+ describe "validation errors" do
+ before(:each) do
+ Chef::Config[:cookbook_path] = cookbook_dir
+ end
+
+ it "should fail for obsolete operators in metadata.rb" do
+ create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", ">> 0.2" ])
+ expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
+ end
+
+ it "should fail for obsolete format in metadata.rb (sadly)" do
+ create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", "> 0.2", "< 1.0" ])
+ expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
+ end
+
+ it "should fail for obsolete operators in metadata.json" do
+ create_metadata_json(name: "foobar", version: "1.0.0", dependencies: { "foo:bar" => ">> 0.2" })
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
+ end
+
+ it "should not fail for unknown field in metadata.rb" do
+ create_metadata_rb(name: "sounders", version: "2.0.0", beats: "toronto")
+ expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
+ expect { knife.run }.not_to raise_error
+ expect(stderr.string).to eql("")
+ end
+
+ it "should not fail for unknown field in metadata.json" do
+ create_metadata_json(name: "sounders", version: "2.0.0", beats: "toronto")
+ expect { knife.run }.not_to raise_error
+ expect(stderr.string).to eql("")
+ end
+
+ it "should fail on unparsable json" do
+ create_invalid_json
+ expect { knife.run }.to raise_error(Chef::Exceptions::JSON::ParseError)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/cookbook_show_spec.rb b/knife/spec/unit/knife/cookbook_show_spec.rb
new file mode 100644
index 0000000000..94e080cb15
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_show_spec.rb
@@ -0,0 +1,253 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::CookbookShow do
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ allow(knife).to receive(:rest).and_return(rest)
+ allow(knife).to receive(:pretty_print).and_return(true)
+ allow(knife).to receive(:output).and_return(true)
+ allow(Chef::CookbookVersion).to receive(:load).and_return(cb)
+ end
+
+ let(:knife) do
+ knife = Chef::Knife::CookbookShow.new
+ knife.config = {}
+ knife.name_args = [ "cookbook_name" ]
+ knife
+ end
+
+ let(:cb) do
+ cb = Chef::CookbookVersion.new("cookbook_name")
+ cb.manifest = manifest
+ cb
+ end
+
+ let(:rest) { double(Chef::ServerAPI) }
+
+ let(:content) { "Example recipe text" }
+
+ let(:manifest) do
+ {
+ "all_files" => [
+ {
+ name: "recipes/default.rb",
+ path: "recipes/default.rb",
+ checksum: "1234",
+ url: "http://example.org/files/default.rb",
+ },
+ ],
+ }
+ end
+
+ describe "run" do
+ describe "with 0 arguments: help" do
+ it "should should print usage and exit when given no arguments" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with 1 argument: versions" do
+ let(:response) do
+ {
+ "cookbook_name" => {
+ "url" => "http://url/cookbooks/cookbook_name",
+ "versions" => [
+ { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" },
+ { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" },
+ { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" },
+ ],
+ },
+ }
+ end
+
+ it "should show the raw cookbook data" do
+ expect(rest).to receive(:get).with("cookbooks/cookbook_name").and_return(response)
+ expect(knife).to receive(:format_cookbook_list_for_display).with(response)
+ knife.run
+ end
+
+ it "should respect the user-supplied environment" do
+ knife.config[:environment] = "foo"
+ expect(rest).to receive(:get).with("environments/foo/cookbooks/cookbook_name").and_return(response)
+ expect(knife).to receive(:format_cookbook_list_for_display).with(response)
+ knife.run
+ end
+ end
+
+ describe "with 2 arguments: name and version" do
+ before do
+ knife.name_args << "0.1.0"
+ end
+
+ let(:output) do
+ { "cookbook_name" => "cookbook_name",
+ "name" => "cookbook_name-0.0.0",
+ "frozen?" => false,
+ "version" => "0.0.0",
+ "metadata" => {
+ "name" => nil,
+ "description" => "",
+ "eager_load_libraries" => true,
+ "long_description" => "",
+ "maintainer" => "",
+ "maintainer_email" => "",
+ "license" => "All rights reserved",
+ "platforms" => {},
+ "dependencies" => {},
+ "providing" => {},
+ "recipes" => {},
+ "version" => "0.0.0",
+ "source_url" => "",
+ "issues_url" => "",
+ "privacy" => false,
+ "chef_versions" => [],
+ "ohai_versions" => [],
+ "gems" => [],
+ },
+ "recipes" =>
+ [{ "name" => "recipes/default.rb",
+ "path" => "recipes/default.rb",
+ "checksum" => "1234",
+ "url" => "http://example.org/files/default.rb" }],
+ }
+ end
+
+ it "should show the specific part of a cookbook" do
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(knife).to receive(:output).with(output)
+ knife.run
+ end
+ end
+
+ describe "with 3 arguments: name, version, and segment" do
+ before(:each) do
+ knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ]
+ end
+
+ it "should print the json of the part" do
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(knife).to receive(:output).with(cb.files_for("recipes"))
+ knife.run
+ end
+ end
+
+ describe "with 4 arguments: name, version, segment and filename" do
+ before(:each) do
+ knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ]
+ end
+
+ it "should print the raw result of the request (likely a file!)" do
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(rest).to receive(:streaming_request).with("http://example.org/files/default.rb").and_return(StringIO.new(content))
+ expect(knife).to receive(:pretty_print).with(content)
+ knife.run
+ end
+ end
+
+ describe "with 4 arguments: name, version, segment and filename -- with specificity" do
+ before(:each) do
+ knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ]
+ cb.manifest = {
+ "all_files" => [
+ {
+ name: "files/afile.rb",
+ path: "files/host-examplehost.example.org/afile.rb",
+ checksum: "1111",
+ specificity: "host-examplehost.example.org",
+ url: "http://example.org/files/1111",
+ },
+ {
+ name: "files/afile.rb",
+ path: "files/ubuntu-9.10/afile.rb",
+ checksum: "2222",
+ specificity: "ubuntu-9.10",
+ url: "http://example.org/files/2222",
+ },
+ {
+ name: "files/afile.rb",
+ path: "files/ubuntu/afile.rb",
+ checksum: "3333",
+ specificity: "ubuntu",
+ url: "http://example.org/files/3333",
+ },
+ {
+ name: "files/afile.rb",
+ path: "files/default/afile.rb",
+ checksum: "4444",
+ specificity: "default",
+ url: "http://example.org/files/4444",
+ },
+ ],
+ }
+
+ end
+
+ describe "with --fqdn" do
+ it "should pass the fqdn" do
+ knife.config[:platform] = "example_platform"
+ knife.config[:platform_version] = "1.0"
+ knife.config[:fqdn] = "examplehost.example.org"
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(rest).to receive(:streaming_request).with("http://example.org/files/1111").and_return(StringIO.new(content))
+ expect(knife).to receive(:pretty_print).with(content)
+ knife.run
+ end
+ end
+
+ describe "and --platform" do
+ it "should pass the platform" do
+ knife.config[:platform] = "ubuntu"
+ knife.config[:platform_version] = "1.0"
+ knife.config[:fqdn] = "differenthost.example.org"
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(rest).to receive(:streaming_request).with("http://example.org/files/3333").and_return(StringIO.new(content))
+ expect(knife).to receive(:pretty_print).with(content)
+ knife.run
+ end
+ end
+
+ describe "and --platform-version" do
+ it "should pass the platform" do
+ knife.config[:platform] = "ubuntu"
+ knife.config[:platform_version] = "9.10"
+ knife.config[:fqdn] = "differenthost.example.org"
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(rest).to receive(:streaming_request).with("http://example.org/files/2222").and_return(StringIO.new(content))
+ expect(knife).to receive(:pretty_print).with(content)
+ knife.run
+ end
+ end
+
+ describe "with none of the arguments, it should use the default" do
+ it "should pass them all" do
+ expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
+ expect(rest).to receive(:streaming_request).with("http://example.org/files/4444").and_return(StringIO.new(content))
+ expect(knife).to receive(:pretty_print).with(content)
+ knife.run
+ end
+ end
+
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/cookbook_upload_spec.rb b/knife/spec/unit/knife/cookbook_upload_spec.rb
new file mode 100644
index 0000000000..0893f6a6b3
--- /dev/null
+++ b/knife/spec/unit/knife/cookbook_upload_spec.rb
@@ -0,0 +1,364 @@
+#
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+require "chef/cookbook_uploader"
+require "timeout"
+
+describe Chef::Knife::CookbookUpload do
+ let(:cookbook) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
+ cookbook
+ end
+
+ let(:cookbooks_by_name) do
+ { cookbook.name => cookbook }
+ end
+
+ let(:cookbook_loader) do
+ cookbook_loader = cookbooks_by_name.dup
+ allow(cookbook_loader).to receive(:merged_cookbooks).and_return([])
+ allow(cookbook_loader).to receive(:load_cookbooks).and_return(cookbook_loader)
+ allow(cookbook_loader).to receive(:compile_metadata).and_return(nil)
+ allow(cookbook_loader).to receive(:freeze_versions).and_return(nil)
+ cookbook_loader
+ end
+
+ let(:cookbook_uploader) { double(upload_cookbooks: nil) }
+
+ let(:output) { StringIO.new }
+
+ let(:name_args) { ["test_cookbook"] }
+
+ let(:knife) do
+ k = Chef::Knife::CookbookUpload.new
+ k.name_args = name_args
+ allow(k.ui).to receive(:stdout).and_return(output)
+ allow(k.ui).to receive(:stderr).and_return(output)
+ k
+ end
+
+ before(:each) do
+ allow(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader)
+ allow(Chef::CookbookLoader).to receive(:copy_to_tmp_dir_from_array).and_yield(cookbook_loader)
+ end
+
+ describe "with --concurrency" do
+ it "should upload cookbooks with predefined concurrency" do
+ allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
+ knife.config[:concurrency] = 3
+ test_cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah")
+ allow(cookbook_loader).to receive(:each).and_yield("test_cookbook", test_cookbook)
+ allow(cookbook_loader).to receive(:cookbook_names).and_return(["test_cookbook"])
+ expect(Chef::CookbookUploader).to receive(:new)
+ .with( kind_of(Array), { force: nil, concurrency: 3 })
+ .and_return(double("Chef::CookbookUploader", upload_cookbooks: true))
+ knife.run
+ end
+ end
+
+ describe "run" do
+ before(:each) do
+ allow(Chef::CookbookUploader).to receive_messages(new: cookbook_uploader)
+ allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
+ end
+
+ it "should print usage and exit when a cookbook name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "when specifying cookbook without metadata.rb or metadata.json" do
+ let(:name_args) { ["test_cookbook1"] }
+ let(:cookbook) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(false)
+ cookbook
+ end
+
+ it "should upload the cookbook" do
+ expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotFound)
+ end
+ end
+
+ describe "when name attribute in metadata not set" do
+ let(:name_args) { ["test_cookbook1"] }
+
+ let(:cookbook) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(nil)
+ cookbook
+ end
+
+ it "should upload the cookbook" do
+ expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotValid)
+ end
+ end
+
+ describe "when specifying a cookbook name" do
+ it "should upload the cookbook" do
+ expect(knife).to receive(:upload).once
+ knife.run
+ end
+
+ it "should report on success" do
+ expect(knife).to receive(:upload).once
+ expect(knife.ui).to receive(:info).with(/Uploaded 1 cookbook/)
+ knife.run
+ end
+ end
+
+ describe "when specifying the same cookbook name twice" do
+ it "should upload the cookbook only once" do
+ knife.name_args = %w{test_cookbook test_cookbook}
+ expect(knife).to receive(:upload).once
+ knife.run
+ end
+ end
+
+ describe "when specifying a cookbook name among many" do
+ let(:name_args) { ["test_cookbook1"] }
+
+ let(:cookbook) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
+ cookbook
+ end
+
+ let(:cookbooks_by_name) do
+ { cookbook.name => cookbook }
+ end
+
+ it "should read only one cookbook" do
+ expect(cookbook_loader).to receive(:[]).once.with("test_cookbook1").and_call_original
+ knife.run
+ end
+
+ it "should not read all cookbooks" do
+ expect(cookbook_loader).to receive(:load_cookbooks)
+ knife.run
+ end
+
+ it "should upload only one cookbook" do
+ expect(knife).to receive(:upload).exactly(1).times
+ knife.run
+ end
+ end
+
+ # This is testing too much. We should break it up.
+ describe "when specifying a cookbook name with dependencies" do
+ let(:name_args) { ["test_cookbook2"] }
+
+ let(:test_cookbook1) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
+ cookbook
+ end
+
+ let(:test_cookbook2) do
+ c = Chef::CookbookVersion.new("test_cookbook2")
+ c.metadata.depends("test_cookbook3")
+ allow(c).to receive(:has_metadata_file?).and_return(true)
+ allow(c.metadata).to receive(:name).and_return(c.name)
+ c
+ end
+
+ let(:test_cookbook3) do
+ c = Chef::CookbookVersion.new("test_cookbook3")
+ c.metadata.depends("test_cookbook1")
+ c.metadata.depends("test_cookbook2")
+ allow(c).to receive(:has_metadata_file?).and_return(true)
+ allow(c.metadata).to receive(:name).and_return(c.name)
+ c
+ end
+
+ let(:cookbooks_by_name) do
+ { "test_cookbook1" => test_cookbook1,
+ "test_cookbook2" => test_cookbook2,
+ "test_cookbook3" => test_cookbook3 }
+ end
+
+ it "should upload all dependencies once" do
+ knife.config[:depends] = true
+ allow(knife).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2 test_cookbook3})
+ expect(knife).to receive(:upload).exactly(3).times
+ expect do
+ Timeout.timeout(5) do
+ knife.run
+ end
+ end.not_to raise_error
+ end
+ end
+
+ describe "when specifying a cookbook name with missing dependencies" do
+ let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") }
+
+ before(:each) do
+ cookbook.metadata.depends("dependency")
+ allow(cookbook_loader).to receive(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency }[ckbk]
+ end
+ allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook})
+ @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
+ knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
+ end
+
+ it "should exit and not upload the cookbook" do
+ expect(cookbook_loader).to receive(:[]).once.with("test_cookbook")
+ expect(cookbook_uploader).not_to receive(:upload_cookbooks)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should output a message for a single missing dependency" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
+ expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
+ expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
+ end
+
+ it "should output a message for a multiple missing dependencies which are concatenated" do
+ cookbook_dependency2 = Chef::CookbookVersion.new("dependency2")
+ cookbook.metadata.depends("dependency2")
+ allow(cookbook_loader).to receive(:[]) do |ckbk|
+ { "test_cookbook" => cookbook,
+ "dependency" => cookbook_dependency,
+ "dependency2" => cookbook_dependency2 }[ckbk]
+ end
+ allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook})
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
+ expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
+ expect(@stderr.string).to include("The missing cookbook(s) are:")
+ expect(@stderr.string).to include("'dependency' version '>= 0.0.0'")
+ expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'")
+ end
+ end
+
+ it "should freeze the version of the cookbooks if --freeze is specified" do
+ knife.config[:freeze] = true
+ expect(cookbook_loader).to receive(:freeze_versions).once
+ knife.run
+ end
+
+ describe "with -a or --all" do
+ before(:each) do
+ knife.config[:all] = true
+ end
+
+ context "when cookbooks exist in the cookbook path" do
+ let(:test_cookbook1) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
+ cookbook
+ end
+
+ let(:test_cookbook2) do
+ cookbook = Chef::CookbookVersion.new("test_cookbook2", "/tmp/blah")
+ allow(cookbook).to receive(:has_metadata_file?).and_return(true)
+ allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
+ cookbook
+ end
+
+ before(:each) do
+ allow(cookbook_loader).to receive(:each).and_yield("test_cookbook1", test_cookbook1).and_yield("test_cookbook2", test_cookbook2)
+ allow(cookbook_loader).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2})
+ end
+
+ it "should upload all cookbooks" do
+ expect(knife).to receive(:upload).once
+ knife.run
+ end
+
+ it "should report on success" do
+ expect(knife).to receive(:upload).once
+ expect(knife.ui).to receive(:info).with(/Uploaded all cookbooks/)
+ knife.run
+ end
+
+ it "should update the version constraints for an environment" do
+ allow(knife).to receive(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ expect(knife).to receive(:update_version_constraints).once
+ knife.run
+ end
+ end
+
+ context "when no cookbooks exist in the cookbook path" do
+ before(:each) do
+ allow(cookbook_loader).to receive(:each)
+ end
+
+ it "should not upload any cookbooks" do
+ expect(knife).to_not receive(:upload)
+ knife.run
+ end
+
+ context "when cookbook path is an array" do
+ it "should warn users that no cookbooks exist" do
+ cookbook_path = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks"
+ knife.config[:cookbook_path] = [cookbook_path, "/home/user/cookbooks"]
+ expect(knife.ui).to receive(:warn).with("Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path].join(", ")}'. Use --cookbook-path to specify the desired path.")
+ knife.run
+ end
+ end
+
+ context "when cookbook path is a string" do
+ it "should warn users that no cookbooks exist" do
+ knife.config[:cookbook_path] = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks"
+ expect(knife.ui).to receive(:warn).with(
+ "Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path]}'. Use --cookbook-path to specify the desired path."
+ )
+ knife.run
+ end
+ end
+ end
+ end
+
+ describe "when a frozen cookbook exists on the server" do
+ it "should fail to replace it" do
+ exception = Chef::Exceptions::CookbookFrozen.new
+ expect(cookbook_uploader).to receive(:upload_cookbooks)
+ .and_raise(exception)
+ allow(knife.ui).to receive(:error)
+ expect(knife.ui).to receive(:error).with(exception)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should not update the version constraints for an environment" do
+ allow(knife).to receive(:assert_environment_valid!).and_return(true)
+ knife.config[:environment] = "production"
+ allow(knife).to receive(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
+ expect(knife.ui).to receive(:error).with(/Failed to upload 1 cookbook/)
+ expect(knife.ui).to receive(:warn).with(/Not updating version constraints/)
+ expect(knife).not_to receive(:update_version_constraints)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+ end
+ end # run
+end
diff --git a/knife/spec/unit/knife/core/bootstrap_context_spec.rb b/knife/spec/unit/knife/core/bootstrap_context_spec.rb
new file mode 100644
index 0000000000..79fddc8184
--- /dev/null
+++ b/knife/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -0,0 +1,287 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/core/bootstrap_context"
+
+describe Chef::Knife::Core::BootstrapContext do
+ let(:config) { { foo: :bar, color: true } }
+ let(:run_list) { Chef::RunList.new("recipe[tmux]", "role[base]") }
+ let(:chef_config) do
+ {
+ config_log_level: "info",
+ config_log_location: "/tmp/log",
+ validation_key: File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"),
+ chef_server_url: "http://chef.example.com:4444",
+ validation_client_name: "chef-validator-testing",
+ }
+ end
+
+ let(:secret) { nil }
+
+ subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) }
+
+ it "initializes with Chef 11 parameters" do
+ expect { described_class.new(config, run_list, chef_config) }.not_to raise_error
+ end
+
+ it "runs chef with the first-boot.json with no environment" do
+ expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json"
+ end
+
+ describe "when in verbosity mode" do
+ let(:config) { { verbosity: 2, color: true } }
+ it "adds '-l debug' when verbosity is >= 2" do
+ expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug"
+ end
+ end
+
+ describe "when no color value has been set in config" do
+ let(:config) { { color: false } }
+ it "adds '--no-color' when color is false" do
+ expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color"
+ end
+ end
+
+ it "reads the validation key" do
+ expect(bootstrap_context.validation_key).to eq IO.read(File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"))
+ end
+
+ it "generates the config file data" do
+ expected = <<~EXPECTED
+ chef_server_url "http://chef.example.com:4444"
+ validation_client_name "chef-validator-testing"
+ log_level :info
+ log_location "/tmp/log"
+ # Using default node name (fqdn)
+ EXPECTED
+ expect(bootstrap_context.config_content).to eq expected
+ end
+
+ describe "when chef_license is set" do
+ let(:chef_config) { { chef_license: "accept-no-persist" } }
+ it "sets chef_license in the generated config file" do
+ expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"")
+ end
+ end
+
+ describe "when file_cache_path is set" do
+ let(:chef_config) { { file_cache_path: "/home/opscode/cache" } }
+ it "sets file_cache_path in the generated config file" do
+ expect(bootstrap_context.config_content).to include("file_cache_path \"/home/opscode/cache\"")
+ end
+ end
+
+ describe "when file_backup_path is set" do
+ let(:chef_config) { { file_backup_path: "/home/opscode/backup" } }
+ it "sets file_backup_path in the generated config file" do
+ expect(bootstrap_context.config_content).to include("file_backup_path \"/home/opscode/backup\"")
+ end
+ end
+
+ describe "alternate chef-client path" do
+ let(:chef_config) { { chef_client_path: "/usr/local/bin/chef-client" } }
+ it "runs chef-client from another path when specified" do
+ expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json"
+ end
+ end
+
+ describe "validation key path that contains a ~" do
+ let(:chef_config) { { validation_key: "~/my.key" } }
+ it "reads the validation key when it contains a ~" do
+ expect(File).to receive(:exist?).with(File.expand_path("my.key", ENV["HOME"])).and_return(true)
+ expect(IO).to receive(:read).with(File.expand_path("my.key", ENV["HOME"]))
+ bootstrap_context.validation_key
+ end
+ end
+
+ describe "when an explicit node name is given" do
+ let(:config) { { chef_node_name: "foobar.example.com" } }
+ it "sets the node name in the client.rb" do
+ expect(bootstrap_context.config_content).to match(/node_name "foobar\.example\.com"/)
+ end
+ end
+
+ describe "when bootstrapping into a specific environment" do
+ let(:config) { { environment: "prodtastic", color: true } }
+ it "starts chef in the configured environment" do
+ expect(bootstrap_context.start_chef).to eq("chef-client -j /etc/chef/first-boot.json -E prodtastic")
+ end
+ end
+
+ describe "when tags are given" do
+ let(:config) { { tags: [ "unicorn" ] } }
+ it "adds the attributes to first_boot" do
+ expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list, tags: ["unicorn"] }))
+ end
+ end
+
+ describe "when JSON attributes are given" do
+ let(:config) { { first_boot_attributes: { baz: :quux } } }
+ it "adds the attributes to first_boot" do
+ expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ baz: :quux, run_list: run_list }))
+ end
+ end
+
+ describe "when JSON attributes are NOT given" do
+ it "sets first_boot equal to run_list" do
+ expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list }))
+ end
+ end
+
+ describe "when policy_name and policy_group are present in config" do
+
+ let(:config) { { policy_name: "my_app_server", policy_group: "staging" } }
+
+ it "includes them in the first_boot data and excludes run_list" do
+ expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ policy_name: "my_app_server", policy_group: "staging" }))
+ end
+
+ end
+
+ describe "when an encrypted_data_bag_secret is provided" do
+ let(:secret) { "supersekret" }
+ it "reads the encrypted_data_bag_secret" do
+ expect(bootstrap_context.encrypted_data_bag_secret).to eq "supersekret"
+ end
+ end
+
+ describe "to support compatibility with existing templates" do
+ it "sets the @config instance variable" do
+ expect(bootstrap_context.instance_variable_get(:@config)).to eq config
+ end
+
+ it "sets the @run_list instance variable" do
+ expect(bootstrap_context.instance_variable_get(:@run_list)).to eq run_list
+ end
+ end
+
+ describe "ssl_verify_mode" do
+ it "isn't set in the config_content by default" do
+ expect(bootstrap_context.config_content).not_to include("ssl_verify_mode")
+ end
+
+ describe "when configured via the config hash" do
+ let(:config) { { node_ssl_verify_mode: "none" } }
+
+ it "uses the config value" do
+ expect(bootstrap_context.config_content).to include("ssl_verify_mode :verify_none")
+ end
+ end
+ end
+
+ describe "fips mode" do
+ before do
+ chef_config[:fips] = true
+ end
+
+ it "sets fips mode in the client.rb" do
+ expect(bootstrap_context.config_content).to match(/fips true/)
+ end
+ end
+
+ describe "verify_api_cert" do
+ it "isn't set in the config_content by default" do
+ expect(bootstrap_context.config_content).not_to include("verify_api_cert")
+ end
+
+ describe "when configured via the config hash" do
+ let(:config) { { node_verify_api_cert: true } }
+
+ it "uses config value" do
+ expect(bootstrap_context.config_content).to include("verify_api_cert true")
+ end
+ end
+ end
+
+ describe "#config_log_location" do
+ context "when config_log_location is nil" do
+ let(:chef_config) { { config_log_location: nil } }
+ it "sets the default config_log_location in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq "STDOUT"
+ end
+ end
+
+ context "when config_log_location is empty" do
+ let(:chef_config) { { config_log_location: "" } }
+ it "sets the default config_log_location in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq "STDOUT"
+ end
+ end
+
+ context "when config_log_location is :win_evt" do
+ let(:chef_config) { { config_log_location: :win_evt } }
+ it "raise error when config_log_location is :win_evt " do
+ expect { bootstrap_context.get_log_location }.to raise_error("The value :win_evt is not supported for config_log_location on Linux Platforms \n")
+ end
+ end
+
+ context "when config_log_location is :syslog" do
+ let(:chef_config) { { config_log_location: :syslog } }
+ it "sets the config_log_location value as :syslog in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq ":syslog"
+ end
+ end
+
+ context "When config_log_location is STDOUT" do
+ let(:chef_config) { { config_log_location: STDOUT } }
+ it "Sets the config_log_location value as STDOUT in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq "STDOUT"
+ end
+ end
+
+ context "when config_log_location is STDERR" do
+ let(:chef_config) { { config_log_location: STDERR } }
+ it "sets the config_log_location value as STDERR in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq "STDERR"
+ end
+ end
+
+ context "when config_log_location is a path" do
+ let(:chef_config) { { config_log_location: "/tmp/ChefLogFile" } }
+ it "sets the config_log_location path in the client.rb" do
+ expect(bootstrap_context.get_log_location).to eq "\"/tmp/ChefLogFile\""
+ end
+ end
+
+ end
+
+ describe "#version_to_install" do
+ context "when bootstrap_version is provided" do
+ let(:config) { { bootstrap_version: "awesome" } }
+
+ it "returns bootstrap_version" do
+ expect(bootstrap_context.version_to_install).to eq "awesome"
+ end
+ end
+
+ context "when bootstrap_version is not provided" do
+ let(:config) { { channel: "stable" } }
+ it "returns the currently running major version out of Chef::VERSION" do
+ expect(bootstrap_context.version_to_install).to eq Chef::VERSION.split(".").first
+ end
+ end
+
+ context "and channel is other than stable" do
+ let(:config) { { channel: "unstable" } }
+ it "returns the version string 'latest'" do
+ expect(bootstrap_context.version_to_install).to eq "latest"
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb
new file mode 100644
index 0000000000..68a155bbbe
--- /dev/null
+++ b/knife/spec/unit/knife/core/cookbook_scm_repo_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/core/cookbook_scm_repo"
+
+describe Chef::Knife::CookbookSCMRepo do
+ before do
+ @repo_path = File.join(CHEF_SPEC_DATA, "cookbooks")
+ @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
+ @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
+ @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "master")
+
+ @branch_list = Mixlib::ShellOut.new
+ @branch_list.stdout.replace(<<-BRANCHES)
+ chef-vendor-apache2
+ chef-vendor-build-essential
+ chef-vendor-dynomite
+ chef-vendor-ganglia
+ chef-vendor-graphite
+ chef-vendor-python
+ chef-vendor-absent-new
+ BRANCHES
+ end
+
+ it "has a path to the cookbook repo" do
+ expect(@cookbook_repo.repo_path).to eq(@repo_path)
+ end
+
+ it "has a default branch" do
+ expect(@cookbook_repo.default_branch).to eq("master")
+ end
+
+ describe "when sanity checking the repo" do
+ it "exits when the directory does not exist" do
+ expect(::File).to receive(:directory?).with(@repo_path).and_return(false)
+ expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
+ end
+
+ describe "and the repo dir exists" do
+ before do
+ allow(::File).to receive(:directory?).with(@repo_path).and_return(true)
+ end
+
+ it "exits when there is no git repo" do
+ allow(::File).to receive(:directory?).with(/.*\.git/).and_return(false)
+ expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
+ end
+
+ describe "and the repo is a git repo" do
+ before do
+ allow(::File).to receive(:directory?).with(File.join(@repo_path, ".git")).and_return(true)
+ end
+
+ it "exits when the default branch doesn't exist" do
+ @nobranches = Mixlib::ShellOut.new.tap { |s| s.stdout.replace "\n" }
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@nobranches)
+ expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
+ end
+
+ describe "and the default branch exists" do
+ before do
+ @master_branch = Mixlib::ShellOut.new
+ @master_branch.stdout.replace "* master\n"
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@master_branch)
+ end
+
+ it "exits when the git repo is dirty" do
+ @dirty_status = Mixlib::ShellOut.new
+ @dirty_status.stdout.replace(<<-DIRTY)
+ M chef/lib/chef/knife/cookbook_site_install.rb
+ DIRTY
+ expect(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@dirty_status)
+ expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
+ end
+
+ describe "and the repo is clean" do
+ before do
+ @clean_status = Mixlib::ShellOut.new.tap { |s| s.stdout.replace("\n") }
+ allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@clean_status)
+ end
+
+ it "passes the sanity check" do
+ @cookbook_repo.sanity_check
+ end
+
+ end
+ end
+ end
+ end
+ end
+
+ it "resets to default state by checking out the default branch" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git checkout master", cwd: @repo_path)
+ @cookbook_repo.reset_to_default_state
+ end
+
+ it "determines if a the pristine copy branch exists" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
+ expect(@cookbook_repo.branch_exists?("chef-vendor-apache2")).to be_truthy
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
+ expect(@cookbook_repo.branch_exists?("chef-vendor-nginx")).to be_falsey
+ end
+
+ it "determines if a the branch not exists correctly without substring search" do
+ expect(@cookbook_repo).to receive(:shell_out!).twice.with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
+ expect(@cookbook_repo).not_to be_branch_exists("chef-vendor-absent")
+ expect(@cookbook_repo).to be_branch_exists("chef-vendor-absent-new")
+ end
+
+ describe "when the pristine copy branch does not exist" do
+ it "prepares for import by creating the pristine copy branch" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
+ expect(@cookbook_repo).to receive(:shell_out!).with("git checkout -b chef-vendor-nginx", cwd: @repo_path)
+ @cookbook_repo.prepare_to_import("nginx")
+ end
+ end
+
+ describe "when the pristine copy branch does exist" do
+ it "prepares for import by checking out the pristine copy branch" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
+ expect(@cookbook_repo).to receive(:shell_out!).with("git checkout chef-vendor-apache2", cwd: @repo_path)
+ @cookbook_repo.prepare_to_import("apache2")
+ end
+ end
+
+ describe "when the pristine copy branch was not updated by the changes" do
+ before do
+ @updates = Mixlib::ShellOut.new
+ @updates.stdout.replace("\n")
+ allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates)
+ end
+
+ it "shows no changes in the pristine copy" do
+ expect(@cookbook_repo.updated?("apache2")).to be_falsey
+ end
+
+ it "does nothing to finalize the updates" do
+ expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_falsey
+ end
+ end
+
+ describe "when the pristine copy branch was updated by the changes" do
+ before do
+ @updates = Mixlib::ShellOut.new
+ @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n")
+ allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates)
+ end
+
+ it "shows changes in the pristine copy" do
+ expect(@cookbook_repo.updated?("apache2")).to be_truthy
+ end
+
+ it "commits the changes to the repo and tags the commit" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git add apache2", cwd: @repo_path)
+ expect(@cookbook_repo).to receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", cwd: @repo_path)
+ expect(@cookbook_repo).to receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", cwd: @repo_path)
+ expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_truthy
+ end
+ end
+
+ describe "when a custom default branch is specified" do
+ before do
+ @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "develop")
+ end
+
+ it "resets to default state by checking out the default branch" do
+ expect(@cookbook_repo).to receive(:shell_out!).with("git checkout develop", cwd: @repo_path)
+ @cookbook_repo.reset_to_default_state
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb b/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb
new file mode 100644
index 0000000000..f40626990a
--- /dev/null
+++ b/knife/spec/unit/knife/core/cookbook_site_streaming_uploader_spec.rb
@@ -0,0 +1,198 @@
+#
+# Author:: Xabier de Zuazo (xabier@onddo.com)
+# Copyright:: Copyright 2013-2016, Onddo Labs, SL.
+# 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 "knife_spec_helper"
+
+require "chef/knife/core/cookbook_site_streaming_uploader"
+
+class FakeTempfile
+ def initialize(basename)
+ @basename = basename
+ end
+
+ def close; end
+
+ def path
+ "#{@basename}.ZZZ"
+ end
+
+end
+
+describe Chef::Knife::Core::CookbookSiteStreamingUploader do
+
+ let(:subject) { Chef::Knife::Core::CookbookSiteStreamingUploader }
+ describe "create_build_dir" do
+ before(:each) do
+ @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ @loader = Chef::CookbookLoader.new(@cookbook_repo)
+ @loader.load_cookbooks
+ allow(File).to receive(:unlink)
+ end
+
+ it "should create the cookbook tmp dir" do
+ cookbook = @loader[:openldap]
+ files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, "**", "*"), File::FNM_DOTMATCH).count { |file| File.file?(file) }
+
+ expect(Tempfile).to receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build"))
+ expect(FileUtils).to receive(:mkdir_p).exactly(files_count + 1).times
+ expect(FileUtils).to receive(:cp).exactly(files_count).times
+ subject.create_build_dir(cookbook)
+ end
+
+ end # create_build_dir
+
+ describe "make_request" do
+
+ before(:each) do
+ @uri = "http://cookbooks.dummy.com/api/v1/cookbooks"
+ @secret_filename = File.join(CHEF_SPEC_DATA, "ssl/private_key.pem")
+ @rsa_key = File.read(@secret_filename)
+ response = Net::HTTPResponse.new("1.0", "200", "OK")
+ allow_any_instance_of(Net::HTTP).to receive(:request).and_return(response)
+ end
+
+ it "should send an http request" do
+ expect_any_instance_of(Net::HTTP).to receive(:request)
+ subject.make_request(:post, @uri, "bill", @secret_filename)
+ end
+
+ it "should read the private key file" do
+ expect(File).to receive(:read).with(@secret_filename).and_return(@rsa_key)
+ subject.make_request(:post, @uri, "bill", @secret_filename)
+ end
+
+ it "should add the authentication signed header" do
+ expect_any_instance_of(Mixlib::Authentication::SigningObject).to receive(:sign).and_return({})
+ subject.make_request(:post, @uri, "bill", @secret_filename)
+ end
+
+ it "should be able to send post requests" do
+ post = Net::HTTP::Post.new(@uri, {})
+
+ expect(Net::HTTP::Post).to receive(:new).once.and_return(post)
+ expect(Net::HTTP::Put).not_to receive(:new)
+ expect(Net::HTTP::Get).not_to receive(:new)
+ subject.make_request(:post, @uri, "bill", @secret_filename)
+ end
+
+ it "should be able to send put requests" do
+ put = Net::HTTP::Put.new(@uri, {})
+
+ expect(Net::HTTP::Post).not_to receive(:new)
+ expect(Net::HTTP::Put).to receive(:new).once.and_return(put)
+ expect(Net::HTTP::Get).not_to receive(:new)
+ subject.make_request(:put, @uri, "bill", @secret_filename)
+ end
+
+ it "should be able to receive files to attach as argument" do
+ subject.make_request(:put, @uri, "bill", @secret_filename, {
+ myfile: File.new(File.join(CHEF_SPEC_DATA, "config.rb")), # a dummy file
+ })
+ end
+
+ it "should be able to receive strings to attach as argument" do
+ subject.make_request(:put, @uri, "bill", @secret_filename, {
+ mystring: "Lorem ipsum",
+ })
+ end
+
+ it "should be able to receive strings and files as argument at the same time" do
+ subject.make_request(:put, @uri, "bill", @secret_filename, {
+ myfile1: File.new(File.join(CHEF_SPEC_DATA, "config.rb")),
+ mystring1: "Lorem ipsum",
+ myfile2: File.new(File.join(CHEF_SPEC_DATA, "config.rb")),
+ mystring2: "Dummy text",
+ })
+ end
+
+ end # make_request
+
+ describe "StreamPart" do
+ before(:each) do
+ @file = File.new(File.join(CHEF_SPEC_DATA, "config.rb"))
+ @stream_part = Chef::Knife::Core::CookbookSiteStreamingUploader::StreamPart.new(@file, File.size(@file))
+ end
+
+ it "should create a StreamPart" do
+ expect(@stream_part).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::StreamPart)
+ end
+
+ it "should expose its size" do
+ expect(@stream_part.size).to eql(File.size(@file))
+ end
+
+ it "should read with offset and how_much" do
+ content = @file.read(4)
+ @file.rewind
+ expect(@stream_part.read(0, 4)).to eql(content)
+ end
+
+ end # StreamPart
+
+ describe "StringPart" do
+ before(:each) do
+ @str = "What a boring string"
+ @string_part = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@str)
+ end
+
+ it "should create a StringPart" do
+ expect(@string_part).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart)
+ end
+
+ it "should expose its size" do
+ expect(@string_part.size).to eql(@str.size)
+ end
+
+ it "should read with offset and how_much" do
+ expect(@string_part.read(2, 4)).to eql(@str[2, 4])
+ end
+
+ end # StringPart
+
+ describe "MultipartStream" do
+ before(:each) do
+ @string1 = "stream1"
+ @string2 = "stream2"
+ @stream1 = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@string1)
+ @stream2 = Chef::Knife::Core::CookbookSiteStreamingUploader::StringPart.new(@string2)
+ @parts = [ @stream1, @stream2 ]
+
+ @multipart_stream = Chef::Knife::Core::CookbookSiteStreamingUploader::MultipartStream.new(@parts)
+ end
+
+ it "should create a MultipartStream" do
+ expect(@multipart_stream).to be_instance_of(Chef::Knife::Core::CookbookSiteStreamingUploader::MultipartStream)
+ end
+
+ it "should expose its size" do
+ expect(@multipart_stream.size).to eql(@stream1.size + @stream2.size)
+ end
+
+ it "should read with how_much" do
+ expect(@multipart_stream.read(10)).to eql("#{@string1}#{@string2}"[0, 10])
+ end
+
+ it "should read receiving destination buffer as second argument (CHEF-4456: Ruby 2 compat)" do
+ dst_buf = ""
+ @multipart_stream.read(10, dst_buf)
+ expect(dst_buf).to eql("#{@string1}#{@string2}"[0, 10])
+ end
+
+ end # MultipartStream
+
+end
diff --git a/knife/spec/unit/knife/core/gem_glob_loader_spec.rb b/knife/spec/unit/knife/core/gem_glob_loader_spec.rb
new file mode 100644
index 0000000000..072dac3986
--- /dev/null
+++ b/knife/spec/unit/knife/core/gem_glob_loader_spec.rb
@@ -0,0 +1,242 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::SubcommandLoader::GemGlobLoader do
+ let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) }
+ let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") }
+ let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") }
+
+ before do
+ allow(ChefUtils).to receive(:windows?) { false }
+ ChefConfig::PathHelper.class_variable_set(:@@home_dir, home)
+ end
+
+ after do
+ ChefConfig::PathHelper.class_variable_set(:@@home_dir, nil)
+ end
+
+ it "builds a list of the core subcommand file require paths" do
+ expect(loader.subcommand_files).not_to be_empty
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
+ end
+ end
+
+ it "finds files installed via rubygems" do
+ expect(loader.find_subcommands_via_rubygems).to include("chef/knife/node_create")
+ loader.find_subcommands_via_rubygems.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) }
+ end
+
+ it "finds files from latest version of installed gems" do
+ gems = [ double("knife-ec2-0.5.12") ]
+ gem_files = [
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb",
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb",
+ ]
+ expect($LOAD_PATH).to receive(:map).and_return([])
+ if Gem::Specification.respond_to? :latest_specs
+ expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:matches_for_glob).with(%r{chef/knife/\*\.rb\{(.*),\.rb,(.*)\}}).and_return(gem_files)
+ else
+ expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"])
+ expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12")
+ expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files)
+ end
+ expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
+ expect(loader.subcommand_files.select { |file| file.include?("knife-ec2") }.sort).to eq(gem_files)
+ end
+ it "excludes knife version file if loaded from a gem" do
+ gems = [ double("knife-ec2-0.5.12") ]
+ gem_files = [
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb",
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb",
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/version.rb",
+ ]
+ expected_files = [
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb",
+ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb",
+ ]
+
+ expect($LOAD_PATH).to receive(:map).and_return([])
+ if Gem::Specification.respond_to? :latest_specs
+ expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:matches_for_glob).with(%r{chef/knife/\*\.rb\{(.*),\.rb,(.*)\}}).and_return(gem_files)
+ else
+ expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"])
+ expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12")
+ expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files)
+ end
+ expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
+ expect(loader.subcommand_files.select { |file| file.include?("knife-ec2") }.sort).to eq(expected_files)
+ end
+
+ it "finds files using a dirglob when rubygems is not available" do
+ expect(loader.find_subcommands_via_dirglob).to include("chef/knife/node_create")
+ loader.find_subcommands_via_dirglob.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) }
+ end
+
+ it "excludes chef/knife/version.rb using a dirglob when rubygems is not available" do
+ expect(loader.find_subcommands_via_dirglob).to_not include("chef/knife/version")
+ loader.find_subcommands_via_dirglob.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) }
+ end
+
+ it "finds user-specific subcommands in the user's ~/.chef directory" do
+ expected_command = File.join(home, ".chef", "plugins", "knife", "example_home_subcommand.rb")
+ expect(loader.site_subcommands).to include(expected_command)
+ end
+
+ it "finds repo specific subcommands by searching for a .chef directory" do
+ expected_command = File.join(CHEF_SPEC_DATA, "knife-site-subcommands", "plugins", "knife", "example_subcommand.rb")
+ expect(loader.site_subcommands).to include(expected_command)
+ end
+
+ # https://github.com/chef/chef-dk/issues/227
+ #
+ # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
+ # of the source, but there can be one or more versions of chef also installed
+ # as a gem. If the gem install contains a command that doesn't exist in the
+ # source tree of the "primary" chef install, it can be loaded and cause an
+ # error. We also want to ensure that we only load builtin commands from the
+ # "primary" chef install.
+ #
+ # NOTE - we need to revisit coverage now that we're moving knife to its own gem;
+ # or remove this test if it's no longer a supported scenario.
+ context "when a different version of chef is also installed as a gem" do
+
+ let(:all_found_commands) do
+ [
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+
+ # We use the fake version 1.0.0 because that version doesn't exist,
+ # which ensures it won't ever equal "chef-#{Chef::VERSION}"
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
+
+ # Test that we don't accept a version number that is different only in
+ # trailing characters, e.g. we are running Chef 12.0.0 but there is a
+ # Chef 12.0.0.rc.0 gem also:
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
+
+ # Test that we ignore the platform suffix when checking for different
+ # gem versions.
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+ # ...but don't ignore the .rc / .dev parts in the case when we have
+ # platform suffixes
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
+
+ # This command is "extra" compared to what's in the embedded/apps/chef install:
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
+
+ # These are fake commands that have names designed to test that the
+ # regex is strict enough
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+
+ # In a real scenario, we'd use rubygems APIs to only select the most
+ # recent gem, but for this test we want to check that we're doing the
+ # right thing both when the plugin version matches and does not match
+ # the current chef version. Looking at
+ # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and
+ # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want
+ # to test these two cases.
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb",
+ ]
+ end
+
+ let(:expected_valid_commands) do
+ [
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb",
+ ]
+ end
+
+ before do
+ expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
+ expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
+ end
+
+ it "ignores commands from the non-matching gem install" do
+ expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
+ end
+
+ end
+
+ describe "finding 3rd party plugins" do
+ let(:env_home) { "/home/alice" }
+ let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
+
+ before do
+ env_dup = ENV.to_hash
+ allow(ENV).to receive(:[]) { |key| env_dup[key] }
+ allow(ENV).to receive(:[]).with("HOME").and_return(env_home)
+ end
+
+ it "searches rubygems for plugins" do
+ if Gem::Specification.respond_to?(:latest_specs)
+ expect(Gem::Specification).to receive(:latest_specs).and_call_original
+ else
+ expect(Gem.source_index).to receive(:latest_specs).and_call_original
+ end
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
+ end
+ end
+
+ context "and HOME environment variable is not set" do
+ before do
+ allow(ENV).to receive(:[]).with("HOME").and_return(nil)
+ end
+
+ it "searches rubygems for plugins" do
+ if Gem::Specification.respond_to?(:latest_specs)
+ expect(Gem::Specification).to receive(:latest_specs).and_call_original
+ else
+ expect(Gem.source_index).to receive(:latest_specs).and_call_original
+ end
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/hashed_command_loader_spec.rb b/knife/spec/unit/knife/core/hashed_command_loader_spec.rb
new file mode 100644
index 0000000000..305c928309
--- /dev/null
+++ b/knife/spec/unit/knife/core/hashed_command_loader_spec.rb
@@ -0,0 +1,112 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::SubcommandLoader::HashedCommandLoader do
+ before do
+ allow(ChefUtils).to receive(:windows?) { false }
+ end
+
+ let(:plugin_manifest) do
+ {
+ "_autogenerated_command_paths" => {
+ "plugins_paths" => {
+ "cool_a" => ["/file/for/plugin/a"],
+ "cooler_b" => ["/file/for/plugin/b"],
+ },
+ "plugins_by_category" => {
+ "cool" => [
+ "cool_a",
+ ],
+ "cooler" => [
+ "cooler_b",
+ ],
+ },
+ },
+ }
+ end
+
+ let(:loader) do
+ Chef::Knife::SubcommandLoader::HashedCommandLoader.new(
+ File.join(CHEF_SPEC_DATA, "knife-site-subcommands"),
+ plugin_manifest
+ )
+ end
+
+ describe "#list_commands" do
+ before do
+ allow(File).to receive(:exist?).and_return(true)
+ end
+
+ it "lists all commands by category when no argument is given" do
+ expect(loader.list_commands).to eq({ "cool" => ["cool_a"], "cooler" => ["cooler_b"] })
+ end
+
+ it "lists only commands in the given category when a category is given" do
+ expect(loader.list_commands("cool")).to eq({ "cool" => ["cool_a"] })
+ end
+
+ context "when the plugin path is invalid" do
+ before do
+ expect(File).to receive(:exist?).with("/file/for/plugin/b").and_return(false)
+ end
+
+ it "lists all commands by category when no argument is given" do
+ expect(Chef::Log).to receive(:error).with(/There are plugin files specified in the knife cache that cannot be found/)
+ expect(Chef::Log).to receive(:error).with("Missing files:\n\t/file/for/plugin/b")
+ expect(loader.list_commands).to eq({})
+ end
+ end
+ end
+
+ describe "#subcommand_files" do
+ it "lists all the files" do
+ expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"])
+ end
+ end
+
+ describe "#load_commands" do
+ before do
+ allow(Kernel).to receive(:load).and_return(true)
+ end
+
+ it "returns false for non-existant commands" do
+ expect(loader.load_command(["nothere"])).to eq(false)
+ end
+
+ it "loads the correct file and returns true if the command exists" do
+ allow(File).to receive(:exist?).and_return(true)
+ expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true)
+ expect(loader.load_command(["cool_a"])).to eq(true)
+ end
+ end
+
+ describe "#subcommand_for_args" do
+ it "returns the subcommands for an exact match" do
+ expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b")
+ end
+
+ it "finds the right subcommand even when _'s are elided" do
+ expect(loader.subcommand_for_args(%w{cooler b})).to eq("cooler_b")
+ end
+
+ it "returns nil if the the subcommand isn't in our manifest" do
+ expect(loader.subcommand_for_args(["cooler c"])).to eq(nil)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/node_editor_spec.rb b/knife/spec/unit/knife/core/node_editor_spec.rb
new file mode 100644
index 0000000000..f4fbe76695
--- /dev/null
+++ b/knife/spec/unit/knife/core/node_editor_spec.rb
@@ -0,0 +1,211 @@
+#
+# Author:: Jordan Running (<jr@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/core/node_editor"
+
+describe Chef::Knife::NodeEditor do
+ let(:node_data) do
+ { "name" => "test_node",
+ "chef_environment" => "production",
+ "automatic" => { "foo" => "bar" },
+ "default" => { "alpha" => { "bravo" => "charlie", "delta" => "echo" } },
+ "normal" => { "alpha" => { "bravo" => "hotel" }, "tags" => [] },
+ "override" => { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } },
+ "policy_name" => nil,
+ "policy_group" => nil,
+ "run_list" => %w{role[comedy] role[drama] recipe[mystery]},
+ }
+ end
+
+ let(:node) { Chef::Node.from_hash(node_data) }
+
+ let(:ui) { double "ui" }
+ let(:base_config) { { editor: "cat" } }
+ let(:config) { base_config.merge(all_attributes: false) }
+
+ subject { described_class.new(node, ui, config) }
+
+ describe "#view" do
+ it "returns a Hash with only the name, chef_environment, normal, " +
+ "policy_name, policy_group, and run_list properties" do
+ expected = node_data.select do |key,|
+ %w{ name chef_environment normal
+ policy_name policy_group run_list }.include?(key)
+ end
+
+ expect(subject.view).to eq(expected)
+ end
+
+ context "when config[:all_attributes] == true" do
+ let(:config) { base_config.merge(all_attributes: true) }
+
+ it "returns a Hash with all of the node's properties" do
+ expect(subject.view).to eq(node_data)
+ end
+ end
+ end
+
+ describe "#apply_updates" do
+ context "when the node name is changed" do
+ before(:each) do
+ allow(ui).to receive(:warn)
+ allow(ui).to receive(:confirm).and_return(true)
+ end
+
+ it "emits a warning and prompts for confirmation" do
+ data = subject.view.merge("name" => "foo_new_name_node")
+ updated_node = subject.apply_updates(data)
+
+ expect(ui).to have_received(:warn)
+ .with "Changing the name of a node results in a new node being " +
+ "created, test_node will not be modified or removed."
+
+ expect(ui).to have_received(:confirm)
+ .with("Proceed with creation of new node")
+
+ expect(updated_node).to be_a(Chef::Node)
+ end
+ end
+
+ context "when config[:all_attributes] == false" do
+ let(:config) { base_config.merge(all_attributes: false) }
+
+ let(:updated_data) do
+ subject.view.merge(
+ "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
+ "policy_name" => "mypolicy",
+ "policy_group" => "prod",
+ "run_list" => %w{role[drama] recipe[mystery]}
+ )
+ end
+
+ it "returns a node with run_list and normal_attrs changed" do
+ updated_node = subject.apply_updates(updated_data)
+ expect(updated_node).to be_a(Chef::Node)
+
+ # Expected to have been changed
+ expect(updated_node.normal_attrs).to eql(updated_data["normal"])
+ expect(updated_node.policy_name).to eql(updated_data["policy_name"])
+ expect(updated_node.policy_group).to eql(updated_data["policy_group"])
+ expect(updated_node.chef_environment).to eql(updated_data["policy_group"])
+ expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"])
+
+ # Expected not to have changed
+ expect(updated_node.default_attrs).to eql(node.default_attrs)
+ expect(updated_node.override_attrs).to eql(node.override_attrs)
+ expect(updated_node.automatic_attrs).to eql(node.automatic_attrs)
+ end
+ end
+
+ context "when config[:all_attributes] == true" do
+ let(:config) { base_config.merge(all_attributes: true) }
+
+ let(:updated_data) do
+ subject.view.merge(
+ "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } },
+ "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
+ "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } },
+ "policy_name" => "mypolicy",
+ "policy_group" => "prod",
+ "run_list" => %w{role[drama] recipe[mystery]}
+ )
+ end
+
+ it "returns a node with all editable properties changed" do
+ updated_node = subject.apply_updates(updated_data)
+ expect(updated_node).to be_a(Chef::Node)
+
+ expect(updated_node.chef_environment).to eql(updated_data["policy_group"])
+ expect(updated_node.automatic_attrs).to eql(updated_data["automatic"])
+ expect(updated_node.normal_attrs).to eql(updated_data["normal"])
+ expect(updated_node.default_attrs).to eql(updated_data["default"])
+ expect(updated_node.override_attrs).to eql(updated_data["override"])
+ expect(updated_node.policy_name).to eql(updated_data["policy_name"])
+ expect(updated_node.policy_group).to eql(updated_data["policy_group"])
+ expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"])
+ end
+ end
+ end
+
+ describe "#updated?" do
+ context "before the node has been edited" do
+ it "returns false" do
+ expect(subject.updated?).to be false
+ end
+ end
+
+ context "after the node has been edited" do
+ context "and changes were made" do
+ let(:updated_data) do
+ subject.view.merge(
+ "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } },
+ "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
+ "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } },
+ "policy_name" => "mypolicy",
+ "policy_group" => "prod",
+ "run_list" => %w{role[drama] recipe[mystery]}
+ )
+ end
+
+ context "and changes affect only editable properties" do
+ before(:each) do
+ allow(ui).to receive(:edit_hash)
+ .with(subject.view)
+ .and_return(updated_data)
+
+ subject.edit_node
+ end
+
+ it "returns an array of the changed property names" do
+ expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list }
+ end
+ end
+
+ context "and the changes include non-editable properties" do
+ before(:each) do
+ data = updated_data.merge("bad_property" => "bad_value")
+
+ allow(ui).to receive(:edit_hash)
+ .with(subject.view)
+ .and_return(data)
+
+ subject.edit_node
+ end
+
+ it "returns an array of property names that doesn't include " +
+ "the non-editable properties" do
+ expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list }
+ end
+ end
+ end
+
+ context "and changes were not made" do
+ before(:each) do
+ allow(ui).to receive(:edit_hash)
+ .with(subject.view)
+ .and_return(subject.view.dup)
+
+ subject.edit_node
+ end
+
+ it { is_expected.not_to be_updated }
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/object_loader_spec.rb b/knife/spec/unit/knife/core/object_loader_spec.rb
new file mode 100644
index 0000000000..00a9ed4553
--- /dev/null
+++ b/knife/spec/unit/knife/core/object_loader_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Juanje Ojeda (<juanje.ojeda@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/core/object_loader"
+
+describe Chef::Knife::Core::ObjectLoader do
+ before(:each) do
+ @knife = Chef::Knife.new
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ Dir.chdir(File.join(CHEF_SPEC_DATA, "object_loader"))
+ end
+
+ shared_examples_for "Chef object" do |chef_class|
+ it "should create a #{chef_class} object" do
+ expect(@object).to be_a_kind_of(chef_class)
+ end
+
+ it "should has a attribute 'name'" do
+ expect(@object.name).to eql("test")
+ end
+ end
+
+ {
+ "nodes" => Chef::Node,
+ "roles" => Chef::Role,
+ "environments" => Chef::Environment,
+ }.each do |repo_location, chef_class|
+
+ describe "when the file is a #{chef_class}" do
+ before do
+ @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui)
+ end
+
+ describe "when the file is a Ruby" do
+ before do
+ @object = @loader.load_from(repo_location, "test.rb")
+ end
+
+ it_behaves_like "Chef object", chef_class
+ end
+
+ # NOTE: This is check for the bug described at CHEF-2352
+ describe "when the file is a JSON" do
+ describe "and it has defined 'json_class'" do
+ before do
+ @object = @loader.load_from(repo_location, "test_json_class.json")
+ end
+
+ it_behaves_like "Chef object", chef_class
+ end
+
+ describe "and it has not defined 'json_class'" do
+ before do
+ @object = @loader.load_from(repo_location, "test.json")
+ end
+
+ it_behaves_like "Chef object", chef_class
+ end
+ end
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/core/status_presenter_spec.rb b/knife/spec/unit/knife/core/status_presenter_spec.rb
new file mode 100644
index 0000000000..a3f297045b
--- /dev/null
+++ b/knife/spec/unit/knife/core/status_presenter_spec.rb
@@ -0,0 +1,54 @@
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::Core::StatusPresenter do
+ describe "#summarize_json" do
+ let(:presenter) { Chef::Knife::Core::StatusPresenter.new(double(:ui), double(:config, :[] => "")) }
+
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.automatic_attrs["name"] = "my_node"
+ n.automatic_attrs["ipaddress"] = "127.0.0.1"
+ end
+ end
+
+ let(:result) { JSON.parse(presenter.summarize_json([node])).first }
+
+ it "uses the first of public_ipv4_addrs when present" do
+ node.automatic_attrs["cloud"] = { "public_ipv4_addrs" => ["2.2.2.2"] }
+
+ expect(result["ip"]).to eq("2.2.2.2")
+ end
+
+ it "falls back to ipaddress when public_ipv4_addrs is empty" do
+ node.automatic_attrs["cloud"] = { "public_ipv4_addrs" => [] }
+
+ expect(result["ip"]).to eq("127.0.0.1")
+ end
+
+ it "falls back to ipaddress when cloud attributes are empty" do
+ node.automatic_attrs["cloud"] = {}
+
+ expect(result["ip"]).to eq("127.0.0.1")
+ end
+
+ it "falls back to ipaddress when cloud attributes is not present" do
+ expect(result["ip"]).to eq("127.0.0.1")
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/subcommand_loader_spec.rb b/knife/spec/unit/knife/core/subcommand_loader_spec.rb
new file mode 100644
index 0000000000..ad53a422fe
--- /dev/null
+++ b/knife/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -0,0 +1,64 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::SubcommandLoader do
+ let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) }
+ let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") }
+ let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") }
+
+ before do
+ allow(ChefUtils).to receive(:windows?) { false }
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
+ end
+
+ after do
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
+ end
+
+ let(:config_dir) { File.join(CHEF_SPEC_DATA, "knife-site-subcommands") }
+
+ describe "#for_config" do
+ context "when ~/.chef/plugin_manifest.json exists" do
+ before do
+ allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(true)
+ end
+
+ it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do
+ allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"_autogenerated_command_paths\": {}}")
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader
+ end
+ end
+
+ context "when ~/.chef/plugin_manifest.json does not exist" do
+ before do
+ allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(false)
+ end
+
+ it "creates a GemGlobLoader" do
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
+ end
+ end
+ end
+
+ describe "#gem_glob_loader" do
+ it "always creates a GemGlobLoader" do
+ expect(Chef::Knife::SubcommandLoader.gem_glob_loader(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/core/ui_spec.rb b/knife/spec/unit/knife/core/ui_spec.rb
new file mode 100644
index 0000000000..d5d09c0fdf
--- /dev/null
+++ b/knife/spec/unit/knife/core/ui_spec.rb
@@ -0,0 +1,656 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Tim Hinderliter (<tim@chef.io>)
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::UI do
+ before do
+ @out, @err, @in = StringIO.new, StringIO.new, StringIO.new
+ @config = {
+ verbosity: 0,
+ yes: nil,
+ format: "summary",
+ field_separator: ".",
+ }
+ @ui = Chef::Knife::UI.new(@out, @err, @in, @config)
+ end
+
+ class TestObject < OpenStruct
+ def self.from_hash(hsh)
+ new(hsh)
+ end
+ end
+
+ describe "edit" do
+ ruby_for_json = { "foo" => "bar" }
+ ruby_from_json = TestObject.from_hash(ruby_for_json)
+ json_from_ruby = "{\n \"foo\": \"bar\"\n}"
+ json_from_editor = "{\n \"bar\": \"foo\"\n}"
+ ruby_from_editor = TestObject.from_hash({ "bar" => "foo" })
+ my_editor = "veeeye"
+ temp_path = "/tmp/bar/baz"
+
+ let(:subject) { @ui.edit_data(ruby_for_json, parse_output, object_class: klass) }
+ let(:parse_output) { false }
+ let(:klass) { nil }
+
+ context "when editing is disabled" do
+ before do
+ @ui.config[:disable_editing] = true
+ stub_const("Tempfile", double) # Tempfiles should never be invoked
+ end
+ context "when parse_output is false" do
+ it "returns pretty json string" do
+ expect(subject).to eql(json_from_ruby)
+ end
+ end
+ context "when parse_output is true" do
+ let(:parse_output) { true }
+ let(:klass) { TestObject }
+ it "returns a ruby object" do
+ expect(subject).to eql(ruby_from_json)
+ end
+ context "but no object class is provided" do
+ let(:klass) { nil }
+ it "raises an error" do
+ expect { subject }.to raise_error ArgumentError,
+ /Please pass in the object class to hydrate or use #edit_hash/
+ end
+ end
+ end
+ end
+
+ context "when editing is enabled" do
+ before do
+ @ui.config[:disable_editing] = false
+ @ui.config[:editor] = my_editor
+ @mock = double("Tempfile")
+ expect(@mock).to receive(:sync=).with(true)
+ expect(@mock).to receive(:puts).with(json_from_ruby)
+ expect(@mock).to receive(:close)
+ expect(@mock).to receive(:path).at_least(:once).and_return(temp_path)
+ expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@mock)
+ end
+ context "and the editor works" do
+ before do
+ expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(true)
+ expect(IO).to receive(:read).with(temp_path).and_return(json_from_editor)
+ end
+
+ context "when parse_output is false" do
+ it "returns an edited pretty json string" do
+ expect(subject).to eql(json_from_editor)
+ end
+ end
+ context "when parse_output is true" do
+ let(:parse_output) { true }
+ let(:klass) { TestObject }
+ it "returns an edited ruby object" do
+ expect(subject).to eql(ruby_from_editor)
+ end
+ end
+ end
+ context "when running the editor fails with nil" do
+ before do
+ expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(nil)
+ expect(IO).not_to receive(:read)
+ end
+ it "throws an exception" do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ context "when running the editor fails with false" do
+ before do
+ expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(false)
+ expect(IO).not_to receive(:read)
+ end
+ it "throws an exception" do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ end
+ context "when editing and not stubbing Tempfile (semi-functional test)" do
+ before do
+ @ui.config[:disable_editing] = false
+ @ui.config[:editor] = my_editor
+ @tempfile = Tempfile.new([ "knife-edit-", ".json" ])
+ expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@tempfile)
+ end
+
+ context "and the editor works" do
+ before do
+ expect(@ui).to receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true)
+ expect(IO).to receive(:read).with(@tempfile.path).and_return(json_from_editor)
+ end
+
+ context "when parse_output is false" do
+ it "returns an edited pretty json string" do
+ expect(subject).to eql(json_from_editor)
+ end
+ it "the tempfile should have mode 0600", :unix_only do
+ # XXX: this looks odd because we're really testing Tempfile.new here
+ expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
+ expect(subject).to eql(json_from_editor)
+ end
+ end
+
+ context "when parse_output is true" do
+ let(:parse_output) { true }
+ let(:klass) { TestObject }
+ it "returns an edited ruby object" do
+ expect(subject).to eql(ruby_from_editor)
+ end
+ it "the tempfile should have mode 0600", :unix_only do
+ # XXX: this looks odd because we're really testing Tempfile.new here
+ expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
+ expect(subject).to eql(ruby_from_editor)
+ end
+ end
+ end
+ end
+ end
+
+ describe "format_list_for_display" do
+ it "should print the full hash if --with-uri is true" do
+ @ui.config[:with_uri] = true
+ expect(@ui.format_list_for_display({ marcy: :playground })).to eq({ marcy: :playground })
+ end
+
+ it "should print only the keys if --with-uri is false" do
+ @ui.config[:with_uri] = false
+ expect(@ui.format_list_for_display({ marcy: :playground })).to eq([ :marcy ])
+ end
+ end
+
+ shared_examples "an output mehthod handling IO exceptions" do |method|
+ it "should throw Errno::EIO exceptions" do
+ allow(@out).to receive(:puts).and_raise(Errno::EIO)
+ allow(@err).to receive(:puts).and_raise(Errno::EIO)
+ expect { @ui.send(method, "hi") }.to raise_error(Errno::EIO)
+ end
+
+ it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do
+ allow(@out).to receive(:puts).and_raise(Errno::EPIPE)
+ allow(@err).to receive(:puts).and_raise(Errno::EPIPE)
+ expect { @ui.send(method, "hi") }.to raise_error(SystemExit)
+ end
+
+ it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do
+ @config[:verbosity] = 2
+ allow(@out).to receive(:puts).and_raise(Errno::EPIPE)
+ allow(@err).to receive(:puts).and_raise(Errno::EPIPE)
+ expect { @ui.send(method, "hi") }.to raise_error(Errno::EPIPE)
+ end
+ end
+
+ describe "output" do
+ it_behaves_like "an output mehthod handling IO exceptions", :output
+
+ it "formats strings appropriately" do
+ @ui.output("hi")
+ expect(@out.string).to eq("hi\n")
+ end
+
+ it "formats hashes appropriately" do
+ @ui.output({ "hi" => "a", "lo" => "b" })
+ expect(@out.string).to eq <<~EOM
+ hi: a
+ lo: b
+ EOM
+ end
+
+ it "formats empty hashes appropriately" do
+ @ui.output({})
+ expect(@out.string).to eq("\n")
+ end
+
+ it "formats arrays appropriately" do
+ @ui.output(%w{a b})
+ expect(@out.string).to eq <<~EOM
+ a
+ b
+ EOM
+ end
+
+ it "formats empty arrays appropriately" do
+ @ui.output([ ])
+ expect(@out.string).to eq("\n")
+ end
+
+ it "formats single-member arrays appropriately" do
+ @ui.output([ "a" ])
+ expect(@out.string).to eq("a\n")
+ end
+
+ it "formats nested single-member arrays appropriately" do
+ @ui.output([ [ "a" ] ])
+ expect(@out.string).to eq("a\n")
+ end
+
+ it "formats nested arrays appropriately" do
+ @ui.output([ %w{a b}, %w{c d}])
+ expect(@out.string).to eq <<~EOM
+ a
+ b
+
+ c
+ d
+ EOM
+ end
+
+ it "formats nested arrays with single- and empty subarrays appropriately" do
+ @ui.output([ %w{a b}, [ "c" ], [], %w{d e}])
+ expect(@out.string).to eq <<~EOM
+ a
+ b
+
+ c
+
+
+ d
+ e
+ EOM
+ end
+
+ it "formats arrays of hashes with extra lines in between for readability" do
+ @ui.output([ { "a" => "b", "c" => "d" }, { "x" => "y" }, { "m" => "n", "o" => "p" }])
+ expect(@out.string).to eq <<~EOM
+ a: b
+ c: d
+
+ x: y
+
+ m: n
+ o: p
+ EOM
+ end
+
+ it "formats hashes with empty array members appropriately" do
+ @ui.output({ "a" => [], "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a:
+ b: c
+ EOM
+ end
+
+ it "formats hashes with single-member array values appropriately" do
+ @ui.output({ "a" => [ "foo" ], "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a: foo
+ b: c
+ EOM
+ end
+
+ it "formats hashes with array members appropriately" do
+ @ui.output({ "a" => %w{foo bar}, "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a:
+ foo
+ bar
+ b: c
+ EOM
+ end
+
+ it "formats hashes with single-member nested array values appropriately" do
+ @ui.output({ "a" => [ [ "foo" ] ], "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a:
+ foo
+ b: c
+ EOM
+ end
+
+ it "formats hashes with nested array values appropriately" do
+ @ui.output({ "a" => [ %w{foo bar}, %w{baz bjork} ], "b" => "c" })
+ # XXX: using a HEREDOC at this point results in a line with required spaces which auto-whitespace removal settings
+ # on editors will remove and will break this test.
+ expect(@out.string).to eq("a:\n foo\n bar\n \n baz\n bjork\nb: c\n")
+ end
+
+ it "formats hashes with hash values appropriately" do
+ @ui.output({ "a" => { "aa" => "bb", "cc" => "dd" }, "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a:
+ aa: bb
+ cc: dd
+ b: c
+ EOM
+ end
+
+ it "formats hashes with empty hash values appropriately" do
+ @ui.output({ "a" => {}, "b" => "c" })
+ expect(@out.string).to eq <<~EOM
+ a:
+ b: c
+ EOM
+ end
+ end
+
+ describe "warn" do
+ it_behaves_like "an output mehthod handling IO exceptions", :warn
+ end
+
+ describe "error" do
+ it_behaves_like "an output mehthod handling IO exceptions", :warn
+ end
+
+ describe "fatal" do
+ it_behaves_like "an output mehthod handling IO exceptions", :warn
+ end
+
+ describe "format_for_display" do
+ it "should return the raw data" do
+ input = { gi: :go }
+ expect(@ui.format_for_display(input)).to eq(input)
+ end
+
+ describe "with --attribute passed" do
+ it "should return the deeply nested attribute" do
+ input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" }
+ @ui.config[:attribute] = "gi.go"
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi.go" => "ge" } })
+ end
+
+ it "should return multiple attributes" do
+ input = { "gi" => "go", "hi" => "ho", "id" => "sample-data-bag-item" }
+ @ui.config[:attribute] = %w{gi hi}
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi" => "go", "hi" => "ho" } })
+ end
+
+ it "should handle attributes named the same as methods" do
+ input = { "keys" => "values", "hi" => "ho", "id" => "sample-data-bag-item" }
+ @ui.config[:attribute] = "keys"
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys" => "values" } })
+ end
+
+ it "should handle nested attributes named the same as methods" do
+ input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" }
+ @ui.config[:attribute] = "keys.keys"
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } })
+ end
+
+ it "should return the name attribute" do
+ input = Chef::Node.new
+ input.name("chef.localdomain")
+ @ui.config[:attribute] = "name"
+ expect(@ui.format_for_display(input)).to eq( { "chef.localdomain" => { "name" => "chef.localdomain" } })
+ end
+
+ it "should return a 'class' attribute and not the node.class" do
+ input = Chef::Node.new
+ input.default["class"] = "classy!"
+ @ui.config[:attribute] = "class"
+ expect(@ui.format_for_display(input)).to eq( { nil => { "class" => "classy!" } } )
+ end
+
+ it "should return the chef_environment attribute" do
+ input = Chef::Node.new
+ input.chef_environment = "production-partner-load-integration-preview-testing"
+ @ui.config[:attribute] = "chef_environment"
+ expect(@ui.format_for_display(input)).to eq( { nil => { "chef_environment" => "production-partner-load-integration-preview-testing" } } )
+ end
+
+ it "works with arrays" do
+ input = Chef::Node.new
+ input.default["array"] = %w{zero one two}
+ @ui.config[:attribute] = "array.1"
+ expect(@ui.format_for_display(input)).to eq( { nil => { "array.1" => "one" } } )
+ end
+
+ it "returns nil when given an attribute path that isn't a name or attribute" do
+ input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" }
+ non_existing_path = "nope.nada.nothingtoseehere"
+ @ui.config[:attribute] = non_existing_path
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } })
+ end
+
+ describe "when --field-separator is passed" do
+ it "honors that separator" do
+ input = { "keys" => { "with spaces" => { "open" => { "doors" => { "with many.dots" => "when asked" } } } } }
+ @ui.config[:field_separator] = ";"
+ @ui.config[:attribute] = "keys;with spaces;open;doors;with many.dots"
+ expect(@ui.format_for_display(input)).to eq({ nil => { "keys;with spaces;open;doors;with many.dots" => "when asked" } })
+ end
+ end
+ end
+
+ describe "with --run-list passed" do
+ it "should return the run list" do
+ input = Chef::Node.new
+ input.name("sample-node")
+ input.run_list("role[monkey]", "role[churchmouse]")
+ @ui.config[:run_list] = true
+ response = @ui.format_for_display(input)
+ expect(response["sample-node"]["run_list"][0]).to eq("role[monkey]")
+ expect(response["sample-node"]["run_list"][1]).to eq("role[churchmouse]")
+ end
+ end
+ end
+
+ describe "format_cookbook_list_for_display" do
+ before(:each) do
+ @item = {
+ "cookbook_name" => {
+ "url" => "http://url/cookbooks/cookbook",
+ "versions" => [
+ { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" },
+ { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" },
+ { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" },
+ ],
+ },
+ }
+ end
+
+ it "should return an array of the cookbooks with versions" do
+ expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ]
+ response = @ui.format_cookbook_list_for_display(@item)
+ expect(response).to eq(expected_response)
+ end
+
+ describe "with --with-uri" do
+ it "should return the URIs" do
+ response = {
+ "cookbook_name" => {
+ "1.0.0" => "http://url/cookbooks/1.0.0",
+ "2.0.0" => "http://url/cookbooks/2.0.0",
+ "3.0.0" => "http://url/cookbooks/3.0.0" },
+ }
+ @ui.config[:with_uri] = true
+ expect(@ui.format_cookbook_list_for_display(@item)).to eq(response)
+ end
+ end
+
+ context "when running on Windows" do
+ before(:each) do
+ stdout = double("StringIO", tty?: true)
+ allow(@ui).to receive(:stdout).and_return(stdout)
+ allow(ChefUtils).to receive(:windows?) { true }
+ Chef::Config.reset
+ end
+
+ after(:each) do
+ Chef::Config.reset
+ end
+
+ it "should have color set to true if knife config has color explicitly set to true" do
+ Chef::Config[:color] = true
+ @ui.config[:color] = true
+ expect(@ui.color?).to eql(true)
+ end
+
+ it "should have color set to false if knife config has color explicitly set to false" do
+ Chef::Config[:color] = false
+ expect(@ui.color?).to eql(false)
+ end
+
+ it "should not have color set to false by default" do
+ expect(@ui.color?).to eql(false)
+ end
+ end
+ end
+
+ describe "color" do
+ context "when ui.color? => true" do
+ it "returns colored output" do
+ skip "doesn't work on systems that don't correctly have terminals setup for color"
+ expect(@ui).to receive(:color?).and_return(true)
+ expect(@ui.color("a_bus_is", :yellow)).to eql("\e[33ma_bus_is\e[0m")
+ end
+ end
+
+ context "when ui.color? => false" do
+ it "returns plain output" do
+ expect(@ui).to receive(:color?).and_return(false)
+ expect(@ui.color("a_bus_is", :yellow)).to eql("a_bus_is")
+ end
+ end
+ end
+
+ describe "confirm" do
+ let(:stdout) { StringIO.new }
+ let(:output) { stdout.string }
+
+ let(:question) { "monkeys rule" }
+ let(:answer) { "y" }
+
+ let(:default_choice) { nil }
+ let(:append_instructions) { true }
+
+ def run_confirm
+ allow(@ui).to receive(:stdout).and_return(stdout)
+ allow(@ui.stdin).to receive(:readline).and_return(answer)
+ @ui.confirm(question, append_instructions, default_choice)
+ end
+
+ def run_confirm_without_exit
+ allow(@ui).to receive(:stdout).and_return(stdout)
+ allow(@ui.stdin).to receive(:readline).and_return(answer)
+ @ui.confirm_without_exit(question, append_instructions, default_choice)
+ end
+
+ shared_examples_for "confirm with positive answer" do
+ it "confirm should return true" do
+ expect(run_confirm).to be_truthy
+ end
+
+ it "confirm_without_exit should return true" do
+ expect(run_confirm_without_exit).to be_truthy
+ end
+ end
+
+ shared_examples_for "confirm with negative answer" do
+ it "confirm should exit 3" do
+ expect do
+ run_confirm
+ end.to raise_error(SystemExit) { |e| expect(e.status).to eq(3) }
+ end
+
+ it "confirm_without_exit should return false" do
+ expect(run_confirm_without_exit).to be_falsey
+ end
+ end
+
+ describe "with default choice set to true" do
+ let(:default_choice) { true }
+
+ it "should show 'Y/n' in the instructions" do
+ run_confirm
+ expect(output).to include("Y/n")
+ end
+
+ describe "with empty answer" do
+ let(:answer) { "" }
+
+ it_behaves_like "confirm with positive answer"
+ end
+
+ describe "with answer N " do
+ let(:answer) { "N" }
+
+ it_behaves_like "confirm with negative answer"
+ end
+ end
+
+ describe "with default choice set to false" do
+ let(:default_choice) { false }
+
+ it "should show 'y/N' in the instructions" do
+ run_confirm
+ expect(output).to include("y/N")
+ end
+
+ describe "with empty answer" do
+ let(:answer) { "" }
+
+ it_behaves_like "confirm with negative answer"
+ end
+
+ describe "with answer N " do
+ let(:answer) { "Y" }
+
+ it_behaves_like "confirm with positive answer"
+ end
+ end
+
+ %w{Y y}.each do |answer|
+ describe "with answer #{answer}" do
+ let(:answer) { answer }
+
+ it_behaves_like "confirm with positive answer"
+ end
+ end
+
+ %w{N n}.each do |answer|
+ describe "with answer #{answer}" do
+ let(:answer) { answer }
+
+ it_behaves_like "confirm with negative answer"
+ end
+ end
+
+ describe "with --y or --yes passed" do
+ it "should return true" do
+ @ui.config[:yes] = true
+ expect(run_confirm).to be_truthy
+ expect(output).to eq("")
+ end
+ end
+ end
+
+ describe "when asking for free-form user input" do
+ it "asks a question and returns the answer provided by the user" do
+ out = StringIO.new
+ allow(@ui).to receive(:stdout).and_return(out)
+ allow(@ui).to receive(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
+ expect(@ui.ask_question("your chef server URL?")).to eq("http://mychefserver.example.com")
+ expect(out.string).to eq("your chef server URL?")
+ end
+
+ it "suggests a default setting and returns the default when the user's response only contains whitespace" do
+ out = StringIO.new
+ allow(@ui).to receive(:stdout).and_return(out)
+ allow(@ui).to receive(:stdin).and_return(StringIO.new(" \n"))
+ expect(@ui.ask_question("your chef server URL? ", default: "http://localhost:4000")).to eq("http://localhost:4000")
+ expect(out.string).to eq("your chef server URL? [http://localhost:4000] ")
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb b/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb
new file mode 100644
index 0000000000..af656facf0
--- /dev/null
+++ b/knife/spec/unit/knife/core/windows_bootstrap_context_spec.rb
@@ -0,0 +1,238 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/core/windows_bootstrap_context"
+describe Chef::Knife::Core::WindowsBootstrapContext do
+ let(:config) { {} }
+ let(:chef_config) { Chef::Config.save } # "dup" to a hash
+ let(:bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(config, nil, chef_config, nil) }
+
+ describe "fips" do
+ context "when fips is set" do
+ before do
+ chef_config[:fips] = true
+ end
+
+ it "sets fips mode in the client.rb" do
+ expect(bootstrap_context.config_content).to match(/fips true/)
+ end
+ end
+
+ context "when fips is not set" do
+ before do
+ chef_config[:fips] = false
+ end
+
+ it "sets fips mode in the client.rb" do
+ expect(bootstrap_context.config_content).not_to match(/fips true/)
+ end
+ end
+ end
+
+ describe "trusted_certs_script" do
+ let(:mock_cert_dir) { ::File.absolute_path(::File.join("spec", "assets", "fake_trusted_certs")) }
+ let(:script_output) { bootstrap_context.trusted_certs_script }
+ let(:crt_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.crt")) }
+ let(:pem_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.pem")) }
+ let(:other_files) { ::Dir.glob(::File.join(mock_cert_dir, "*")) - crt_files - pem_files }
+
+ before do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(trusted_certs_dir: mock_cert_dir))
+ end
+
+ it "should echo every .crt file in the trusted_certs directory" do
+ crt_files.each do |f|
+ echo_file = ::File.read(f).gsub(/^/, "echo.")
+ expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f)))
+ expect(script_output).to include(echo_file)
+ end
+ end
+
+ it "should echo every .pem file in the trusted_certs directory" do
+ pem_files.each do |f|
+ echo_file = ::File.read(f).gsub(/^/, "echo.")
+ expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f)))
+ expect(script_output).to include(echo_file)
+ end
+ end
+
+ it "should not echo files which aren't .crt or .pem files" do
+ other_files.each do |f|
+ echo_file = ::File.read(f).gsub(/^/, "echo.")
+ expect(script_output).to_not include(::File.join("trusted_certs", ::File.basename(f)))
+ expect(script_output).to_not include(echo_file)
+ end
+ end
+ end
+
+ describe "validation_key" do
+ before do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(validation_key: "C:\\chef\\key.pem"))
+ end
+
+ it "should return false if validation_key does not exist" do
+ allow(::File).to receive(:expand_path).with("C:\\chef\\key.pem").and_call_original
+ allow(::File).to receive(:exist?).and_return(false)
+ expect(bootstrap_context.validation_key).to eq(false)
+ end
+ end
+
+ describe "#get_log_location" do
+
+ context "when config_log_location value is nil" do
+ it "sets STDOUT in client.rb as default" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: nil))
+ expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
+ end
+ end
+
+ context "when config_log_location value is empty" do
+ it "sets STDOUT in client.rb as default" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: ""))
+ expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
+ end
+ end
+
+ context "when config_log_location value is STDOUT" do
+ it "sets STDOUT in client.rb" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDOUT))
+ expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
+ end
+ end
+
+ context "when config_log_location value is STDERR" do
+ it "sets STDERR in client.rb" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDERR))
+ expect(bootstrap_context.get_log_location).to eq("STDERR\n")
+ end
+ end
+
+ context "when config_log_location value is path to a file" do
+ it "sets file path in client.rb" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: "C:\\chef\\chef.log"))
+ expect(bootstrap_context.get_log_location).to eq("\"C:\\chef\\chef.log\"\n")
+ end
+ end
+
+ context "when config_log_location value is :win_evt" do
+ it "sets :win_evt in client.rb" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :win_evt))
+ expect(bootstrap_context.get_log_location).to eq(":win_evt\n")
+ end
+ end
+
+ context "when config_log_location value is :syslog" do
+ it "raise error with message and exit" do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :syslog))
+ expect { bootstrap_context.get_log_location }.to raise_error("syslog is not supported for log_location on Windows OS\n")
+ end
+ end
+
+ end
+
+ describe "#config_content" do
+ before do
+ bootstrap_context.instance_variable_set(
+ :@chef_config, Mash.new(
+ config_log_level: :info,
+ config_log_location: STDOUT,
+ chef_server_url: "http://chef.example.com:4444",
+ validation_client_name: "chef-validator-testing",
+ file_cache_path: "c:/chef/cache",
+ file_backup_path: "c:/chef/backup",
+ cache_options: ({ path: "c:/chef/cache/checksums", skip_expires: true })
+ )
+ )
+ end
+
+ it "generates the config file data" do
+ expected = <<~EXPECTED
+ echo.chef_server_url "http://chef.example.com:4444"
+ echo.validation_client_name "chef-validator-testing"
+ echo.file_cache_path "C:\\\\chef\\\\cache"
+ echo.file_backup_path "C:\\\\chef\\\\backup"
+ echo.cache_options ^({:path =^> "C:\\\\chef\\\\cache\\\\checksums", :skip_expires =^> true}^)
+ echo.# Using default node name ^(fqdn^)
+ echo.log_level :auto
+ echo.log_location STDOUT
+ EXPECTED
+ expect(bootstrap_context.config_content).to eq expected
+ end
+
+ describe "when chef_license is set" do
+ before do
+ bootstrap_context.instance_variable_set(:@chef_config, Mash.new(chef_license: "accept-no-persist"))
+ end
+ it "sets chef_license in the generated config file" do
+ expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"")
+ end
+ end
+ end
+
+ describe "#start_chef" do
+ it "returns the expected string" do
+ expect(bootstrap_context.start_chef).to eq(
+ <<~EOH
+ SET "PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin;%PATH%"
+ chef-client -c C:\\chef\\client.rb -j C:\\chef\\first-boot.json
+ EOH
+ )
+ end
+ end
+
+ describe "msi_url" do
+ context "when msi_url config option is not set" do
+ let(:config) { { channel: "stable" } }
+ before do
+ expect(bootstrap_context).to receive(:version_to_install).and_return("something")
+ end
+
+ it "returns a chef.io msi url with minimal url parameters" do
+ reference_url = "https://omnitruck.chef.io/chef/download?p=windows&channel=stable&v=something"
+ expect(bootstrap_context.msi_url).to eq(reference_url)
+ end
+
+ it "returns a chef.io msi url with provided url parameters substituted" do
+ reference_url = "https://omnitruck.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&channel=stable&v=something"
+ expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(reference_url)
+ end
+
+ context "when a channel is provided in config" do
+ let(:config) { { channel: "current" } }
+ it "returns a chef.io msi url with the requested channel" do
+ reference_url = "https://omnitruck.chef.io/chef/download?p=windows&channel=current&v=something"
+ expect(bootstrap_context.msi_url).to eq(reference_url)
+ end
+ end
+ end
+
+ context "when msi_url config option is set" do
+ let(:custom_url) { "file://something" }
+ let(:config) { { msi_url: custom_url, install: true } }
+
+ it "returns the overridden url" do
+ expect(bootstrap_context.msi_url).to eq(custom_url)
+ end
+
+ it "doesn't introduce any unnecessary query parameters if provided by the template" do
+ expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(custom_url)
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/data_bag_create_spec.rb b/knife/spec/unit/knife/data_bag_create_spec.rb
new file mode 100644
index 0000000000..6700d886fc
--- /dev/null
+++ b/knife/spec/unit/knife/data_bag_create_spec.rb
@@ -0,0 +1,175 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tempfile"
+
+describe Chef::Knife::DataBagCreate do
+ let(:knife) do
+ k = Chef::Knife::DataBagCreate.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+
+ let(:raw_hash) { { "login_name" => "alphaomega", "id" => item_name } }
+
+ let(:config) { {} }
+
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
+ end
+
+ context "when data_bag already exists" do
+ it "doesn't create a data bag" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(rest).to receive(:get).with("data/#{bag_name}")
+ expect(rest).to_not receive(:post).with("data", { "name" => bag_name })
+ expect(knife.ui).to receive(:info).with("Data bag #{bag_name} already exists")
+
+ knife.run
+ end
+ end
+
+ context "when data_bag doesn't exist" do
+ before do
+ # Data bag doesn't exist by default so we mock the GET request to return 404
+ exception = double("404 error", code: "404")
+ allow(rest).to receive(:get)
+ .with("data/#{bag_name}")
+ .and_raise(Net::HTTPClientException.new("404", exception))
+ end
+
+ it "tries to create a data bag with an invalid name when given one argument" do
+ knife.name_args = ["invalid&char"]
+ expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
+ expect { knife.run }.to exit_with_code(1)
+ end
+
+ it "won't create a data bag with a reserved name for search" do
+ %w{node role client environment}.each do |name|
+ knife.name_args = [name]
+ expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
+ expect { knife.run }.to exit_with_code(1)
+ end
+ end
+
+ context "when part of the name is a reserved name" do
+ before do
+ exception = double("404 error", code: "404")
+ %w{node role client environment}.each do |name|
+ allow(rest).to receive(:get)
+ .with("data/sudoing_#{name}_admins")
+ .and_raise(Net::HTTPClientException.new("404", exception))
+ end
+ end
+
+ it "will create a data bag containing a reserved word" do
+ %w{node role client environment}.each do |name|
+ knife.name_args = ["sudoing_#{name}_admins"]
+ expect(rest).to receive(:post).with("data", { "name" => knife.name_args[0] })
+ expect(knife.ui).to receive(:info).with("Created data_bag[#{knife.name_args[0]}]")
+
+ knife.run
+ end
+ end
+ end
+
+ context "when given one argument" do
+ before do
+ knife.name_args = [bag_name]
+ end
+
+ it "creates a data bag" do
+ expect(rest).to receive(:post).with("data", { "name" => bag_name })
+ expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
+
+ knife.run
+ end
+ end
+
+ context "when given a data bag name partially matching a reserved name for search" do
+ %w{xnode rolex xenvironmentx xclientx}.each do |name|
+ let(:bag_name) { name }
+
+ before do
+ knife.name_args = [bag_name]
+ end
+
+ it "creates a data bag named '#{name}'" do
+ expect(rest).to receive(:post).with("data", { "name" => bag_name })
+ expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
+
+ knife.run
+ end
+ end
+ end
+
+ context "no secret is specified for encryption" do
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(raw_hash)
+ item.data_bag(bag_name)
+ item
+ end
+
+ it "creates a data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(false)
+ expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered
+ expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered
+
+ knife.run
+ end
+ end
+
+ context "a secret is specified for encryption" do
+ let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
+
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(encoded_data)
+ item.data_bag(bag_name)
+ item
+ end
+
+ it "creates an encrypted data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem)
+ .to receive(:encrypt_data_bag_item)
+ .with(raw_hash, secret)
+ .and_return(encoded_data)
+ expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered
+ expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered
+
+ knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/data_bag_edit_spec.rb b/knife/spec/unit/knife/data_bag_edit_spec.rb
new file mode 100644
index 0000000000..1be75ba014
--- /dev/null
+++ b/knife/spec/unit/knife/data_bag_edit_spec.rb
@@ -0,0 +1,126 @@
+#
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "tempfile"
+
+describe Chef::Knife::DataBagEdit do
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
+ end
+
+ let(:knife) do
+ k = Chef::Knife::DataBagEdit.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:raw_hash) { { "login_name" => "alphaomega", "id" => "item_name" } }
+ let(:db) { Chef::DataBagItem.from_hash(raw_hash) }
+ let(:raw_edited_hash) { { "login_name" => "rho", "id" => "item_name", "new_key" => "new_value" } }
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+
+ let(:config) { {} }
+
+ let(:is_encrypted?) { false }
+ let(:transmitted_hash) { raw_edited_hash }
+ let(:data_to_edit) { db.raw_data }
+ shared_examples_for "editing a data bag" do
+ it "correctly edits then uploads the data bag" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?)
+ expect(knife).to receive(:edit_hash).with(data_to_edit).and_return(raw_edited_hash)
+ expect(rest).to receive(:put).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered
+
+ knife.run
+ end
+ end
+
+ it "requires data bag and item arguments" do
+ knife.name_args = []
+ expect(stdout).to receive(:puts).twice.with(anything)
+ expect { knife.run }.to exit_with_code(1)
+ expect(stdout.string).to eq("")
+ end
+
+ context "when no secret is provided" do
+ include_examples "editing a data bag"
+ end
+
+ context "when config[:print_after] is set" do
+ let(:config) { { print_after: true } }
+ before do
+ expect(knife.ui).to receive(:output).with(raw_edited_hash)
+ end
+
+ include_examples "editing a data bag"
+ end
+
+ context "when a secret is provided" do
+ let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
+ let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) }
+ let(:transmitted_hash) { enc_edited_hash }
+
+ before(:each) do
+ expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash)
+ end
+
+ context "the data bag starts encrypted" do
+ let(:is_encrypted?) { true }
+ let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) }
+ # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag
+ let(:data_to_edit) { raw_hash }
+
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ end
+
+ include_examples "editing a data bag"
+ end
+
+ context "the data bag starts unencrypted" do
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ end
+
+ include_examples "editing a data bag"
+ end
+ end
+
+ it "fails to edit an encrypted data bag if the secret is missing" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true)
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+
+ expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.")
+ expect { knife.run }.to exit_with_code(1)
+ end
+
+end
diff --git a/knife/spec/unit/knife/data_bag_from_file_spec.rb b/knife/spec/unit/knife/data_bag_from_file_spec.rb
new file mode 100644
index 0000000000..4d7c506d0d
--- /dev/null
+++ b/knife/spec/unit/knife/data_bag_from_file_spec.rb
@@ -0,0 +1,174 @@
+#
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+require "chef/data_bag_item"
+require "chef/encrypted_data_bag_item"
+require "tempfile"
+
+Chef::Knife::DataBagFromFile.load_deps
+
+describe Chef::Knife::DataBagFromFile do
+ before :each do
+ allow(ChefUtils).to receive(:windows?) { false }
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ FileUtils.mkdir_p([db_folder, db_folder2])
+ db_file.write(Chef::JSONCompat.to_json(plain_data))
+ db_file.flush
+ allow(knife).to receive(:config).and_return(config)
+ allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader)
+ end
+
+ # We have to explicitly clean up Tempfile on Windows because it said so.
+ after :each do
+ db_file.close
+ db_file2.close
+ db_file3.close
+ FileUtils.rm_rf(db_folder)
+ FileUtils.rm_rf(db_folder2)
+ FileUtils.remove_entry_secure tmp_dir
+ end
+
+ let(:knife) do
+ k = Chef::Knife::DataBagFromFile.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:tmp_dir) { make_canonical_temp_directory }
+ let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) }
+ let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) }
+ let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) }
+ let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) }
+ let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) }
+
+ def new_bag_expects(b = bag_name, d = plain_data)
+ data_bag = double
+ expect(data_bag).to receive(:data_bag).with(b)
+ expect(data_bag).to receive(:raw_data=).with(d)
+ expect(data_bag).to receive(:save)
+ expect(data_bag).to receive(:data_bag)
+ expect(data_bag).to receive(:id)
+ data_bag
+ end
+
+ let(:loader) { double("Knife::Core::ObjectLoader") }
+
+ let(:data_bags_path) { "data_bags" }
+ let(:plain_data) do
+ {
+ "id" => "item_name",
+ "greeting" => "hello",
+ "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true } },
+ }
+ end
+ let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) }
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:bag_name2) { "sudoing_admins2" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+
+ let(:config) { {} }
+
+ it "loads from a file and saves" do
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects)
+
+ knife.run
+ end
+
+ it "loads all from multiple files and saves" do
+ knife.name_args = [ bag_name, db_file.path, db_file2.path ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
+ end
+
+ it "loads all from a folder and saves" do
+ knife.name_args = [ bag_name, db_folder ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
+ end
+
+ describe "loading all data bags" do
+
+ it "loads all data bags when -a or --all options is provided" do
+ knife.name_args = []
+ config[:all] = true
+ expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2))
+
+ knife.run
+ end
+
+ it "loads all data bags items when -a or --all options is provided" do
+ knife.name_args = [bag_name2]
+ config[:all] = true
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2))
+
+ knife.run
+ end
+
+ end
+
+ describe "encrypted data bag items" do
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data)
+ end
+
+ it "encrypts values when given --secret" do
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data))
+
+ knife.run
+ end
+
+ end
+
+ describe "command line parsing" do
+ it "prints help if given no arguments" do
+ knife.name_args = [bag_name]
+ expect { knife.run }.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)")
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/data_bag_secret_options_spec.rb b/knife/spec/unit/knife/data_bag_secret_options_spec.rb
new file mode 100644
index 0000000000..9946b82110
--- /dev/null
+++ b/knife/spec/unit/knife/data_bag_secret_options_spec.rb
@@ -0,0 +1,173 @@
+#
+# Author:: Tyler Ball (<tball@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife"
+require "chef/config"
+require "tempfile"
+
+class ExampleDataBagCommand < Chef::Knife
+ include Chef::Knife::DataBagSecretOptions
+end
+
+describe Chef::Knife::DataBagSecretOptions do
+ let(:example_db) do
+ k = ExampleDataBagCommand.new
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:stdout) { StringIO.new }
+
+ let(:secret) { "abc123SECRET" }
+ let(:secret_file) do
+ sfile = Tempfile.new("encrypted_data_bag_secret")
+ sfile.puts(secret)
+ sfile.flush
+ sfile
+ end
+
+ after do
+ secret_file.close
+ secret_file.unlink
+ end
+
+ describe "#validate_secrets" do
+
+ it "throws an error when provided with both --secret and --secret-file on the CL" do
+ example_db.config[:cl_secret_file] = secret_file.path
+ example_db.config[:cl_secret] = secret
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file")
+
+ example_db.validate_secrets
+ end
+
+ it "throws an error when provided with `secret` and `secret_file` in knife.rb" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file")
+
+ example_db.validate_secrets
+ end
+
+ end
+
+ describe "#read_secret" do
+
+ it "returns the secret first" do
+ example_db.config[:cl_secret] = secret
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file only if secret does not exist" do
+ example_db.config[:cl_secret_file] = secret_file.path
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ example_db.merge_configs
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ it "returns the secret from the knife.rb config" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file from the knife.rb config only if the secret does not exist" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ example_db.merge_configs
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ end
+
+ describe "#encryption_secret_provided?" do
+
+ it "returns true if the secret is passed on the CL" do
+ example_db.config[:cl_secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if the secret_file is passed on the CL" do
+ example_db.config[:cl_secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret is in config" do
+ example_db.config[:encrypt] = true
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret_file is in config" do
+ example_db.config[:encrypt] = true
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do
+ example_db.config[:encrypt] = true
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.")
+ example_db.encryption_secret_provided?
+ end
+
+ it "returns false if no secret is passed" do
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret is in the config" do
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret_file is in the config" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret] = secret
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ example_db.merge_configs
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns false if --encrypt is not provided and need_encrypt_flag is false" do
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false)
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/data_bag_show_spec.rb b/knife/spec/unit/knife/data_bag_show_spec.rb
new file mode 100644
index 0000000000..0cc0bdf766
--- /dev/null
+++ b/knife/spec/unit/knife/data_bag_show_spec.rb
@@ -0,0 +1,139 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Seth Falcon (<seth@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+require "chef/data_bag_item"
+require "chef/encrypted_data_bag_item"
+require "chef/json_compat"
+require "tempfile"
+
+describe Chef::Knife::DataBagShow do
+
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
+ end
+
+ let(:knife) do
+ k = Chef::Knife::DataBagShow.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
+
+ let(:data_bag_contents) do
+ { "id" => "id", "baz" => "http://localhost:4000/data/bag_o_data/baz",
+ "qux" => "http://localhost:4000/data/bag_o_data/qux" }
+ end
+ let(:enc_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret) }
+ let(:data_bag) { Chef::DataBagItem.from_hash(data_bag_contents) }
+ let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) }
+ let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) }
+
+ let(:secret) { "abc123SECRET" }
+ #
+ # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
+ #
+ let(:config) { { format: "json" } }
+
+ context "Data bag to show is encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(true)
+ end
+
+ it "decrypts and displays the encrypted data bag when the secret is provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.")
+ expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag)
+
+ expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux}
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
+ end
+
+ it "displays the encrypted data bag when the secret is not provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+
+ knife.run
+ expect(stdout.string.strip).to include("baz", "qux", "cipher")
+ end
+ end
+
+ context "Data bag to show is not encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(false)
+ end
+
+ it "displays the data bag" do
+ expect(knife).to receive(:read_secret).exactly(0).times
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
+
+ expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux}
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
+ end
+
+ context "when a secret is given" do
+ it "displays the data bag" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
+ expect(knife.ui).to receive(:warn).with("Unencrypted data bag detected, ignoring any provided secret options.")
+
+ expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux}
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
+ end
+ end
+ end
+
+ it "displays the list of items in the data bag when only one @name_arg is provided" do
+ knife.name_args = [bag_name]
+ expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({})
+
+ knife.run
+ expect(stdout.string.strip).to eq("")
+ end
+
+ it "raises an error when no @name_args are provided" do
+ knife.name_args = []
+
+ expect { knife.run }.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)")
+ end
+
+end
diff --git a/knife/spec/unit/knife/environment_compare_spec.rb b/knife/spec/unit/knife/environment_compare_spec.rb
new file mode 100644
index 0000000000..001c725624
--- /dev/null
+++ b/knife/spec/unit/knife/environment_compare_spec.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Sander Botman (<sbotman@schubergphilis.com>)
+# Copyright:: Copyright 2013-2016, Sander Botman.
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentCompare do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentCompare.new
+
+ @environments = {
+ "cita" => "http://localhost:4000/environments/cita",
+ "citm" => "http://localhost:4000/environments/citm",
+ }
+
+ allow(@knife).to receive(:environment_list).and_return(@environments)
+
+ @constraints = {
+ "cita" => { "foo" => "= 1.0.1", "bar" => "= 0.0.4" },
+ "citm" => { "foo" => "= 1.0.1", "bar" => "= 0.0.2" },
+ }
+
+ allow(@knife).to receive(:constraint_list).and_return(@constraints)
+
+ @cookbooks = { "foo" => "= 1.0.1", "bar" => "= 0.0.1" }
+
+ allow(@knife).to receive(:cookbook_list).and_return(@cookbooks)
+
+ @rest_double = double("rest")
+ allow(@knife).to receive(:rest).and_return(@rest_double)
+ @cookbook_names = %w{apache2 mysql foo bar dummy chef_handler}
+ @base_url = "https://server.example.com/cookbooks"
+ @cookbook_data = {}
+ @cookbook_names.each do |item|
+ @cookbook_data[item] = { "url" => "#{@base_url}/#{item}",
+ "versions" => [{ "version" => "1.0.1",
+ "url" => "#{@base_url}/#{item}/1.0.1" }] }
+ end
+
+ allow(@rest_double).to receive(:get).with("/cookbooks?num_versions=1").and_return(@cookbook_data)
+
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should display only cookbooks with version constraints" do
+ @knife.config[:format] = "summary"
+ @knife.run
+ @environments.each_key do |item|
+ expect(@stdout.string).to(match(/#{item}/)) && expect(@stdout.string.lines.count).to(be 4)
+ end
+ end
+
+ it "should display 4 number of lines" do
+ @knife.config[:format] = "summary"
+ @knife.run
+ expect(@stdout.string.lines.count).to be 4
+ end
+ end
+
+ describe "with -m or --mismatch" do
+ it "should display only cookbooks that have mismatching version constraints" do
+ @knife.config[:format] = "summary"
+ @knife.config[:mismatch] = true
+ @knife.run
+ @constraints.each_value do |ver|
+ expect(@stdout.string).to match(/#{ver[1]}/)
+ end
+ end
+
+ it "should display 3 number of lines" do
+ @knife.config[:format] = "summary"
+ @knife.config[:mismatch] = true
+ @knife.run
+ expect(@stdout.string.lines.count).to be 3
+ end
+ end
+
+ describe "with -a or --all" do
+ it "should display all cookbooks" do
+ @knife.config[:format] = "summary"
+ @knife.config[:all] = true
+ @knife.run
+ @constraints.each_value do |ver|
+ expect(@stdout.string).to match(/#{ver[1]}/)
+ end
+ end
+
+ it "should display 8 number of lines" do
+ @knife.config[:format] = "summary"
+ @knife.config[:all] = true
+ @knife.run
+ expect(@stdout.string.lines.count).to be 8
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/environment_create_spec.rb b/knife/spec/unit/knife/environment_create_spec.rb
new file mode 100644
index 0000000000..0535276e9c
--- /dev/null
+++ b/knife/spec/unit/knife/environment_create_spec.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentCreate do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentCreate.new
+ allow(@knife).to receive(:msg).and_return true
+ allow(@knife).to receive(:output).and_return true
+ allow(@knife).to receive(:show_usage).and_return true
+ @knife.name_args = [ "production" ]
+
+ @environment = Chef::Environment.new
+ allow(@environment).to receive(:save)
+
+ allow(Chef::Environment).to receive(:new).and_return @environment
+ allow(@knife).to receive(:edit_data).and_return @environment
+ end
+
+ describe "run" do
+ it "should create a new environment" do
+ expect(Chef::Environment).to receive(:new)
+ @knife.run
+ end
+
+ it "should set the environment name" do
+ expect(@environment).to receive(:name).with("production")
+ @knife.run
+ end
+
+ it "should not print the environment" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ it "should prompt you to edit the data" do
+ expect(@knife).to receive(:edit_data).with(@environment, object_class: Chef::Environment)
+ @knife.run
+ end
+
+ it "should save the environment" do
+ expect(@environment).to receive(:save)
+ @knife.run
+ end
+
+ it "should show usage and exit when no environment name is provided" do
+ @knife.name_args = [ ]
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "with --description" do
+ before(:each) do
+ @knife.config[:description] = "This is production"
+ end
+
+ it "should set the description" do
+ expect(@environment).to receive(:description).with("This is production")
+ @knife.run
+ end
+ end
+
+ describe "with --print-after" do
+ before(:each) do
+ @knife.config[:print_after] = true
+ end
+
+ it "should pretty print the environment, formatted for display" do
+ expect(@knife).to receive(:output).with(@environment)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/environment_delete_spec.rb b/knife/spec/unit/knife/environment_delete_spec.rb
new file mode 100644
index 0000000000..e088f6a791
--- /dev/null
+++ b/knife/spec/unit/knife/environment_delete_spec.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentDelete do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentDelete.new
+ allow(@knife).to receive(:msg).and_return true
+ allow(@knife).to receive(:output).and_return true
+ allow(@knife).to receive(:show_usage).and_return true
+ allow(@knife).to receive(:confirm).and_return true
+ @knife.name_args = [ "production" ]
+
+ @environment = Chef::Environment.new
+ @environment.name("production")
+ @environment.description("Please delete me")
+ allow(@environment).to receive(:destroy).and_return true
+ allow(Chef::Environment).to receive(:load).and_return @environment
+ end
+
+ it "should confirm that you want to delete" do
+ expect(@knife).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should load the environment" do
+ expect(Chef::Environment).to receive(:load).with("production")
+ @knife.run
+ end
+
+ it "should delete the environment" do
+ expect(@environment).to receive(:destroy)
+ @knife.run
+ end
+
+ it "should not print the environment" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ it "should show usage and exit when no environment name is provided" do
+ @knife.name_args = []
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "with --print-after" do
+ it "should pretty print the environment, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output).with(@environment)
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/environment_edit_spec.rb b/knife/spec/unit/knife/environment_edit_spec.rb
new file mode 100644
index 0000000000..f05de2cb2f
--- /dev/null
+++ b/knife/spec/unit/knife/environment_edit_spec.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentEdit do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentEdit.new
+ allow(@knife.ui).to receive(:msg).and_return true
+ allow(@knife.ui).to receive(:output).and_return true
+ allow(@knife.ui).to receive(:show_usage).and_return true
+ @knife.name_args = [ "production" ]
+
+ @environment = Chef::Environment.new
+ @environment.name("production")
+ @environment.description("Please edit me")
+ allow(@environment).to receive(:save).and_return true
+ allow(Chef::Environment).to receive(:load).and_return @environment
+ allow(@knife.ui).to receive(:edit_data).and_return @environment
+ end
+
+ it "should load the environment" do
+ expect(Chef::Environment).to receive(:load).with("production")
+ @knife.run
+ end
+
+ it "should let you edit the environment" do
+ expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment)
+ @knife.run
+ end
+
+ it "should save the edited environment data" do
+ pansy = Chef::Environment.new
+
+ @environment.name("new_environment_name")
+ expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment).and_return(pansy)
+ expect(pansy).to receive(:save)
+ @knife.run
+ end
+
+ it "should not save the unedited environment data" do
+ expect(@environment).not_to receive(:save)
+ @knife.run
+ end
+
+ it "should not print the environment" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ it "should show usage and exit when no environment name is provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "with --print-after" do
+ it "should pretty print the environment, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife.ui).to receive(:output).with(@environment)
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/environment_from_file_spec.rb b/knife/spec/unit/knife/environment_from_file_spec.rb
new file mode 100644
index 0000000000..fb9329eb57
--- /dev/null
+++ b/knife/spec/unit/knife/environment_from_file_spec.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Author:: Seth Falcon (<seth@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::EnvironmentFromFile.load_deps
+
+describe Chef::Knife::EnvironmentFromFile do
+ before(:each) do
+ allow(ChefUtils).to receive(:windows?) { false }
+ @knife = Chef::Knife::EnvironmentFromFile.new
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ @knife.name_args = [ "spec.rb" ]
+
+ @environment = Chef::Environment.new
+ @environment.name("spec")
+ @environment.description("runs the unit tests")
+ @environment.cookbook_versions({ "apt" => "= 1.2.3" })
+ allow(@environment).to receive(:save).and_return true
+ allow(@knife.loader).to receive(:load_from).and_return @environment
+ end
+
+ describe "run" do
+ it "loads the environment data from a file and saves it" do
+ expect(@knife.loader).to receive(:load_from).with("environments", "spec.rb").and_return(@environment)
+ expect(@environment).to receive(:save)
+ @knife.run
+ end
+
+ context "when handling multiple environments" do
+ before(:each) do
+ @env_apple = @environment.dup
+ @env_apple.name("apple")
+ allow(@knife.loader).to receive(:load_from).with("apple.rb").and_return @env_apple
+ end
+
+ it "loads multiple environments if given" do
+ @knife.name_args = [ "spec.rb", "apple.rb" ]
+ expect(@environment).to receive(:save).twice
+ @knife.run
+ end
+
+ it "loads all environments with -a" do
+ allow(File).to receive(:expand_path).with("./environments/").and_return("/tmp/environments")
+ allow(Dir).to receive(:glob).with("/tmp/environments/*.{json,rb}").and_return(["spec.rb", "apple.rb"])
+ @knife.name_args = []
+ allow(@knife).to receive(:config).and_return({ all: true })
+ expect(@environment).to receive(:save).twice
+ @knife.run
+ end
+ end
+
+ it "should not print the environment" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ it "should show usage and exit if not filename is provided" do
+ @knife.name_args = []
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ describe "with --print-after" do
+ it "should pretty print the environment, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/environment_list_spec.rb b/knife/spec/unit/knife/environment_list_spec.rb
new file mode 100644
index 0000000000..4f44a93f60
--- /dev/null
+++ b/knife/spec/unit/knife/environment_list_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentList do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentList.new
+ allow(@knife).to receive(:msg).and_return true
+ allow(@knife).to receive(:output).and_return true
+ allow(@knife).to receive(:show_usage).and_return true
+
+ @environments = {
+ "production" => "http://localhost:4000/environments/production",
+ "development" => "http://localhost:4000/environments/development",
+ "testing" => "http://localhost:4000/environments/testing",
+ }
+ allow(Chef::Environment).to receive(:list).and_return @environments
+ end
+
+ it "should make an api call to list the environments" do
+ expect(Chef::Environment).to receive(:list)
+ @knife.run
+ end
+
+ it "should print the environment names in a sorted list" do
+ names = @environments.keys.sort { |a, b| a <=> b }
+ expect(@knife).to receive(:output).with(names)
+ @knife.run
+ end
+
+ describe "with --with-uri" do
+ it "should print and unsorted list of the environments and their URIs" do
+ @knife.config[:with_uri] = true
+ expect(@knife).to receive(:output).with(@environments)
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/environment_show_spec.rb b/knife/spec/unit/knife/environment_show_spec.rb
new file mode 100644
index 0000000000..536afcc058
--- /dev/null
+++ b/knife/spec/unit/knife/environment_show_spec.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Stephen Delano (<stephen@ospcode.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::EnvironmentShow do
+ before(:each) do
+ @knife = Chef::Knife::EnvironmentShow.new
+ allow(@knife).to receive(:msg).and_return true
+ allow(@knife).to receive(:output).and_return true
+ allow(@knife).to receive(:show_usage).and_return true
+ @knife.name_args = [ "production" ]
+
+ @environment = Chef::Environment.new
+ @environment.name("production")
+ @environment.description("Look at me!")
+ allow(Chef::Environment).to receive(:load).and_return @environment
+ end
+
+ it "should load the environment" do
+ expect(Chef::Environment).to receive(:load).with("production")
+ @knife.run
+ end
+
+ it "should pretty print the environment, formatted for display" do
+ expect(@knife).to receive(:format_for_display).with(@environment)
+ expect(@knife).to receive(:output)
+ @knife.run
+ end
+
+ it "should show usage and exit when no environment name is provided" do
+ @knife.name_args = []
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/knife/spec/unit/knife/key_create_spec.rb b/knife/spec/unit/knife/key_create_spec.rb
new file mode 100644
index 0000000000..91d3fc0f69
--- /dev/null
+++ b/knife/spec/unit/knife/key_create_spec.rb
@@ -0,0 +1,223 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/user_key_create"
+require "chef/knife/client_key_create"
+require "chef/knife/key_create"
+require "chef/key"
+
+describe "key create commands that inherit knife" do
+ shared_examples_for "a key create command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do
+ expect(Chef::Knife::KeyCreate).to receive(:new)
+ .with("charmander", command.actor_field_name, command.ui, command.config)
+ .and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key create command
+
+ describe Chef::Knife::UserKeyCreate do
+ it_should_behave_like "a key create command"
+ # defined in key_helper.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:params) { ["charmander"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyCreate do
+ it_should_behave_like "a key create command"
+ # defined in key_helper.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:params) { ["charmander"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyCreate do
+ let(:public_key) do
+ "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+ end
+ let(:config) { {} }
+ let(:actor) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key create run command" do
+ let(:key_create_object) do
+ described_class.new(actor, actor_field_name, ui, config)
+ end
+
+ context "when public_key and key_name weren't passed" do
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect { key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg)
+ end
+ end
+
+ context "when the command is run" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ }
+ end
+
+ before do
+ allow(File).to receive(:read).and_return(public_key)
+ allow(File).to receive(:expand_path)
+
+ allow(key_create_object).to receive(:output_private_key_to_file)
+ allow(key_create_object).to receive(:display_private_key)
+ allow(key_create_object).to receive(:edit_hash).and_return(expected_hash)
+ allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ allow(key_create_object).to receive(:display_info)
+ end
+
+ context "when a valid hash is passed" do
+ let(:key_name) { "charmander-key" }
+ let(:valid_expiration_date) { "2020-12-24T21:00:00Z" }
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "expiration_date" => valid_expiration_date,
+ "key_name" => key_name,
+ }
+ end
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ key_create_object.config[:expiration_Date] = valid_expiration_date,
+ key_create_object.config[:key_name] = key_name
+ end
+
+ it "creates the proper hash" do
+ expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+ key_create_object.run
+ end
+ end
+
+ context "when public_key is passed" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ }
+ end
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ end
+
+ it "calls File.expand_path with the public_key input" do
+ expect(File).to receive(:expand_path).with("public_key_path")
+ key_create_object.run
+ end
+ end # when public_key is passed
+
+ context "when public_key isn't passed and key_name is" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "name" => "charmander-key",
+ "create_key" => true,
+ }
+ end
+ before do
+ key_create_object.config[:key_name] = "charmander-key"
+ end
+
+ it "should set create_key to true" do
+ expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+ key_create_object.run
+ end
+ end
+
+ context "when the server returns a private key" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "private_key" => "super_private",
+ }
+ end
+
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ end
+
+ context "when file is not passed" do
+ it "calls display_private_key with the private_key" do
+ expect(key_create_object).to receive(:display_private_key).with("super_private")
+ key_create_object.run
+ end
+ end
+
+ context "when file is passed" do
+ before do
+ key_create_object.config[:file] = "/fake/file"
+ end
+
+ it "calls output_private_key_to_file with the private_key" do
+ expect(key_create_object).to receive(:output_private_key_to_file).with("super_private")
+ key_create_object.run
+ end
+ end
+ end # when the server returns a private key
+ end # when the command is run
+ end # key create run command"
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key create run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key create run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/key_delete_spec.rb b/knife/spec/unit/knife/key_delete_spec.rb
new file mode 100644
index 0000000000..ff669446bb
--- /dev/null
+++ b/knife/spec/unit/knife/key_delete_spec.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/user_key_delete"
+require "chef/knife/client_key_delete"
+require "chef/knife/key_delete"
+require "chef/key"
+
+describe "key delete commands that inherit knife" do
+ shared_examples_for "a key delete command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { %w{charmander charmander-key} }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do
+ expect(Chef::Knife::KeyDelete).to receive(:new)
+ .with("charmander-key", "charmander", command.actor_field_name, command.ui)
+ .and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key delete command
+
+ describe Chef::Knife::UserKeyDelete do
+ it_should_behave_like "a key delete command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyDelete do
+ it_should_behave_like "a key delete command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+end
+
+describe Chef::Knife::KeyDelete do
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander-key" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key delete run command" do
+ let(:key_delete_object) do
+ described_class.new(keyname, actor, actor_field_name, ui)
+ end
+
+ before do
+ allow_any_instance_of(Chef::Key).to receive(:destroy)
+ allow(key_delete_object).to receive(:print_destroyed)
+ allow(key_delete_object).to receive(:confirm!)
+ end
+
+ context "when the command is run" do
+ it "calls Chef::Key.new with the proper input" do
+ expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original
+ key_delete_object.run
+ end
+
+ it "calls name on the Chef::Key instance with the proper input" do
+ expect_any_instance_of(Chef::Key).to receive(:name).with(keyname)
+ key_delete_object.run
+ end
+
+ it "calls destroy on the Chef::Key instance" do
+ expect_any_instance_of(Chef::Key).to receive(:destroy).once
+ key_delete_object.run
+ end
+
+ it "calls confirm!" do
+ expect(key_delete_object).to receive(:confirm!)
+ key_delete_object.run
+ end
+
+ it "calls print_destroyed" do
+ expect(key_delete_object).to receive(:print_destroyed)
+ key_delete_object.run
+ end
+ end # when the command is run
+
+ end # key delete run command
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key delete run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key delete run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/key_edit_spec.rb b/knife/spec/unit/knife/key_edit_spec.rb
new file mode 100644
index 0000000000..ae58d281b7
--- /dev/null
+++ b/knife/spec/unit/knife/key_edit_spec.rb
@@ -0,0 +1,264 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/user_key_edit"
+require "chef/knife/client_key_edit"
+require "chef/knife/key_edit"
+require "chef/key"
+
+describe "key edit commands that inherit knife" do
+ shared_examples_for "a key edit command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { %w{charmander charmander-key} }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do
+ expect(Chef::Knife::KeyEdit).to receive(:new)
+ .with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config)
+ .and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key edit command
+
+ describe Chef::Knife::UserKeyEdit do
+ it_should_behave_like "a key edit command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyEdit do
+ it_should_behave_like "a key edit command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+end
+
+describe Chef::Knife::KeyEdit do
+ let(:public_key) do
+ "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+ end
+ let(:config) { {} }
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander-key" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key edit run command" do
+ let(:key_edit_object) do
+ described_class.new(keyname, actor, actor_field_name, ui, config)
+ end
+
+ context "when the command is run" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ }
+ end
+ let(:new_keyname) { "charizard-key" }
+
+ before do
+ allow(File).to receive(:read).and_return(public_key)
+ allow(File).to receive(:expand_path)
+
+ allow(key_edit_object).to receive(:output_private_key_to_file)
+ allow(key_edit_object).to receive(:display_private_key)
+ allow(key_edit_object).to receive(:edit_hash).and_return(expected_hash)
+ allow(key_edit_object).to receive(:display_info)
+ end
+
+ context "when public_key and create_key are passed" do
+ before do
+ key_edit_object.config[:public_key] = "public_key_path"
+ key_edit_object.config[:create_key] = true
+ end
+
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect { key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg)
+ end
+ end
+
+ context "when key_name is passed" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "name" => new_keyname,
+ }
+ end
+ before do
+ key_edit_object.config[:key_name] = new_keyname
+ allow_any_instance_of(Chef::Key).to receive(:update)
+ end
+
+ it "update_key_from_hash gets passed a hash with new key name" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+
+ it "Chef::Key.update is passed a string containing the original keyname" do
+ expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+
+ it "Chef::Key.update is not passed a string containing the new keyname" do
+ expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/)
+ allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+ end
+
+ context "when public_key, key_name, and expiration_date are passed" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "name" => new_keyname,
+ "expiration_date" => "infinity",
+ }
+ end
+ before do
+ key_edit_object.config[:public_key] = "this-public-key"
+ key_edit_object.config[:key_name] = new_keyname
+ key_edit_object.config[:expiration_date] = "infinity"
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ it "passes the right hash to update_key_from_hash" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+ key_edit_object.run
+ end
+ end
+
+ context "when create_key is passed" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "create_key" => true,
+ }
+ end
+
+ before do
+ key_edit_object.config[:create_key] = true
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ it "passes the right hash to update_key_from_hash" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+ key_edit_object.run
+ end
+ end
+
+ context "when public_key is passed" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ }
+ end
+ before do
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.config[:public_key] = "public_key_path"
+ end
+
+ it "calls File.expand_path with the public_key input" do
+ expect(File).to receive(:expand_path).with("public_key_path")
+ key_edit_object.run
+ end
+ end # when public_key is passed
+
+ context "when the server returns a private key" do
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "private_key" => "super_private",
+ }
+ end
+
+ before do
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.config[:public_key] = "public_key_path"
+ end
+
+ context "when file is not passed" do
+ it "calls display_private_key with the private_key" do
+ expect(key_edit_object).to receive(:display_private_key).with("super_private")
+ key_edit_object.run
+ end
+ end
+
+ context "when file is passed" do
+ before do
+ key_edit_object.config[:file] = "/fake/file"
+ end
+
+ it "calls output_private_key_to_file with the private_key" do
+ expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private")
+ key_edit_object.run
+ end
+ end
+ end # when the server returns a private key
+
+ end # when the command is run
+
+ end # key edit run command
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key edit run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key edit run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/key_helper.rb b/knife/spec/unit/knife/key_helper.rb
new file mode 100644
index 0000000000..c58d383703
--- /dev/null
+++ b/knife/spec/unit/knife/key_helper.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+shared_examples_for "a knife key command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args" do
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.actor_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ it "properly defines the actor" do
+ expect(command.actor).to eq("charmander")
+ end
+ end # after apply_params! is called with valid args
+
+ context "when the command is run" do
+ before do
+ allow(command).to receive(:service_object).and_return(service_object)
+ allow(command).to receive(:name_args).and_return(["charmander"])
+ end
+
+ context "when the command is successful" do
+ before do
+ expect(service_object).to receive(:run)
+ end
+ end
+ end
+end # a knife key command
diff --git a/knife/spec/unit/knife/key_list_spec.rb b/knife/spec/unit/knife/key_list_spec.rb
new file mode 100644
index 0000000000..3cb8a1c58d
--- /dev/null
+++ b/knife/spec/unit/knife/key_list_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/user_key_list"
+require "chef/knife/client_key_list"
+require "chef/knife/key_list"
+require "chef/key"
+
+describe "key list commands that inherit knife" do
+ shared_examples_for "a key list command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyList with the correct args" do
+ expect(Chef::Knife::KeyList).to receive(:new)
+ .with("charmander", command.list_method, command.ui, command.config)
+ .and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key list command
+
+ describe Chef::Knife::UserKeyList do
+ it_should_behave_like "a key list command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:params) { ["charmander"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyList do
+ it_should_behave_like "a key list command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:params) { ["charmander"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyList do
+ let(:config) { {} }
+ let(:actor) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key list run command" do
+ let(:key_list_object) do
+ described_class.new(actor, list_method, ui, config)
+ end
+
+ before do
+ allow(Chef::Key).to receive(list_method).and_return(http_response)
+ allow(key_list_object).to receive(:display_info)
+ # simply pass the string though that colorize takes in
+ allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input|
+ input
+ end
+ end
+
+ context "when only_expired and only_non_expired were both passed" do
+ before do
+ key_list_object.config[:only_expired] = true
+ key_list_object.config[:only_non_expired] = true
+ end
+
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect { key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg)
+ end
+ end
+
+ context "when the command is run" do
+ before do
+ key_list_object.config[:only_expired] = false
+ key_list_object.config[:only_non_expired] = false
+ key_list_object.config[:with_details] = false
+ end
+
+ it "calls Chef::Key with the proper list command and input" do
+ expect(Chef::Key).to receive(list_method).with(actor)
+ key_list_object.run
+ end
+
+ it "displays all the keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+ key_list_object.run
+ end
+
+ context "when only_expired is called" do
+ before do
+ key_list_object.config[:only_expired] = true
+ end
+
+ it "excludes displaying non-expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "displays the expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+ key_list_object.run
+ end
+ end # when only_expired is called
+
+ context "when only_non_expired is called" do
+ before do
+ key_list_object.config[:only_non_expired] = true
+ end
+
+ it "excludes displaying expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "displays the non-expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+ key_list_object.run
+ end
+ end # when only_expired is called
+
+ context "when with_details is false" do
+ before do
+ key_list_object.config[:with_details] = false
+ end
+
+ it "does not display the uri" do
+ expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "does not display the expired status" do
+ expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times
+ key_list_object.run
+ end
+ end # when with_details is false
+
+ context "when with_details is true" do
+ before do
+ key_list_object.config[:with_details] = true
+ end
+
+ it "displays the uri" do
+ expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times
+ key_list_object.run
+ end
+
+ it "displays the expired status" do
+ expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once
+ key_list_object.run
+ end
+ end # when with_details is true
+
+ end # when the command is run
+
+ end # key list run command
+
+ context "when list_method is :list_by_user" do
+ it_should_behave_like "key list run command" do
+ let(:list_method) { :list_by_user }
+ let(:http_response) do
+ [
+ { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false },
+ { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false },
+ { "uri" => "https://api.opscode.piab/users/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true },
+ ]
+ end
+ end
+ end
+
+ context "when list_method is :list_by_client" do
+ it_should_behave_like "key list run command" do
+ let(:list_method) { :list_by_client }
+ let(:http_response) do
+ [
+ { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false },
+ { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false },
+ { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true },
+ ]
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/key_show_spec.rb b/knife/spec/unit/knife/key_show_spec.rb
new file mode 100644
index 0000000000..ace6dad990
--- /dev/null
+++ b/knife/spec/unit/knife/key_show_spec.rb
@@ -0,0 +1,126 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/user_key_show"
+require "chef/knife/client_key_show"
+require "chef/knife/key_show"
+require "chef/key"
+
+describe "key show commands that inherit knife" do
+ shared_examples_for "a key show command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { %w{charmander charmander-key} }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyShow with the correct args" do
+ expect(Chef::Knife::KeyShow).to receive(:new)
+ .with("charmander-key", "charmander", command.load_method, command.ui)
+ .and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key show command
+
+ describe Chef::Knife::UserKeyShow do
+ it_should_behave_like "a key show command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyShow do
+ it_should_behave_like "a key show command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:params) { %w{charmander charmander-key} }
+ end
+ end
+end
+
+describe Chef::Knife::KeyShow do
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+ let(:expected_hash) do
+ {
+ actor_field_name => "charmander",
+ "name" => "charmander-key",
+ "public_key" => "some-public-key",
+ "expiration_date" => "infinity",
+ }
+ end
+
+ shared_examples_for "key show run command" do
+ let(:key_show_object) do
+ described_class.new(keyname, actor, load_method, ui)
+ end
+
+ before do
+ allow(key_show_object).to receive(:display_output)
+ allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ context "when the command is run" do
+ it "loads the key using the proper method and args" do
+ expect(Chef::Key).to receive(load_method).with(actor, keyname)
+ key_show_object.run
+ end
+
+ it "displays the key" do
+ expect(key_show_object).to receive(:display_output)
+ key_show_object.run
+ end
+ end
+ end
+
+ context "when load_method is :load_by_user" do
+ it_should_behave_like "key show run command" do
+ let(:load_method) { :load_by_user }
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when load_method is :load_by_client" do
+ it_should_behave_like "key show run command" do
+ let(:load_method) { :load_by_client }
+ let(:actor_field_name) { "user" }
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_bulk_delete_spec.rb b/knife/spec/unit/knife/node_bulk_delete_spec.rb
new file mode 100644
index 0000000000..cf38d542fa
--- /dev/null
+++ b/knife/spec/unit/knife/node_bulk_delete_spec.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeBulkDelete do
+ before(:each) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeBulkDelete.new
+ @knife.name_args = ["."]
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ @nodes = {}
+ %w{adam brent jacob}.each do |node_name|
+ @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}"
+ end
+ end
+
+ describe "when creating the list of nodes" do
+ it "fetches the node list" do
+ expected = @nodes.inject({}) do |inflatedish, (name, uri)|
+ inflatedish[name] = Chef::Node.new.tap { |n| n.name(name) }
+ inflatedish
+ end
+ expect(Chef::Node).to receive(:list).and_return(@nodes)
+ # I hate not having == defined for anything :(
+ actual = @knife.all_nodes
+ expect(actual.keys).to match_array(expected.keys)
+ expect(actual.values.map(&:name)).to match_array(%w{adam brent jacob})
+ end
+ end
+
+ describe "run" do
+ before do
+ @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name|
+ node = Chef::Node.new
+ node.name(name)
+ allow(node).to receive(:destroy).and_return(true)
+ nodes_by_name[name] = node
+ nodes_by_name
+ end
+ allow(@knife).to receive(:all_nodes).and_return(@inflatedish_list)
+ end
+
+ it "should print the nodes you are about to delete" do
+ @knife.run
+ expect(@stdout.string).to match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/)
+ end
+
+ it "should confirm you really want to delete them" do
+ expect(@knife.ui).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should delete each node" do
+ @inflatedish_list.each_value do |n|
+ expect(n).to receive(:destroy)
+ end
+ @knife.run
+ end
+
+ it "should only delete nodes that match the regex" do
+ @knife.name_args = ["adam"]
+ expect(@inflatedish_list["adam"]).to receive(:destroy)
+ expect(@inflatedish_list["brent"]).not_to receive(:destroy)
+ expect(@inflatedish_list["jacob"]).not_to receive(:destroy)
+ @knife.run
+ end
+
+ it "should exit if the regex is not provided" do
+ @knife.name_args = []
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/node_delete_spec.rb b/knife/spec/unit/knife/node_delete_spec.rb
new file mode 100644
index 0000000000..92932c0b6f
--- /dev/null
+++ b/knife/spec/unit/knife/node_delete_spec.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeDelete do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeDelete.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = %w{ adam ben }
+ allow(@knife).to receive(:output).and_return(true)
+ allow(@knife).to receive(:confirm).and_return(true)
+
+ @adam_node = Chef::Node.new
+ @ben_node = Chef::Node.new
+ allow(@ben_node).to receive(:destroy).and_return(true)
+ allow(@adam_node).to receive(:destroy).and_return(true)
+ allow(Chef::Node).to receive(:load).with("adam").and_return(@adam_node)
+ allow(Chef::Node).to receive(:load).with("ben").and_return(@ben_node)
+
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should confirm that you want to delete" do
+ expect(@knife).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should load the nodes" do
+ expect(Chef::Node).to receive(:load).with("adam").and_return(@adam_node)
+ expect(Chef::Node).to receive(:load).with("ben").and_return(@ben_node)
+ @knife.run
+ end
+
+ it "should delete the nodes" do
+ expect(@adam_node).to receive(:destroy).and_return(@adam_node)
+ expect(@ben_node).to receive(:destroy).and_return(@ben_node)
+ @knife.run
+ end
+
+ it "should not print the node" do
+ expect(@knife).not_to receive(:output).with("poop")
+ @knife.run
+ end
+
+ describe "with -p or --print-after" do
+ it "should pretty print the node, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:format_for_display).with(@adam_node).and_return("adam")
+ expect(@knife).to receive(:format_for_display).with(@ben_node).and_return("ben")
+ expect(@knife).to receive(:output).with("adam")
+ expect(@knife).to receive(:output).with("ben")
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_edit_spec.rb b/knife/spec/unit/knife/node_edit_spec.rb
new file mode 100644
index 0000000000..e89322d415
--- /dev/null
+++ b/knife/spec/unit/knife/node_edit_spec.rb
@@ -0,0 +1,116 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+Chef::Knife::NodeEdit.load_deps
+
+describe Chef::Knife::NodeEdit do
+
+ # helper to convert the view from Chef objects into Ruby objects representing JSON
+ def deserialized_json_view
+ Chef::JSONCompat.from_json(Chef::JSONCompat.to_json_pretty(@knife.node_editor.send(:view)))
+ end
+
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeEdit.new
+ @knife.config = {
+ editor: "cat",
+ attribute: nil,
+ print_after: nil,
+ }
+ @knife.name_args = [ "adam" ]
+ @node = Chef::Node.new
+ end
+
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam").and_return(@node)
+ @knife.node
+ end
+
+ describe "after loading the node" do
+ before do
+ @knife.config[:all_attributes] = false
+
+ allow(@knife).to receive(:node).and_return(@node)
+ @node.automatic_attrs = { go: :away }
+ @node.default_attrs = { hide: :me }
+ @node.override_attrs = { dont: :show }
+ @node.normal_attrs = { do_show: :these }
+ @node.chef_environment("prod")
+ @node.run_list("recipe[foo]")
+ end
+
+ it "creates a view of the node without attributes from roles or ohai" do
+ actual = deserialized_json_view
+ expect(actual).not_to have_key("automatic")
+ expect(actual).not_to have_key("override")
+ expect(actual).not_to have_key("default")
+ expect(actual["normal"]).to eq({ "do_show" => "these" })
+ expect(actual["run_list"]).to eq(["recipe[foo]"])
+ expect(actual["chef_environment"]).to eq("prod")
+ end
+
+ it "shows the extra attributes when given the --all option" do
+ @knife.config[:all_attributes] = true
+
+ actual = deserialized_json_view
+ expect(actual["automatic"]).to eq({ "go" => "away" })
+ expect(actual["override"]).to eq({ "dont" => "show" })
+ expect(actual["default"]).to eq({ "hide" => "me" })
+ expect(actual["normal"]).to eq({ "do_show" => "these" })
+ expect(actual["run_list"]).to eq(["recipe[foo]"])
+ expect(actual["chef_environment"]).to eq("prod")
+ end
+
+ it "does not consider unedited data updated" do
+ view = deserialized_json_view
+ @knife.node_editor.send(:apply_updates, view)
+ expect(@knife.node_editor).not_to be_updated
+ end
+
+ it "considers edited data updated" do
+ view = deserialized_json_view
+ view["run_list"] << "role[fuuu]"
+ @knife.node_editor.send(:apply_updates, view)
+ expect(@knife.node_editor).to be_updated
+ end
+
+ end
+
+ describe "edit_node" do
+
+ before do
+ allow(@knife).to receive(:node).and_return(@node)
+ end
+
+ let(:subject) { @knife.node_editor.edit_node }
+
+ it "raises an exception when editing is disabled" do
+ @knife.config[:disable_editing] = true
+ expect { subject }.to raise_error(SystemExit)
+ end
+
+ it "raises an exception when the editor is not set" do
+ @knife.config[:editor] = nil
+ expect { subject }.to raise_error(SystemExit)
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/node_environment_set_spec.rb b/knife/spec/unit/knife/node_environment_set_spec.rb
new file mode 100644
index 0000000000..c2d55d0ab1
--- /dev/null
+++ b/knife/spec/unit/knife/node_environment_set_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Jimmy McCrory (<jimmy.mccrory@gmail.com>)
+# Copyright:: Copyright 2014-2016, Jimmy McCrory
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeEnvironmentSet do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeEnvironmentSet.new
+ @knife.name_args = %w{adam bar}
+ allow(@knife).to receive(:output).and_return(true)
+ @node = Chef::Node.new
+ @node.name("knifetest-node")
+ @node.chef_environment << "foo"
+ allow(@node).to receive(:save).and_return(true)
+ allow(Chef::Node).to receive(:load).and_return(@node)
+ end
+
+ describe "run" do
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam")
+ @knife.run
+ end
+
+ it "should update the environment" do
+ @knife.run
+ expect(@node.chef_environment).to eq("bar")
+ end
+
+ it "should save the node" do
+ expect(@node).to receive(:save)
+ @knife.run
+ end
+
+ it "sets the environment to config for display" do
+ @knife.run
+ expect(@knife.config[:environment]).to eq("bar")
+ end
+
+ it "should print the environment" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.run
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/node_from_file_spec.rb b/knife/spec/unit/knife/node_from_file_spec.rb
new file mode 100644
index 0000000000..359b9726b6
--- /dev/null
+++ b/knife/spec/unit/knife/node_from_file_spec.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::NodeFromFile.load_deps
+
+describe Chef::Knife::NodeFromFile do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeFromFile.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "adam.rb" ]
+ allow(@knife).to receive(:output).and_return(true)
+ allow(@knife).to receive(:confirm).and_return(true)
+ @node = Chef::Node.new
+ allow(@node).to receive(:save)
+ allow(@knife.loader).to receive(:load_from).and_return(@node)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should load from a file" do
+ expect(@knife.loader).to receive(:load_from).with("nodes", "adam.rb").and_return(@node)
+ @knife.run
+ end
+
+ it "should not print the Node" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ describe "with -p or --print-after" do
+ it "should print the Node" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_list_spec.rb b/knife/spec/unit/knife/node_list_spec.rb
new file mode 100644
index 0000000000..baa79cb81f
--- /dev/null
+++ b/knife/spec/unit/knife/node_list_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeList do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded
+ @knife = Chef::Knife::NodeList.new
+ allow(@knife).to receive(:output).and_return(true)
+ @list = {
+ "foo" => "http://example.com/foo",
+ "bar" => "http://example.com/foo",
+ }
+ allow(Chef::Node).to receive(:list).and_return(@list)
+ allow(Chef::Node).to receive(:list_by_environment).and_return(@list)
+ end
+
+ describe "run" do
+ it "should list all of the nodes if -E is not specified" do
+ expect(Chef::Node).to receive(:list).and_return(@list)
+ @knife.run
+ end
+
+ it "should pretty print the list" do
+ expect(Chef::Node).to receive(:list).and_return(@list)
+ expect(@knife).to receive(:output).with(%w{bar foo})
+ @knife.run
+ end
+
+ it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do
+ Chef::Config[:environment] = "prod"
+ expect(Chef::Node).to receive(:list_by_environment).with("prod").and_return(@list)
+ @knife.run
+ end
+
+ describe "with -w or --with-uri" do
+ it "should pretty print the hash" do
+ @knife.config[:with_uri] = true
+ expect(Chef::Node).to receive(:list).and_return(@list)
+ expect(@knife).to receive(:output).with(@list)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_policy_set_spec.rb b/knife/spec/unit/knife/node_policy_set_spec.rb
new file mode 100644
index 0000000000..5815da29df
--- /dev/null
+++ b/knife/spec/unit/knife/node_policy_set_spec.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodePolicySet do
+ let(:node) do
+ node = Chef::Node.new
+ node.name("adam")
+ node.run_list = ["role[base]"]
+ node
+ end
+
+ let(:knife) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+ Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
+ knife_obj = Chef::Knife::NodePolicySet.new(bootstrap_cli_options)
+ knife_obj.merge_configs
+ allow(knife_obj.ui).to receive(:stderr).and_return(stderr)
+ allow(knife_obj).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ knife_obj
+ end
+
+ let(:stderr) { StringIO.new }
+ let(:bootstrap_template) { nil }
+ let(:bootstrap_cli_options) { [ ] }
+
+ describe "#run" do
+ context "when node_name is not given" do
+ let(:bootstrap_cli_options) { %w{ } }
+ it "returns an error that you must specify a node name" do
+ expect { knife.send(:validate_node!) }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: You must specify a node name")
+ end
+ end
+
+ context "when node is given" do
+ let(:bootstrap_cli_options) { %w{ adam staging my-app } }
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
+ allow(node).to receive(:save).and_return(true)
+ knife.run
+ end
+ end
+
+ context "when node not saved" do
+ let(:bootstrap_cli_options) { %w{ adam staging my-app } }
+ it "returns an error node not updated successfully" do
+ allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
+ allow(node).to receive(:save).and_return(false)
+ knife.run
+ expect(stderr.string.strip).to eq("Error in updating node #{node.name}")
+ end
+ end
+
+ context "when the policy is set successfully on the node" do
+ let(:bootstrap_cli_options) { %w{ adam staging my-app } }
+ it "returns node updated successfully" do
+ allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
+ allow(node).to receive(:save).and_return(true)
+ knife.run
+ expect(stderr.string.strip).to eq("Successfully set the policy on node #{node.name}")
+ end
+ end
+ end
+
+ describe "handling policy options" do
+ context "when policy_group and policy_name is not given" do
+ let(:bootstrap_cli_options) { %w{ } }
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
+ end
+ end
+
+ context "when only policy_name is given" do
+ let(:bootstrap_cli_options) { %w{ adam staging } }
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
+ end
+ end
+
+ context "when only policy_group is given" do
+ let(:bootstrap_cli_options) { %w{ adam my-app } }
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
+ end
+ end
+
+ context "when policy_name and policy_group are given with no conflicting options" do
+ let(:bootstrap_cli_options) { %w{ adam staging my-app } }
+ it "passes options validation" do
+ expect { knife.send(:validate_options!) }.to_not raise_error
+ end
+
+ it "returns value set in config" do
+ allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
+ allow(node).to receive(:save).and_return(false)
+ knife.run
+ expect(node.policy_name).to eq("my-app")
+ expect(node.policy_group).to eq("staging")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_run_list_add_spec.rb b/knife/spec/unit/knife/node_run_list_add_spec.rb
new file mode 100644
index 0000000000..87b75d9818
--- /dev/null
+++ b/knife/spec/unit/knife/node_run_list_add_spec.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeRunListAdd do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeRunListAdd.new
+ @knife.config = {
+ after: nil,
+ }
+ @knife.name_args = [ "adam", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+ @node = Chef::Node.new
+ allow(@node).to receive(:save).and_return(true)
+ allow(Chef::Node).to receive(:load).and_return(@node)
+ end
+
+ describe "run" do
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam")
+ @knife.run
+ end
+
+ it "should add to the run list" do
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ end
+
+ it "should save the node" do
+ expect(@node).to receive(:save)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.run
+ end
+
+ describe "with -a or --after specified" do
+ it "should add to the run list after the specified entry" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[barn]"
+ @knife.config[:after] = "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ expect(@node.run_list[2]).to eq("role[barn]")
+ end
+ end
+
+ describe "with -b or --before specified" do
+ it "should add to the run list before the specified entry" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[barn]"
+ @knife.config[:before] = "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[acorns]")
+ expect(@node.run_list[2]).to eq("role[barn]")
+ end
+ end
+
+ describe "with both --after and --before specified" do
+ it "exits with an error" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[barn]"
+ @knife.config[:before] = "role[acorns]"
+ @knife.config[:after] = "role[acorns]"
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with more than one role or recipe" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
+ @node.run_list << "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ expect(@node.run_list[2]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe with space between items" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
+ @node.run_list << "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ expect(@node.run_list[2]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+ @node.run_list << "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ expect(@node.run_list[2]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments and list separated by commas" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+ @node.run_list << "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ expect(@node.run_list[2]).to eq("role[duck]")
+ end
+ end
+
+ describe "with one role or recipe but with an extraneous comma" do
+ it "should add to the run list one item" do
+ @knife.name_args = [ "adam", "role[monkey]," ]
+ @node.run_list << "role[acorns]"
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[monkey]")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_run_list_remove_spec.rb b/knife/spec/unit/knife/node_run_list_remove_spec.rb
new file mode 100644
index 0000000000..0eff7c6d27
--- /dev/null
+++ b/knife/spec/unit/knife/node_run_list_remove_spec.rb
@@ -0,0 +1,106 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeRunListRemove do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeRunListRemove.new
+ @knife.config[:print_after] = nil
+ @knife.name_args = [ "adam", "role[monkey]" ]
+ @node = Chef::Node.new
+ @node.name("knifetest-node")
+ @node.run_list << "role[monkey]"
+ allow(@node).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:output).and_return(true)
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+
+ allow(Chef::Node).to receive(:load).and_return(@node)
+ end
+
+ describe "run" do
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam").and_return(@node)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @knife.run
+ expect(@node.run_list[0]).not_to eq("role[monkey]")
+ end
+
+ it "should save the node" do
+ expect(@node).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ @knife.config[:print_after] = true
+ expect(@knife.ui).to receive(:output).with({ "knifetest-node" => { "run_list" => [] } })
+ @knife.run
+ end
+
+ describe "run with a list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @node.run_list << "role[monkey]"
+ @node.run_list << "recipe[duck::type]"
+ @knife.name_args = [ "adam", "role[monkey],recipe[duck::type]" ]
+ @knife.run
+ expect(@node.run_list).not_to include("role[monkey]")
+ expect(@node.run_list).not_to include("recipe[duck::type]")
+ end
+
+ it "should remove the items from the run list when name args contains whitespace" do
+ @node.run_list << "role[monkey]"
+ @node.run_list << "recipe[duck::type]"
+ @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]" ]
+ @knife.run
+ expect(@node.run_list).not_to include("role[monkey]")
+ expect(@node.run_list).not_to include("recipe[duck::type]")
+ end
+
+ it "should remove the items from the run list when name args contains multiple run lists" do
+ @node.run_list << "role[blah]"
+ @node.run_list << "recipe[duck::type]"
+ @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]", "role[blah]" ]
+ @knife.run
+ expect(@node.run_list).not_to include("role[monkey]")
+ expect(@node.run_list).not_to include("recipe[duck::type]")
+ end
+
+ it "should warn when the thing to remove is not in the runlist" do
+ @node.run_list << "role[blah]"
+ @node.run_list << "recipe[duck::type]"
+ @knife.name_args = [ "adam", "role[blork]" ]
+ expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list")
+ @knife.run
+ end
+
+ it "should warn even more when the thing to remove is not in the runlist and unqualified" do
+ @node.run_list << "role[blah]"
+ @node.run_list << "recipe[duck::type]"
+ @knife.name_args = %w{adam blork}
+ expect(@knife.ui).to receive(:warn).with("blork is not in the run list")
+ expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/node_run_list_set_spec.rb b/knife/spec/unit/knife/node_run_list_set_spec.rb
new file mode 100644
index 0000000000..35fdd63e4d
--- /dev/null
+++ b/knife/spec/unit/knife/node_run_list_set_spec.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Mike Fiedler (<miketheman@gmail.com>)
+# Copyright:: Copyright 2013-2016, Mike Fiedler
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeRunListSet do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::NodeRunListSet.new
+ @knife.config = {}
+ @knife.name_args = [ "adam", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+ @node = Chef::Node.new
+ allow(@node).to receive(:save).and_return(true)
+ allow(Chef::Node).to receive(:load).and_return(@node)
+ end
+
+ describe "run" do
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam")
+ @knife.run
+ end
+
+ it "should set the run list" do
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ end
+
+ it "should save the node" do
+ expect(@node).to receive(:save)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.run
+ end
+
+ describe "with more than one role or recipe" do
+ it "should set the run list to all the entries" do
+ @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe with space between items" do
+ it "should set the run list to all the entries" do
+ @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments" do
+ it "should set the run list to all the entries" do
+ @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[duck]")
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments and list separated by comas" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[duck]")
+ end
+ end
+
+ describe "with one role or recipe but with an extraneous comma" do
+ it "should add to the run list one item" do
+ @knife.name_args = [ "adam", "role[monkey]," ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ end
+ end
+
+ describe "with an existing run list" do
+ it "should overwrite any existing run list items" do
+ @node.run_list << "role[acorns]"
+ @node.run_list << "role[zebras]"
+ expect(@node.run_list[0]).to eq("role[acorns]")
+ expect(@node.run_list[1]).to eq("role[zebras]")
+ expect(@node.run_list.run_list_items.size).to eq(2)
+
+ @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+ @knife.run
+ expect(@node.run_list[0]).to eq("role[monkey]")
+ expect(@node.run_list[1]).to eq("role[duck]")
+ expect(@node.run_list.run_list_items.size).to eq(2)
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/node_show_spec.rb b/knife/spec/unit/knife/node_show_spec.rb
new file mode 100644
index 0000000000..c26ae94f40
--- /dev/null
+++ b/knife/spec/unit/knife/node_show_spec.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::NodeShow do
+
+ let(:node) do
+ node = Chef::Node.new
+ node.name("adam")
+ node.run_list = ["role[base]"]
+ node
+ end
+
+ let(:knife) do
+ knife = Chef::Knife::NodeShow.new
+ knife.name_args = [ "adam" ]
+ knife
+ end
+
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ end
+
+ describe "run" do
+ it "should load the node" do
+ expect(Chef::Node).to receive(:load).with("adam").and_return(node)
+ allow(knife).to receive(:output).and_return(true)
+ knife.run
+ end
+
+ it "should pretty print the node, formatted for display" do
+ knife.config[:format] = nil
+ stdout = StringIO.new
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(Chef::Node).to receive(:load).and_return(node)
+ knife.run
+ expect(stdout.string).to eql("Node Name: adam\nEnvironment: _default\nFQDN: \nIP: \nRun List: \nRoles: \nRecipes: \nPlatform: \nTags: \n")
+ end
+
+ it "should pretty print json" do
+ knife.config[:format] = "json"
+ stdout = StringIO.new
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ expect(Chef::Node).to receive(:load).with("adam").and_return(node)
+ knife.run
+ expect(stdout.string).to eql("{\n \"name\": \"adam\",\n \"chef_environment\": \"_default\",\n \"run_list\": [\n\n]\n,\n \"normal\": {\n\n }\n}\n")
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/org_create_spec.rb b/knife/spec/unit/knife/org_create_spec.rb
new file mode 100644
index 0000000000..f45ade2df7
--- /dev/null
+++ b/knife/spec/unit/knife/org_create_spec.rb
@@ -0,0 +1,76 @@
+#
+# Copyright:: Copyright 2014-2016 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 "knife_spec_helper"
+require "chef/org"
+
+describe Chef::Knife::OrgCreate do
+ before :each do
+ Chef::Knife::OrgCreate.load_deps
+ @knife = Chef::Knife::OrgCreate.new
+ @org = double("Chef::Org")
+ allow(Chef::Org).to receive(:new).and_return(@org)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@org).to receive(:private_key).and_return(@key)
+ @org_name = "ss"
+ @org_full_name = "secretsauce"
+ end
+
+ let(:org_args) do
+ {
+ name: @org_name,
+ full_name: @org_full_name,
+ }
+ end
+
+ describe "with no org_name and org_fullname" do
+ it "fails with an informative message" do
+ expect(@knife.ui).to receive(:fatal).with("You must specify an ORG_NAME and an ORG_FULL_NAME")
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with org_name and org_fullname" do
+ before :each do
+ @knife.name_args << @org_name << @org_full_name
+ end
+
+ it "creates an org" do
+ expect(@org).to receive(:create).and_return(@org)
+ expect(@org).to receive(:full_name).with("secretsauce")
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+
+ context "with --assocation-user" do
+ before :each do
+ @knife.config[:association_user] = "ramsay"
+ end
+
+ it "creates an org, associates a user, and adds it to the admins group" do
+ expect(@org).to receive(:full_name).with("secretsauce")
+ expect(@org).to receive(:create).and_return(@org)
+ expect(@org).to receive(:associate_user).with("ramsay")
+ expect(@org).to receive(:add_user_to_group).with("admins", "ramsay")
+ expect(@org).to receive(:add_user_to_group).with("billing-admins", "ramsay")
+ expect(@knife.ui).to receive(:msg).with(@key)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/org_delete_spec.rb b/knife/spec/unit/knife/org_delete_spec.rb
new file mode 100644
index 0000000000..33311bd678
--- /dev/null
+++ b/knife/spec/unit/knife/org_delete_spec.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+describe Chef::Knife::OrgDelete do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::OrgDelete.new
+ @org_name = "foobar"
+ @org_full_name = "secretsauce"
+ @knife.name_args << @org_name
+ @org = double("Chef::Org")
+ end
+
+ it "should confirm that you want to delete and then delete organizations" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(root_rest)
+ expect(@knife.ui).to receive(:confirm).with("Do you want to delete the organization #{@org_name}")
+ expect(root_rest).to receive(:delete).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/knife/spec/unit/knife/org_edit_spec.rb b/knife/spec/unit/knife/org_edit_spec.rb
new file mode 100644
index 0000000000..af9dae2c49
--- /dev/null
+++ b/knife/spec/unit/knife/org_edit_spec.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::OrgEdit do
+ let(:knife) { Chef::Knife::OrgEdit.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ Chef::Knife::OrgEdit.load_deps
+ @org_name = "foobar"
+ knife.name_args << @org_name
+ @org = double("Chef::Org")
+ knife.config[:disable_editing] = true
+ end
+
+ it "loads and edits the organisation" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ original_data = { "org_name" => "my_org" }
+ data = { "org_name" => "my_org1" }
+ expect(root_rest).to receive(:get).with("organizations/foobar").and_return(original_data)
+ expect(knife).to receive(:edit_hash).with(original_data).and_return(data)
+ expect(root_rest).to receive(:put).with("organizations/foobar", data)
+ knife.run
+ end
+
+ it "prints usage and exits when a org name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/knife/spec/unit/knife/org_list_spec.rb b/knife/spec/unit/knife/org_list_spec.rb
new file mode 100644
index 0000000000..aa5fca5099
--- /dev/null
+++ b/knife/spec/unit/knife/org_list_spec.rb
@@ -0,0 +1,58 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+describe Chef::Knife::OrgList do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ let(:orgs) do
+ {
+ "org1" => "first",
+ "org2" => "second",
+ "hiddenhiddenhiddenhi" => "hidden",
+ }
+ end
+
+ before :each do
+ @org = double("Chef::Org")
+ @knife = Chef::Knife::OrgList.new
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(root_rest).to receive(:get).with("organizations").and_return(orgs)
+ end
+
+ describe "with no arguments" do
+ it "lists all non hidden orgs" do
+ expect(@knife.ui).to receive(:output).with(%w{org1 org2})
+ @knife.run
+ end
+
+ end
+
+ describe "with all_orgs argument" do
+ before do
+ @knife.config[:all_orgs] = true
+ end
+
+ it "lists all orgs including hidden orgs" do
+ expect(@knife.ui).to receive(:output).with(%w{hiddenhiddenhiddenhi org1 org2})
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/org_show_spec.rb b/knife/spec/unit/knife/org_show_spec.rb
new file mode 100644
index 0000000000..364b879a7c
--- /dev/null
+++ b/knife/spec/unit/knife/org_show_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+describe Chef::Knife::OrgShow do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::OrgShow.new
+ @org_name = "foobar"
+ @knife.name_args << @org_name
+ @org = double("Chef::Org")
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(@org).to receive(:root_rest).and_return(root_rest)
+ end
+
+ it "should load the organisation" do
+ expect(root_rest).to receive(:get).with("organizations/#{@org_name}")
+ @knife.run
+ end
+
+ it "should pretty print the output organisation" do
+ expect(root_rest).to receive(:get).with("organizations/#{@org_name}")
+ expect(@knife.ui).to receive(:output)
+ @knife.run
+ end
+end
diff --git a/knife/spec/unit/knife/org_user_add_spec.rb b/knife/spec/unit/knife/org_user_add_spec.rb
new file mode 100644
index 0000000000..72ee1d0607
--- /dev/null
+++ b/knife/spec/unit/knife/org_user_add_spec.rb
@@ -0,0 +1,39 @@
+#
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+describe Chef::Knife::OrgUserAdd do
+ context "with --admin" do
+ subject(:knife) { Chef::Knife::OrgUserAdd.new }
+ let(:org) { double("Chef::Org") }
+
+ it "adds the user to admins and billing-admins groups" do
+ allow(Chef::Org).to receive(:new).and_return(org)
+
+ knife.config[:admin] = true
+ knife.name_args = %w{testorg testuser}
+
+ expect(org).to receive(:associate_user).with("testuser")
+ expect(org).to receive(:add_user_to_group).with("admins", "testuser")
+ expect(org).to receive(:add_user_to_group).with("billing-admins", "testuser")
+
+ knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/raw_spec.rb b/knife/spec/unit/knife/raw_spec.rb
new file mode 100644
index 0000000000..90a09a31e6
--- /dev/null
+++ b/knife/spec/unit/knife/raw_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::Raw do
+ let(:rest) do
+ r = double("Chef::Knife::Raw::RawInputServerAPI")
+ allow(Chef::Knife::Raw::RawInputServerAPI).to receive(:new).and_return(r)
+ r
+ end
+
+ let(:knife) do
+ k = Chef::Knife::Raw.new
+ k.config[:method] = "GET"
+ k.name_args = [ "/nodes" ]
+ k
+ end
+
+ describe "run" do
+ it "should set the x-ops-request-source header when --proxy-auth is set" do
+ knife.config[:proxy_auth] = true
+ expect(rest).to receive(:request).with(:GET, "/nodes",
+ { "Content-Type" => "application/json",
+ "x-ops-request-source" => "web" }, false)
+ knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_bulk_delete_spec.rb b/knife/spec/unit/knife/role_bulk_delete_spec.rb
new file mode 100644
index 0000000000..f68efba57c
--- /dev/null
+++ b/knife/spec/unit/knife/role_bulk_delete_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleBulkDelete do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleBulkDelete.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = ["."]
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ @roles = {}
+ %w{dev staging production}.each do |role_name|
+ role = Chef::Role.new
+ role.name(role_name)
+ allow(role).to receive(:destroy).and_return(true)
+ @roles[role_name] = role
+ end
+ allow(Chef::Role).to receive(:list).and_return(@roles)
+ end
+
+ describe "run" do
+
+ it "should get the list of the roles" do
+ expect(Chef::Role).to receive(:list).and_return(@roles)
+ @knife.run
+ end
+
+ it "should print the roles you are about to delete" do
+ @knife.run
+ expect(@stdout.string).to match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/)
+ end
+
+ it "should confirm you really want to delete them" do
+ expect(@knife.ui).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should delete each role" do
+ @roles.each_value do |r|
+ expect(r).to receive(:destroy)
+ end
+ @knife.run
+ end
+
+ it "should only delete roles that match the regex" do
+ @knife.name_args = ["dev"]
+ expect(@roles["dev"]).to receive(:destroy)
+ expect(@roles["staging"]).not_to receive(:destroy)
+ expect(@roles["production"]).not_to receive(:destroy)
+ @knife.run
+ end
+
+ it "should exit if the regex is not provided" do
+ @knife.name_args = []
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/role_create_spec.rb b/knife/spec/unit/knife/role_create_spec.rb
new file mode 100644
index 0000000000..13f47492b1
--- /dev/null
+++ b/knife/spec/unit/knife/role_create_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleCreate do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleCreate.new
+ @knife.config = {
+ description: nil,
+ }
+ @knife.name_args = [ "adam" ]
+ allow(@knife).to receive(:output).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:save)
+ allow(Chef::Role).to receive(:new).and_return(@role)
+ allow(@knife).to receive(:edit_data).and_return(@role)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should create a new role" do
+ expect(Chef::Role).to receive(:new).and_return(@role)
+ @knife.run
+ end
+
+ it "should set the role name" do
+ expect(@role).to receive(:name).with("adam")
+ @knife.run
+ end
+
+ it "should not print the role" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ it "should allow you to edit the data" do
+ expect(@knife).to receive(:edit_data).with(@role, object_class: Chef::Role)
+ @knife.run
+ end
+
+ it "should save the role" do
+ expect(@role).to receive(:save)
+ @knife.run
+ end
+
+ describe "with -d or --description" do
+ it "should set the description" do
+ @knife.config[:description] = "All is bob"
+ expect(@role).to receive(:description).with("All is bob")
+ @knife.run
+ end
+ end
+
+ describe "with -p or --print-after" do
+ it "should pretty print the node, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output).with(@role)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_delete_spec.rb b/knife/spec/unit/knife/role_delete_spec.rb
new file mode 100644
index 0000000000..658da5299d
--- /dev/null
+++ b/knife/spec/unit/knife/role_delete_spec.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleDelete do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleDelete.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "adam" ]
+ allow(@knife).to receive(:output).and_return(true)
+ allow(@knife).to receive(:confirm).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:destroy).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should confirm that you want to delete" do
+ expect(@knife).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should load the Role" do
+ expect(Chef::Role).to receive(:load).with("adam").and_return(@role)
+ @knife.run
+ end
+
+ it "should delete the Role" do
+ expect(@role).to receive(:destroy).and_return(@role)
+ @knife.run
+ end
+
+ it "should not print the Role" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ describe "with -p or --print-after" do
+ it "should pretty print the Role, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_edit_spec.rb b/knife/spec/unit/knife/role_edit_spec.rb
new file mode 100644
index 0000000000..adade177a7
--- /dev/null
+++ b/knife/spec/unit/knife/role_edit_spec.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEdit do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleEdit.new
+ @knife.config[:print_after] = nil
+ @knife.name_args = [ "adam" ]
+ allow(@knife.ui).to receive(:output).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:save)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+ allow(@knife.ui).to receive(:edit_data).and_return(@role)
+ allow(@knife.ui).to receive(:msg)
+ end
+
+ describe "run" do
+ it "should load the role" do
+ expect(Chef::Role).to receive(:load).with("adam").and_return(@role)
+ @knife.run
+ end
+
+ it "should edit the role data" do
+ expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role)
+ @knife.run
+ end
+
+ it "should save the edited role data" do
+ pansy = Chef::Role.new
+
+ @role.name("new_role_name")
+ expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy)
+ expect(pansy).to receive(:save)
+ @knife.run
+ end
+
+ it "should not save the unedited role data" do
+ pansy = Chef::Role.new
+
+ expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy)
+ expect(pansy).not_to receive(:save)
+ @knife.run
+
+ end
+
+ it "should not print the role" do
+ expect(@knife.ui).not_to receive(:output)
+ @knife.run
+ end
+
+ describe "with -p or --print-after" do
+ it "should pretty print the role, formatted for display" do
+ @knife.config[:print_after] = true
+ expect(@knife.ui).to receive(:output).with(@role)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_env_run_list_add_spec.rb b/knife/spec/unit/knife/role_env_run_list_add_spec.rb
new file mode 100644
index 0000000000..b42ec6141f
--- /dev/null
+++ b/knife/spec/unit/knife/role_env_run_list_add_spec.rb
@@ -0,0 +1,217 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEnvRunListAdd do
+ before(:each) do
+ # Chef::Config[:role_name] = "websimian"
+ # Chef::Config[:env_name] = "QA"
+ @knife = Chef::Knife::RoleEnvRunListAdd.new
+ @knife.config = {
+ after: nil,
+ }
+ @knife.name_args = [ "will", "QA", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:save).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should have an empty default run list" do
+ @knife.run
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should have a QA environment" do
+ @knife.run
+ expect(@role.active_run_list_for("QA")).to eq("QA")
+ end
+
+ it "should load the role named will" do
+ expect(Chef::Role).to receive(:load).with("will")
+ @knife.run
+ end
+
+ it "should be able to add an environment specific run list" do
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ end
+
+ it "should save the role" do
+ expect(@role).to receive(:save)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.run
+ end
+
+ describe "with -a or --after specified" do
+ it "should not create a change if the specified 'after' never comes" do
+ @role.run_list_for("_default") << "role[acorns]"
+ @role.run_list_for("_default") << "role[barn]"
+ @knife.config[:after] = "role[acorns]"
+ @knife.name_args = [ "will", "QA", "role[pad]" ]
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to be_nil
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[barn]")
+ expect(@role.run_list[2]).to be_nil
+ end
+
+ it "should add to the run list after the specified entries in the QA run list" do
+ # Setup
+ @role.run_list_for("_default") << "role[acorns]"
+ @role.run_list_for("_default") << "role[barn]"
+ @knife.run
+ @role.run_list_for("QA") << "role[pencil]"
+ @role.run_list_for("QA") << "role[pen]"
+ # Configuration we are testing
+ @knife.config[:after] = "role[pencil]"
+ @knife.name_args = [ "will", "QA", "role[pad]", "role[whackadoo]" ]
+ @knife.run
+ # The actual tests
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[pencil]")
+ expect(@role.run_list_for("QA")[2]).to eq("role[pad]")
+ expect(@role.run_list_for("QA")[3]).to eq("role[whackadoo]")
+ expect(@role.run_list_for("QA")[4]).to eq("role[pen]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[barn]")
+ expect(@role.run_list[2]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe" do
+ it "should add to the QA run list all the entries" do
+ @knife.name_args = [ "will", "QA", "role[monkey],role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe with space between items" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "QA", "role[monkey], role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments and list separated by comas" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
+ expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "Recipe with version number is allowed" do
+ it "should add to the run list all the entries including the versioned recipe" do
+ @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
+ expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly@1.1.3]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "with one role or recipe but with an extraneous comma" do
+ it "should add to the run list one item" do
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.name_args = [ "will", "QA", "role[monkey]," ]
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[1]).to be_nil
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ describe "with more than one command" do
+ it "should be able to the environment run list by running multiple knife commands" do
+ @knife.name_args = [ "will", "QA", "role[blue]," ]
+ @knife.run
+ @knife.name_args = [ "will", "QA", "role[black]," ]
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[blue]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[black]")
+ expect(@role.run_list[0]).to be_nil
+ end
+ end
+
+ describe "with more than one environment" do
+ it "should add to the run list a second environment in the specific run list" do
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.name_args = [ "will", "QA", "role[blue]," ]
+ @knife.run
+ @role.run_list_for("QA") << "role[walnuts]"
+
+ @knife.name_args = [ "will", "PRD", "role[ball]," ]
+ @knife.run
+ @role.run_list_for("PRD") << "role[pen]"
+
+ expect(@role.run_list_for("QA")[0]).to eq("role[blue]")
+ expect(@role.run_list_for("PRD")[0]).to eq("role[ball]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[walnuts]")
+ expect(@role.run_list_for("PRD")[1]).to eq("role[pen]")
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to be_nil
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/role_env_run_list_clear_spec.rb b/knife/spec/unit/knife/role_env_run_list_clear_spec.rb
new file mode 100644
index 0000000000..ad88d1ae37
--- /dev/null
+++ b/knife/spec/unit/knife/role_env_run_list_clear_spec.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEnvRunListClear do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ Chef::Config[:env_name] = "QA"
+ @setup = Chef::Knife::RoleEnvRunListAdd.new
+ @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ]
+
+ @knife = Chef::Knife::RoleEnvRunListClear.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = %w{will QA}
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to be_nil
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "should clear an environmental run list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = %w{will QA}
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to be_nil
+ expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
+ expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
+ expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
+ expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
+ expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_env_run_list_remove_spec.rb b/knife/spec/unit/knife/role_env_run_list_remove_spec.rb
new file mode 100644
index 0000000000..8755ce452b
--- /dev/null
+++ b/knife/spec/unit/knife/role_env_run_list_remove_spec.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEnvRunListRemove do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ Chef::Config[:env_name] = "QA"
+ @setup = Chef::Knife::RoleEnvRunListAdd.new
+ @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ]
+
+ @knife = Chef::Knife::RoleEnvRunListRemove.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "QA", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).not_to eq("role[monkey]")
+ expect(@role.run_list_for("QA")[0]).to eq("role[person]")
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "run with a list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "QA", "role[monkey]" ]
+ @knife.run
+ @knife.name_args = [ "will", "QA", "recipe[duck::type]" ]
+ @knife.run
+ expect(@role.run_list_for("QA")).not_to include("role[monkey]")
+ expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]")
+ expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[person]")
+ expect(@role.run_list_for("QA")[2]).to eq("role[bird]")
+ expect(@role.run_list_for("QA")[3]).to eq("role[town]")
+ expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
+ expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
+ expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
+ expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
+ expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_env_run_list_replace_spec.rb b/knife/spec/unit/knife/role_env_run_list_replace_spec.rb
new file mode 100644
index 0000000000..457f4efbd7
--- /dev/null
+++ b/knife/spec/unit/knife/role_env_run_list_replace_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEnvRunListReplace do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ Chef::Config[:env_name] = "QA"
+ @setup = Chef::Knife::RoleEnvRunListAdd.new
+ @setup.name_args = [ "will", "QA", "role[monkey]", "role[dude]", "role[fixer]" ]
+
+ @knife = Chef::Knife::RoleEnvRunListReplace.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "QA", "role[dude]", "role[person]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list_for("QA")[1]).not_to eq("role[dude]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[person]")
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "run with a list of roles and recipes" do
+ it "should replace the items from the run list" do
+ @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "QA", "role[monkey]", "role[gibbon]" ]
+ @knife.run
+ @knife.name_args = [ "will", "QA", "recipe[duck::type]", "recipe[duck::mallard]" ]
+ @knife.run
+ expect(@role.run_list_for("QA")).not_to include("role[monkey]")
+ expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]")
+ expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[gibbon]")
+ expect(@role.run_list_for("QA")[2]).to eq("recipe[duck::mallard]")
+ expect(@role.run_list_for("QA")[3]).to eq("role[person]")
+ expect(@role.run_list_for("QA")[4]).to eq("role[bird]")
+ expect(@role.run_list_for("QA")[5]).to eq("role[town]")
+ expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
+ expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
+ expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
+ expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
+ expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
+ expect(@role.run_list[0]).to be_nil
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_env_run_list_set_spec.rb b/knife/spec/unit/knife/role_env_run_list_set_spec.rb
new file mode 100644
index 0000000000..34233398f5
--- /dev/null
+++ b/knife/spec/unit/knife/role_env_run_list_set_spec.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleEnvRunListSet do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ Chef::Config[:env_name] = "QA"
+ @setup = Chef::Knife::RoleEnvRunListAdd.new
+ @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]", "role[bucket]" ]
+
+ @knife = Chef::Knife::RoleEnvRunListSet.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "QA", "role[owen]", "role[mauntel]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should replace all the items in the runlist with what is specified" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[owen]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[mauntel]")
+ expect(@role.run_list_for("QA")[2]).to be_nil
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "should clear an environmental run list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "QA", "role[coke]", "role[pepsi]" ]
+ @knife.run
+ expect(@role.run_list_for("QA")[0]).to eq("role[coke]")
+ expect(@role.run_list_for("QA")[1]).to eq("role[pepsi]")
+ expect(@role.run_list_for("QA")[2]).to be_nil
+ expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
+ expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
+ expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
+ expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
+ expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
+ expect(@role.run_list[0]).to be_nil
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_from_file_spec.rb b/knife/spec/unit/knife/role_from_file_spec.rb
new file mode 100644
index 0000000000..6e2fdf7cfb
--- /dev/null
+++ b/knife/spec/unit/knife/role_from_file_spec.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::RoleFromFile.load_deps
+
+describe Chef::Knife::RoleFromFile do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleFromFile.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "adam.rb" ]
+ allow(@knife).to receive(:output).and_return(true)
+ allow(@knife).to receive(:confirm).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:save)
+ allow(@knife.loader).to receive(:load_from).and_return(@role)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ it "should load from a file" do
+ expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role)
+ @knife.run
+ end
+
+ it "should not print the role" do
+ expect(@knife).not_to receive(:output)
+ @knife.run
+ end
+
+ describe "with -p or --print-after" do
+ it "should print the role" do
+ @knife.config[:print_after] = true
+ expect(@knife).to receive(:output)
+ @knife.run
+ end
+ end
+ end
+
+ describe "run with multiple arguments" do
+ it "should load each file" do
+ @knife.name_args = [ "adam.rb", "caleb.rb" ]
+ expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role)
+ expect(@knife.loader).to receive(:load_from).with("roles", "caleb.rb").and_return(@role)
+ @knife.run
+ end
+ end
+
+end
diff --git a/knife/spec/unit/knife/role_list_spec.rb b/knife/spec/unit/knife/role_list_spec.rb
new file mode 100644
index 0000000000..f37a85b6dc
--- /dev/null
+++ b/knife/spec/unit/knife/role_list_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleList do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::RoleList.new
+ allow(@knife).to receive(:output).and_return(true)
+ @list = {
+ "foo" => "http://example.com/foo",
+ "bar" => "http://example.com/foo",
+ }
+ allow(Chef::Role).to receive(:list).and_return(@list)
+ end
+
+ describe "run" do
+ it "should list the roles" do
+ expect(Chef::Role).to receive(:list).and_return(@list)
+ @knife.run
+ end
+
+ it "should pretty print the list" do
+ expect(Chef::Role).to receive(:list).and_return(@list)
+ expect(@knife).to receive(:output).with(%w{bar foo})
+ @knife.run
+ end
+
+ describe "with -w or --with-uri" do
+ it "should pretty print the hash" do
+ @knife.config[:with_uri] = true
+ expect(Chef::Role).to receive(:list).and_return(@list)
+ expect(@knife).to receive(:output).with(@list)
+ @knife.run
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_run_list_add_spec.rb b/knife/spec/unit/knife/role_run_list_add_spec.rb
new file mode 100644
index 0000000000..7b038c2e81
--- /dev/null
+++ b/knife/spec/unit/knife/role_run_list_add_spec.rb
@@ -0,0 +1,179 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleRunListAdd do
+ before(:each) do
+ # Chef::Config[:role_name] = "websimian"
+ # Chef::Config[:env_name] = "QA"
+ @knife = Chef::Knife::RoleRunListAdd.new
+ @knife.config = {
+ after: nil,
+ }
+ @knife.name_args = [ "will", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+ @role = Chef::Role.new
+ allow(@role).to receive(:save).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should have a run list with the monkey role" do
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[monkey]")
+ end
+
+ it "should load the role named will" do
+ expect(Chef::Role).to receive(:load).with("will")
+ @knife.run
+ end
+
+ it "should save the role" do
+ expect(@role).to receive(:save)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.run
+ end
+
+ describe "with -a or --after specified" do
+ it "should not create a change if the specified 'after' never comes" do
+ @role.run_list_for("_default") << "role[acorns]"
+ @role.run_list_for("_default") << "role[barn]"
+ @knife.config[:after] = "role[tree]"
+ @knife.name_args = [ "will", "role[pad]" ]
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[barn]")
+ expect(@role.run_list[2]).to be_nil
+ end
+
+ it "should add to the run list after the specified entries in the default run list" do
+ # Setup
+ @role.run_list_for("_default") << "role[acorns]"
+ @role.run_list_for("_default") << "role[barn]"
+ # Configuration we are testing
+ @knife.config[:after] = "role[acorns]"
+ @knife.name_args = [ "will", "role[pad]", "role[whackadoo]" ]
+ @knife.run
+ # The actual tests
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[pad]")
+ expect(@role.run_list[2]).to eq("role[whackadoo]")
+ expect(@role.run_list[3]).to eq("role[barn]")
+ expect(@role.run_list[4]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe" do
+ it "should add to the QA run list all the entries" do
+ @knife.name_args = [ "will", "role[monkey],role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to eq("role[duck]")
+ expect(@role.run_list[3]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe with space between items" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "role[monkey], role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to eq("role[duck]")
+ expect(@role.run_list[3]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "role[monkey]", "role[duck]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to eq("role[duck]")
+ expect(@role.run_list[3]).to be_nil
+ end
+ end
+
+ describe "with more than one role or recipe as different arguments and list separated by comas" do
+ it "should add to the run list all the entries" do
+ @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to eq("role[duck]")
+ expect(@role.run_list[3]).to eq("recipe[bird::fly]")
+ expect(@role.run_list[4]).to be_nil
+ end
+ end
+
+ describe "Recipe with version number is allowed" do
+ it "should add to the run list all the entries including the versioned recipe" do
+ @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ]
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to eq("role[duck]")
+ expect(@role.run_list[3]).to eq("recipe[bird::fly@1.1.3]")
+ expect(@role.run_list[4]).to be_nil
+ end
+ end
+
+ describe "with one role or recipe but with an extraneous comma" do
+ it "should add to the run list one item" do
+ @role.run_list_for("_default") << "role[acorns]"
+ @knife.name_args = [ "will", "role[monkey]," ]
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[acorns]")
+ expect(@role.run_list[1]).to eq("role[monkey]")
+ expect(@role.run_list[2]).to be_nil
+ end
+ end
+
+ describe "with more than one command" do
+ it "should be able to the environment run list by running multiple knife commands" do
+ @knife.name_args = [ "will", "role[blue]," ]
+ @knife.run
+ @knife.name_args = [ "will", "role[black]," ]
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[blue]")
+ expect(@role.run_list[1]).to eq("role[black]")
+ expect(@role.run_list[2]).to be_nil
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/role_run_list_clear_spec.rb b/knife/spec/unit/knife/role_run_list_clear_spec.rb
new file mode 100644
index 0000000000..5479b01811
--- /dev/null
+++ b/knife/spec/unit/knife/role_run_list_clear_spec.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleRunListClear do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ @setup = Chef::Knife::RoleRunListAdd.new
+ @setup.name_args = [ "will", "role[monkey]", "role[person]" ]
+
+ @knife = Chef::Knife::RoleRunListClear.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list[0]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "should clear an environmental run list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will" ]
+ @knife.run
+ expect(@role.run_list[0]).to be_nil
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_run_list_remove_spec.rb b/knife/spec/unit/knife/role_run_list_remove_spec.rb
new file mode 100644
index 0000000000..353ae36c1a
--- /dev/null
+++ b/knife/spec/unit/knife/role_run_list_remove_spec.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleRunListRemove do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ @setup = Chef::Knife::RoleRunListAdd.new
+ @setup.name_args = [ "will", "role[monkey]", "role[person]" ]
+
+ @knife = Chef::Knife::RoleRunListRemove.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "role[monkey]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[person]")
+ expect(@role.run_list[1]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "run with a list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "role[monkey]" ]
+ @knife.run
+ @knife.name_args = [ "will", "recipe[duck::type]" ]
+ @knife.run
+ expect(@role.run_list).not_to include("role[monkey]")
+ expect(@role.run_list).not_to include("recipe[duck::type]")
+ expect(@role.run_list[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list[1]).to eq("role[person]")
+ expect(@role.run_list[2]).to eq("role[bird]")
+ expect(@role.run_list[3]).to eq("role[town]")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_run_list_replace_spec.rb b/knife/spec/unit/knife/role_run_list_replace_spec.rb
new file mode 100644
index 0000000000..e59b704f00
--- /dev/null
+++ b/knife/spec/unit/knife/role_run_list_replace_spec.rb
@@ -0,0 +1,98 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleRunListReplace do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ @setup = Chef::Knife::RoleRunListAdd.new
+ @setup.name_args = [ "will", "role[monkey]", "role[dude]", "role[fixer]" ]
+
+ @knife = Chef::Knife::RoleRunListReplace.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "role[dude]", "role[person]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should remove the item from the run list" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[monkey]")
+ expect(@role.run_list[1]).not_to eq("role[dude]")
+ expect(@role.run_list[1]).to eq("role[person]")
+ expect(@role.run_list[2]).to eq("role[fixer]")
+ expect(@role.run_list[3]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "run with a list of roles and recipes" do
+ it "should replace the items from the run list" do
+ @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "role[monkey]", "role[gibbon]" ]
+ @knife.run
+ @knife.name_args = [ "will", "recipe[duck::type]", "recipe[duck::mallard]" ]
+ @knife.run
+ expect(@role.run_list).not_to include("role[monkey]")
+ expect(@role.run_list).not_to include("recipe[duck::type]")
+ expect(@role.run_list[0]).to eq("recipe[orange::chicken]")
+ expect(@role.run_list[1]).to eq("role[gibbon]")
+ expect(@role.run_list[2]).to eq("recipe[duck::mallard]")
+ expect(@role.run_list[3]).to eq("role[person]")
+ expect(@role.run_list[4]).to eq("role[bird]")
+ expect(@role.run_list[5]).to eq("role[town]")
+ expect(@role.run_list[6]).to be_nil
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_run_list_set_spec.rb b/knife/spec/unit/knife/role_run_list_set_spec.rb
new file mode 100644
index 0000000000..b75f1ab377
--- /dev/null
+++ b/knife/spec/unit/knife/role_run_list_set_spec.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Will Albenzi (<walbenzi@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleRunListSet do
+ before(:each) do
+ Chef::Config[:role_name] = "will"
+ @setup = Chef::Knife::RoleRunListAdd.new
+ @setup.name_args = [ "will", "role[monkey]", "role[person]", "role[bucket]" ]
+
+ @knife = Chef::Knife::RoleRunListSet.new
+ @knife.config = {
+ print_after: nil,
+ }
+ @knife.name_args = [ "will", "role[owen]", "role[mauntel]" ]
+ allow(@knife).to receive(:output).and_return(true)
+
+ @role = Chef::Role.new
+ @role.name("will")
+ allow(@role).to receive(:save).and_return(true)
+
+ allow(@knife.ui).to receive(:confirm).and_return(true)
+ allow(Chef::Role).to receive(:load).and_return(@role)
+
+ end
+
+ describe "run" do
+
+ # it "should display all the things" do
+ # @knife.run
+ # @role.to_json.should == 'show all the things'
+ # end
+
+ it "should load the node" do
+ expect(Chef::Role).to receive(:load).with("will").and_return(@role)
+ @knife.run
+ end
+
+ it "should replace all the items in the runlist with what is specified" do
+ @setup.run
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[owen]")
+ expect(@role.run_list[1]).to eq("role[mauntel]")
+ expect(@role.run_list[2]).to be_nil
+ end
+
+ it "should save the node" do
+ expect(@role).to receive(:save).and_return(true)
+ @knife.run
+ end
+
+ it "should print the run list" do
+ expect(@knife).to receive(:output).and_return(true)
+ @knife.config[:print_after] = true
+ @setup.run
+ @knife.run
+ end
+
+ describe "should clear an environmental run list of roles and recipes" do
+ it "should remove the items from the run list" do
+ @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
+ @setup.run
+ @knife.name_args = [ "will", "role[coke]", "role[pepsi]" ]
+ @knife.run
+ expect(@role.run_list[0]).to eq("role[coke]")
+ expect(@role.run_list[1]).to eq("role[pepsi]")
+ expect(@role.run_list[2]).to be_nil
+ expect(@role.run_list[3]).to be_nil
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/role_show_spec.rb b/knife/spec/unit/knife/role_show_spec.rb
new file mode 100644
index 0000000000..a79cb40e81
--- /dev/null
+++ b/knife/spec/unit/knife/role_show_spec.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright 2014-2016, Lamont Granquist
+# 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 "knife_spec_helper"
+
+describe Chef::Knife::RoleShow do
+ let(:role) { "base" }
+
+ let(:knife) do
+ knife = Chef::Knife::RoleShow.new
+ knife.name_args = [ role ]
+ knife
+ end
+
+ let(:role_mock) { double("role_mock") }
+
+ describe "run" do
+ it "should list the role" do
+ expect(Chef::Role).to receive(:load).with("base").and_return(role_mock)
+ expect(knife).to receive(:format_for_display).with(role_mock)
+ knife.run
+ end
+
+ it "should pretty print json" do
+ knife.config[:format] = "json"
+ stdout = StringIO.new
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ fake_role_contents = { "foo" => "bar", "baz" => "qux" }
+ expect(Chef::Role).to receive(:load).with("base").and_return(fake_role_contents)
+ knife.run
+ expect(stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n")
+ end
+
+ context "without a role name" do
+ let(:role) {}
+
+ it "should print usage and exit when a role name is not provided" do
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/ssh_spec.rb b/knife/spec/unit/knife/ssh_spec.rb
new file mode 100644
index 0000000000..59015f024a
--- /dev/null
+++ b/knife/spec/unit/knife/ssh_spec.rb
@@ -0,0 +1,403 @@
+#
+# Author:: Bryan McLellan <btm@chef.io>
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "net/ssh"
+require "net/ssh/multi"
+
+describe Chef::Knife::Ssh do
+ let(:query_result) { double("chef search results") }
+
+ before do
+ Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
+ @knife = Chef::Knife::Ssh.new
+ @knife.merge_configs
+ @node_foo = {}
+ @node_foo["fqdn"] = "foo.example.org"
+ @node_foo["ipaddress"] = "10.0.0.1"
+ @node_foo["cloud"] = {}
+
+ @node_bar = {}
+ @node_bar["fqdn"] = "bar.example.org"
+ @node_bar["ipaddress"] = "10.0.0.2"
+ @node_bar["cloud"] = {}
+
+ end
+
+ describe "#configure_session" do
+ context "manual is set to false (default)" do
+ before do
+ @knife.config[:manual] = false
+ allow(query_result).to receive(:search).with(any_args).and_yield(@node_foo).and_yield(@node_bar)
+ allow(Chef::Search::Query).to receive(:new).and_return(query_result)
+ end
+
+ def self.should_return_specified_attributes
+ it "returns an array of the attributes specified on the command line OR config file, if only one is set" do
+ @node_bar["target"] = "10.0.0.2"
+ @node_foo["target"] = "10.0.0.1"
+ @node_bar["prefix"] = "bar"
+ @node_foo["prefix"] = "foo"
+ @knife.config[:ssh_attribute] = "ipaddress"
+ @knife.config[:prefix_attribute] = "name"
+ Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file
+ Chef::Config[:knife][:prefix_attribute] = "name" # this value will be in the config file
+ expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]])
+ @knife.configure_session
+ end
+
+ it "returns an array of the attributes specified on the command line even when a config value is set" do
+ @node_bar["target"] = "10.0.0.2"
+ @node_foo["target"] = "10.0.0.1"
+ @node_bar["prefix"] = "bar"
+ @node_foo["prefix"] = "foo"
+ Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file
+ Chef::Config[:knife][:prefix_attribute] = "config_file" # this value will be in the config file
+ @knife.config[:ssh_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
+ @knife.config[:prefix_attribute] = "name" # this is the value of the command line via #configure_attribute
+ expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]])
+ @knife.configure_session
+ end
+ end
+
+ it "searches for and returns an array of fqdns" do
+ expect(@knife).to receive(:session_from_list).with([
+ ["foo.example.org", nil, nil],
+ ["bar.example.org", nil, nil],
+ ])
+ @knife.configure_session
+ end
+
+ should_return_specified_attributes
+
+ context "when cloud hostnames are available" do
+ before do
+ @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
+ @node_bar["cloud"]["public_hostname"] = "ec2-10-0-0-2.compute-1.amazonaws.com"
+ end
+ it "returns an array of cloud public hostnames" do
+ expect(@knife).to receive(:session_from_list).with([
+ ["ec2-10-0-0-1.compute-1.amazonaws.com", nil, nil],
+ ["ec2-10-0-0-2.compute-1.amazonaws.com", nil, nil],
+ ])
+ @knife.configure_session
+ end
+
+ should_return_specified_attributes
+ end
+
+ context "when cloud hostnames are available but empty" do
+ before do
+ @node_foo["cloud"]["public_hostname"] = ""
+ @node_bar["cloud"]["public_hostname"] = ""
+ end
+
+ it "returns an array of fqdns" do
+ expect(@knife).to receive(:session_from_list).with([
+ ["foo.example.org", nil, nil],
+ ["bar.example.org", nil, nil],
+ ])
+ @knife.configure_session
+ end
+
+ should_return_specified_attributes
+ end
+
+ it "should raise an error if no host are found" do
+ allow(query_result).to receive(:search).with(any_args)
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:exit).with(10)
+ @knife.configure_session
+ end
+
+ context "when there are some hosts found but they do not have an attribute to connect with" do
+ before do
+ @node_foo["fqdn"] = nil
+ @node_bar["fqdn"] = nil
+ end
+
+ it "should raise a specific error (CHEF-3402)" do
+ expect(@knife.ui).to receive(:fatal).with(/^2 nodes found/)
+ expect(@knife).to receive(:exit).with(10)
+ @knife.configure_session
+ end
+ end
+
+ context "when there are some hosts found but IPs duplicated if duplicated_fqdns option sets :fatal" do
+ before do
+ @knife.config[:duplicated_fqdns] = :fatal
+ @node_foo["fqdn"] = "foo.example.org"
+ @node_bar["fqdn"] = "foo.example.org"
+ end
+
+ it "should raise a specific error" do
+ expect(@knife.ui).to receive(:fatal).with(/^SSH node is duplicated: foo\.example\.org/)
+ expect(@knife).to receive(:exit).with(10)
+ expect(@knife).to receive(:session_from_list).with([
+ ["foo.example.org", nil, nil],
+ ["foo.example.org", nil, nil],
+ ])
+ @knife.configure_session
+ end
+ end
+ end
+
+ context "manual is set to true" do
+ before do
+ @knife.config[:manual] = true
+ end
+
+ it "returns an array of provided values" do
+ @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"])
+ expect(@knife).to receive(:session_from_list).with(["foo.example.org", "bar.example.org"])
+ @knife.configure_session
+ end
+ end
+ end
+
+ describe "#get_prefix_attribute" do
+ # Order of precedence for prefix
+ # 1) config value (cli or knife config)
+ # 2) nil
+ before do
+ Chef::Config[:knife][:prefix_attribute] = nil
+ @knife.config[:prefix_attribute] = nil
+ @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
+ @node_bar["cloud"]["public_hostname"] = ""
+ end
+
+ it "should return nil by default" do
+ expect(@knife.get_prefix_attribute({})).to eq(nil)
+ end
+
+ it "should favor config over nil" do
+ @node_foo["prefix"] = "config"
+ expect( @knife.get_prefix_attribute(@node_foo)).to eq("config")
+ end
+ end
+
+ describe "#get_ssh_attribute" do
+ # Order of precedence for ssh target
+ # 1) config value (cli or knife config)
+ # 2) cloud attribute
+ # 3) fqdn
+ before do
+ Chef::Config[:knife][:ssh_attribute] = nil
+ @knife.config[:ssh_attribute] = nil
+ @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
+ @node_bar["cloud"]["public_hostname"] = ""
+ end
+
+ it "should return fqdn by default" do
+ expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn")
+ end
+
+ it "should return cloud.public_hostname attribute if available" do
+ expect(@knife.get_ssh_attribute(@node_foo)).to eq("ec2-10-0-0-1.compute-1.amazonaws.com")
+ end
+
+ it "should favor config over cloud and default" do
+ @node_foo["target"] = "config"
+ expect( @knife.get_ssh_attribute(@node_foo)).to eq("config")
+ end
+
+ it "should return fqdn if cloud.hostname is empty" do
+ expect( @knife.get_ssh_attribute(@node_bar)).to eq("bar.example.org")
+ end
+ end
+
+ describe "#session_from_list" do
+ before :each do
+ @knife.instance_variable_set(:@longest, 0)
+ ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 }
+ allow(Net::SSH).to receive(:configuration_for).with("the.b.org", true).and_return(ssh_config)
+ end
+
+ it "uses the port from an ssh config file" do
+ @knife.session_from_list([["the.b.org", nil, nil]])
+ expect(@knife.session.servers[0].port).to eq(23)
+ end
+
+ it "uses the port from a cloud attr" do
+ @knife.session_from_list([["the.b.org", 123, nil]])
+ expect(@knife.session.servers[0].port).to eq(123)
+ end
+
+ it "uses the prefix from list" do
+ @knife.session_from_list([["the.b.org", nil, "b-team"]])
+ expect(@knife.session.servers[0][:prefix]).to eq("b-team")
+ end
+
+ it "defaults to a prefix of host" do
+ @knife.session_from_list([["the.b.org", nil, nil]])
+ expect(@knife.session.servers[0][:prefix]).to eq("the.b.org")
+ end
+
+ it "defaults to a timeout of 120 seconds" do
+ @knife.session_from_list([["the.b.org", nil, nil]])
+ expect(@knife.session.servers[0].options[:timeout]).to eq(120)
+ end
+
+ it "uses the timeout from the CLI" do
+ @knife.config = {}
+ Chef::Config[:knife][:ssh_timeout] = nil
+ @knife.config[:ssh_timeout] = 5
+ @knife.session_from_list([["the.b.org", nil, nil]])
+ @knife.merge_configs
+ expect(@knife.session.servers[0].options[:timeout]).to eq(5)
+ end
+
+ it "uses the timeout from knife config" do
+ @knife.config = {}
+ Chef::Config[:knife][:ssh_timeout] = 6
+ @knife.merge_configs
+ @knife.session_from_list([["the.b.org", nil, nil]])
+ expect(@knife.session.servers[0].options[:timeout]).to eq(6)
+ end
+
+ it "uses the user from an ssh config file" do
+ @knife.session_from_list([["the.b.org", 123, nil]])
+ expect(@knife.session.servers[0].user).to eq("locutus")
+ end
+
+ it "uses keepalive settings from an ssh config file" do
+ @knife.session_from_list([["the.b.org", 123, nil]])
+ expect(@knife.session.servers[0].options[:keepalive]).to be true
+ expect(@knife.session.servers[0].options[:keepalive_interval]).to eq 60
+ end
+ end
+
+ describe "#ssh_command" do
+ let(:execution_channel) { double(:execution_channel, on_data: nil, on_extended_data: nil) }
+ let(:session_channel) { double(:session_channel, request_pty: nil) }
+
+ let(:execution_channel2) { double(:execution_channel, on_data: nil, on_extended_data: nil) }
+ let(:session_channel2) { double(:session_channel, request_pty: nil) }
+
+ let(:session) { double(:session, loop: nil) }
+
+ let(:command) { "false" }
+
+ before do
+ expect(execution_channel)
+ .to receive(:on_request)
+ .and_yield(nil, double(:data_stream, read_long: exit_status))
+
+ expect(session_channel)
+ .to receive(:exec)
+ .with(command)
+ .and_yield(execution_channel, true)
+
+ expect(execution_channel2)
+ .to receive(:on_request)
+ .and_yield(nil, double(:data_stream, read_long: exit_status2))
+
+ expect(session_channel2)
+ .to receive(:exec)
+ .with(command)
+ .and_yield(execution_channel2, true)
+
+ expect(session)
+ .to receive(:open_channel)
+ .and_yield(session_channel)
+ .and_yield(session_channel2)
+ end
+
+ context "both connections return 0" do
+ let(:exit_status) { 0 }
+ let(:exit_status2) { 0 }
+
+ it "returns a 0 exit code" do
+ expect(@knife.ssh_command(command, session)).to eq(0)
+ end
+ end
+
+ context "the first connection returns 1 and the second returns 0" do
+ let(:exit_status) { 1 }
+ let(:exit_status2) { 0 }
+
+ it "returns a non-zero exit code" do
+ expect(@knife.ssh_command(command, session)).to eq(1)
+ end
+ end
+
+ context "the first connection returns 1 and the second returns 2" do
+ let(:exit_status) { 1 }
+ let(:exit_status2) { 2 }
+
+ it "returns a non-zero exit code" do
+ expect(@knife.ssh_command(command, session)).to eq(2)
+ end
+ end
+ end
+
+ describe "#tmux" do
+ before do
+ ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 }
+ allow(Net::SSH).to receive(:configuration_for).with("foo.example.org", true).and_return(ssh_config)
+ @query = Chef::Search::Query.new
+ expect(@query).to receive(:search).and_yield(@node_foo)
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
+ allow(@knife).to receive(:exec).and_return(0)
+ end
+
+ it "filters out invalid characters from tmux session name" do
+ @knife.name_args = ["name:foo.example.org", "tmux"]
+ expect(@knife).to receive(:shell_out!).with("tmux new-session -d -s 'knife ssh name=foo-example-org' -n 'foo.example.org' 'ssh locutus@foo.example.org' ")
+ @knife.run
+ end
+ end
+
+ describe "#run" do
+
+ it "should print usage and exit when a SEARCH QUERY is not provided" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with(/You must specify the SEARCH QUERY./)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ context "exit" do
+ before do
+ @query = Chef::Search::Query.new
+ expect(@query).to receive(:search).and_yield(@node_foo)
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
+ allow(@knife).to receive(:ssh_command).and_return(exit_code)
+ @knife.name_args = ["*:*", "false"]
+ end
+
+ context "with an error" do
+ let(:exit_code) { 1 }
+
+ it "should exit with a non-zero exit code" do
+ expect(@knife).to receive(:exit).with(exit_code)
+ @knife.run
+ end
+ end
+
+ context "with no error" do
+ let(:exit_code) { 0 }
+
+ it "should not exit" do
+ expect(@knife).not_to receive(:exit)
+ @knife.run
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/ssl_check_spec.rb b/knife/spec/unit/knife/ssl_check_spec.rb
new file mode 100644
index 0000000000..4412ee0be9
--- /dev/null
+++ b/knife/spec/unit/knife/ssl_check_spec.rb
@@ -0,0 +1,256 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "stringio"
+
+describe Chef::Knife::SslCheck do
+
+ let(:name_args) { [] }
+ let(:stdout_io) { StringIO.new }
+ let(:stderr_io) { StringIO.new }
+
+ def stderr
+ stderr_io.string
+ end
+
+ def stdout
+ stdout_io.string
+ end
+
+ subject(:ssl_check) do
+ s = Chef::Knife::SslCheck.new
+ allow(s.ui).to receive(:stdout).and_return(stdout_io)
+ allow(s.ui).to receive(:stderr).and_return(stderr_io)
+ s.name_args = name_args
+ s
+ end
+
+ before do
+ Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
+ end
+
+ context "when no arguments are given" do
+ it "uses the chef_server_url as the host to check" do
+ expect(ssl_check.host).to eq("example.com")
+ expect(ssl_check.port).to eq(8443)
+ end
+ end
+
+ context "when a specific URI is given" do
+ let(:name_args) { %w{https://example.test:10443/foo} }
+
+ it "checks the SSL configuration against the given host" do
+ expect(ssl_check.host).to eq("example.test")
+ expect(ssl_check.port).to eq(10443)
+ end
+ end
+
+ context "when an invalid URI is given" do
+
+ let(:name_args) { %w{foo.test} }
+
+ it "prints an error and exits" do
+ expect { ssl_check.run }.to raise_error(SystemExit)
+ expected_stdout = <<~E
+ USAGE: knife ssl check [URL] (options)
+ E
+ expected_stderr = <<~E
+ ERROR: Given URI: `foo.test' is invalid
+ E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+
+ context "and its malformed enough to make URI.parse barf" do
+
+ let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
+
+ it "prints an error and exits" do
+ expect { ssl_check.run }.to raise_error(SystemExit)
+ expected_stdout = <<~E
+ USAGE: knife ssl check [URL] (options)
+ E
+ expected_stderr = <<~E
+ ERROR: Given URI: `#{name_args[0]}' is invalid
+ E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+ end
+ end
+
+ describe "verifying trusted certificate X509 properties" do
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") }
+ let(:trusted_cert_file) { File.join(trusted_certs_dir, "example.crt") }
+
+ let(:store) { OpenSSL::X509::Store.new }
+ let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_cert_file)) }
+
+ before do
+ Chef::Config[:trusted_certs_dir] = trusted_certs_dir
+ allow(ssl_check).to receive(:trusted_certificates).and_return([trusted_cert_file])
+ allow(store).to receive(:add_cert).with(certificate)
+ allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
+ allow(OpenSSL::X509::Certificate).to receive(:new).with(IO.read(trusted_cert_file)).and_return(certificate)
+ allow(ssl_check).to receive(:verify_cert).and_return(true)
+ allow(ssl_check).to receive(:verify_cert_host).and_return(true)
+ end
+
+ context "when the trusted certificates directory is not glob escaped", :windows_only do
+ let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA.tr("/", "\\"), "trusted_certs") }
+
+ before do
+ allow(ssl_check).to receive(:trusted_certificates).and_call_original
+ allow(store).to receive(:verify).with(certificate).and_return(true)
+ end
+
+ it "escpaes the trusted certificates directory" do
+ expect(Dir).to receive(:glob)
+ .with("#{ChefConfig::PathHelper.escape_glob_dir(trusted_certs_dir)}/*.{crt,pem}")
+ .and_return([trusted_cert_file])
+ ssl_check.run
+ end
+ end
+
+ context "when the trusted certificates have valid X509 properties" do
+ before do
+ allow(store).to receive(:verify).with(certificate).and_return(true)
+ end
+
+ it "does not generate any X509 warnings" do
+ expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/)
+ ssl_check.run
+ end
+ end
+
+ context "when the trusted certificates have invalid X509 properties" do
+ before do
+ allow(store).to receive(:verify).with(certificate).and_return(false)
+ allow(store).to receive(:error_string).and_return("unable to get local issuer certificate")
+ end
+
+ it "generates a warning message with invalid certificate file names" do
+ expect(ssl_check.ui).to receive(:warn).with(/#{trusted_cert_file}: unable to get local issuer certificate/)
+ ssl_check.run
+ end
+ end
+ end
+
+ describe "verifying the remote certificate" do
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:tcp_socket) { double(TCPSocket) }
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
+
+ before do
+ expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket)
+ end
+
+ def run
+ ssl_check.run
+ rescue Exception
+ # puts "OUT: #{stdout_io.string}"
+ # puts "ERR: #{stderr_io.string}"
+ raise
+ end
+
+ context "when the remote host's certificate is valid" do
+
+ before do
+ expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn)
+ expect(ssl_socket).to receive(:connect) # no error
+ expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error
+ expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
+ end
+
+ it "prints a success message" do
+ ssl_check.run
+ expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'")
+ end
+ end
+
+ describe "and the certificate is not valid" do
+
+ let(:tcp_socket_for_debug) { double(TCPSocket) }
+ let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) }
+
+ let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
+ let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+ before do
+ @old_signal = trap(:INT, "DEFAULT")
+
+ expect(ssl_check).to receive(:proxified_socket)
+ .with("foo.example.com", 8443)
+ .and_return(tcp_socket_for_debug)
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new)
+ .with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context)
+ .and_return(ssl_socket_for_debug)
+ end
+
+ after do
+ trap(:INT, @old_signal)
+ end
+
+ context "when the certificate's CN does not match the hostname" do
+ before do
+ expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
+ expect(ssl_socket).to receive(:connect) # no error
+ expect(ssl_socket).to receive(:post_connection_check)
+ .with("foo.example.com")
+ .and_raise(OpenSSL::SSL::SSLError)
+ expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
+ expect(ssl_socket_for_debug).to receive(:connect)
+ expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
+ end
+
+ it "shows the CN used by the certificate and prints an error" do
+ expect { run }.to raise_error(SystemExit)
+ expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname")
+ expect(stderr).to include("You are attempting to connect to: 'foo.example.com'")
+ expect(stderr).to include("The server's certificate belongs to 'example.local'")
+ end
+
+ end
+
+ context "when the cert is not signed by any trusted authority" do
+ before do
+ expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
+ expect(ssl_socket).to receive(:connect)
+ .and_raise(OpenSSL::SSL::SSLError)
+ expect(ssl_socket).to receive(:hostname=)
+ .with("foo.example.com") # no error
+ expect(ssl_socket_for_debug).to receive(:connect)
+ expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
+ end
+
+ it "shows the CN used by the certificate and prints an error" do
+ expect { run }.to raise_error(SystemExit)
+ expect(stderr).to include("The SSL certificate of foo.example.com could not be verified")
+ end
+
+ end
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/ssl_fetch_spec.rb b/knife/spec/unit/knife/ssl_fetch_spec.rb
new file mode 100644
index 0000000000..c2dc5bdade
--- /dev/null
+++ b/knife/spec/unit/knife/ssl_fetch_spec.rb
@@ -0,0 +1,222 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/ssl_fetch"
+
+describe Chef::Knife::SslFetch do
+
+ let(:name_args) { [] }
+ let(:stdout_io) { StringIO.new }
+ let(:stderr_io) { StringIO.new }
+
+ def stderr
+ stderr_io.string
+ end
+
+ def stdout
+ stdout_io.string
+ end
+
+ subject(:ssl_fetch) do
+ s = Chef::Knife::SslFetch.new
+ s.name_args = name_args
+ allow(s.ui).to receive(:stdout).and_return(stdout_io)
+ allow(s.ui).to receive(:stderr).and_return(stderr_io)
+ s
+ end
+
+ context "when no arguments are given" do
+
+ before do
+ Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
+ end
+
+ it "uses the chef_server_url as the host to fetch" do
+ expect(ssl_fetch.host).to eq("example.com")
+ expect(ssl_fetch.port).to eq(8443)
+ end
+ end
+
+ context "when a specific URI is given" do
+ let(:name_args) { %w{https://example.test:10443/foo} }
+
+ it "fetches the SSL configuration against the given host" do
+ expect(ssl_fetch.host).to eq("example.test")
+ expect(ssl_fetch.port).to eq(10443)
+ end
+ end
+
+ context "when an invalid URI is given" do
+
+ let(:name_args) { %w{foo.test} }
+
+ it "prints an error and exits" do
+ expect { ssl_fetch.run }.to raise_error(SystemExit)
+ expected_stdout = <<~E
+ USAGE: knife ssl fetch [URL] (options)
+ E
+ expected_stderr = <<~E
+ ERROR: Given URI: `foo.test' is invalid
+ E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+
+ context "and its malformed enough to make URI.parse barf" do
+
+ let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
+
+ it "prints an error and exits" do
+ expect { ssl_fetch.run }.to raise_error(SystemExit)
+ expected_stdout = <<~E
+ USAGE: knife ssl fetch [URL] (options)
+ E
+ expected_stderr = <<~E
+ ERROR: Given URI: `#{name_args[0]}' is invalid
+ E
+ expect(stdout_io.string).to eq(expected_stdout)
+ expect(stderr_io.string).to eq(expected_stderr)
+ end
+ end
+ end
+
+ describe "normalizing CNs for use as paths" do
+
+ it "normalizes '*' to 'wildcard'" do
+ expect(ssl_fetch.normalize_cn("*.example.com")).to eq("wildcard_example_com")
+ end
+
+ it "normalizes non-alnum and hyphen characters to underscores" do
+ expect(ssl_fetch.normalize_cn("Billy-Bob's Super Awesome CA!")).to eq("Billy-Bob_s_Super_Awesome_CA_")
+ end
+
+ end
+
+ describe "#cn_of" do
+ let(:certificate) { double("Certificate", subject: subject) }
+
+ describe "when the certificate has a common name" do
+ let(:subject) { [["CN", "common name"]] }
+ it "returns the common name" do
+ expect(ssl_fetch.cn_of(certificate)).to eq("common name")
+ end
+ end
+
+ describe "when the certificate does not have a common name" do
+ let(:subject) { [] }
+ it "returns nil" do
+ expect(ssl_fetch.cn_of(certificate)).to eq(nil)
+ end
+ end
+ end
+
+ describe "fetching the remote cert chain" do
+
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:tcp_socket) { double(TCPSocket) }
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
+
+ let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
+ let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+ let(:trusted_certs_dir) { Dir.mktmpdir }
+
+ def run
+ ssl_fetch.run
+ rescue Exception
+ puts "OUT: #{stdout_io.string}"
+ puts "ERR: #{stderr_io.string}"
+ raise
+ end
+
+ before do
+ Chef::Config.trusted_certs_dir = trusted_certs_dir
+ end
+
+ after do
+ FileUtils.rm_rf(trusted_certs_dir)
+ end
+
+ context "when the TLS connection is successful" do
+
+ before do
+ expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
+ expect(ssl_socket).to receive(:connect)
+ expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt])
+ end
+
+ it "fetches the cert chain and writes the certs to the trusted_certs_dir" do
+ run
+ stored_cert_path = File.join(trusted_certs_dir, "example_local.crt")
+ expect(File).to exist(stored_cert_path)
+ expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path))
+ end
+
+ end
+
+ context "when connecting to a non-SSL service (like HTTP)" do
+
+ let(:name_args) { %w{http://foo.example.com} }
+
+ let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") }
+
+ before do
+ expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket)
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
+ expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error)
+
+ expect(ssl_fetch).to receive(:exit).with(1)
+ end
+
+ it "tells the user their URL is for a non-ssl service" do
+ expected_error_text = <<~ERROR_TEXT
+ ERROR: The service at the given URI (http://foo.example.com) does not accept SSL connections
+ ERROR: Perhaps you meant to connect to 'https://foo.example.com'?
+ ERROR_TEXT
+
+ run
+ expect(stderr).to include(expected_error_text)
+ end
+
+ end
+
+ describe "when the certificate does not have a CN" do
+ let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example_no_cn.crt") }
+ let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+ before do
+ expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
+ expect(ssl_socket).to receive(:connect)
+ expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt])
+ expect(Time).to receive(:new).and_return(1)
+ end
+
+ it "fetches the certificate and writes it to a file in the trusted_certs_dir" do
+ run
+ stored_cert_path = File.join(trusted_certs_dir, "foo.example.com_1.crt")
+ expect(File).to exist(stored_cert_path)
+ expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path))
+ end
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/status_spec.rb b/knife/spec/unit/knife/status_spec.rb
new file mode 100644
index 0000000000..f3b31c1897
--- /dev/null
+++ b/knife/spec/unit/knife/status_spec.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Sahil Muthoo (<sahil.muthoo@gmail.com>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::Status do
+ before(:each) do
+ node = Chef::Node.new.tap do |n|
+ n.automatic_attrs["fqdn"] = "foobar"
+ n.automatic_attrs["ohai_time"] = 1343845969
+ n.automatic_attrs["platform"] = "mac_os_x"
+ n.automatic_attrs["platform_version"] = "10.12.5"
+ end
+ allow(Time).to receive(:now).and_return(Time.at(1428573420))
+ @query = double("Chef::Search::Query")
+ allow(@query).to receive(:search).and_yield(node)
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
+ @knife = Chef::Knife::Status.new
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+ let(:opts) do
+ { filter_result:
+ { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
+ cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"],
+ platform_version: ["platform_version"], chef_environment: ["chef_environment"] } }
+ end
+
+ it "should default to searching for everything" do
+ expect(@query).to receive(:search).with(:node, "*:*", opts)
+ @knife.run
+ end
+
+ it "should filter by nodes older than some mins" do
+ @knife.config[:hide_by_mins] = 59
+ expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should filter by environment" do
+ @knife.config[:environment] = "production"
+ expect(@query).to receive(:search).with(:node, "chef_environment:production", opts)
+ @knife.run
+ end
+
+ it "should filter by environment and nodes older than some mins" do
+ @knife.config[:environment] = "production"
+ @knife.config[:hide_by_mins] = 59
+ expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should not use partial search with long output" do
+ @knife.config[:long_output] = true
+ expect(@query).to receive(:search).with(:node, "*:*", {})
+ @knife.run
+ end
+
+ context "with a custom query" do
+ before :each do
+ @knife.instance_variable_set(:@name_args, ["name:my_custom_name"])
+ end
+
+ it "should allow a custom query to be specified" do
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name", opts)
+ @knife.run
+ end
+
+ it "should filter by nodes older than some mins with nodename specified" do
+ @knife.config[:hide_by_mins] = 59
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should filter by environment with nodename specified" do
+ @knife.config[:environment] = "production"
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts)
+ @knife.run
+ end
+
+ it "should filter by environment and nodes older than some mins with nodename specified" do
+ @knife.config[:environment] = "production"
+ @knife.config[:hide_by_mins] = 59
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
+ @knife.run
+ end
+ end
+
+ it "should not colorize output unless it's writing to a tty" do
+ @knife.run
+ expect(@stdout.string.match(/foobar/)).not_to be_nil
+ expect(@stdout.string.match(/\e.*ago/)).to be_nil
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/supermarket_download_spec.rb b/knife/spec/unit/knife/supermarket_download_spec.rb
new file mode 100644
index 0000000000..3796140d61
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_download_spec.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
+# Copyright:: Copyright 2012-2016, Thomas Bishop
+# Copyright:: Copyright (c) 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 "chef/knife/supermarket_download"
+require "knife_spec_helper"
+
+describe Chef::Knife::SupermarketDownload do
+
+ describe "run" do
+ before do
+ @knife = Chef::Knife::SupermarketDownload.new
+ @knife.name_args = ["apache2"]
+ @noauth_rest = double("no auth rest")
+ @stderr = StringIO.new
+ @cookbook_api_url = "https://supermarket.chef.io/api/v1/cookbooks"
+ @version = "1.0.2"
+ @version_us = @version.tr ".", "_"
+ @current_data = { "deprecated" => false,
+ "latest_version" => "#{@cookbook_api_url}/apache2/versions/#{@version_us}",
+ "replacement" => "other_apache2" }
+
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest)
+ expect(@noauth_rest).to receive(:get)
+ .with("#{@cookbook_api_url}/apache2")
+ .and_return(@current_data)
+ @knife.configure_chef
+ end
+
+ context "when the cookbook is deprecated and not forced" do
+ before do
+ @current_data["deprecated"] = true
+ end
+
+ it "should warn with info about the replacement" do
+ expect(@knife.ui).to receive(:warn)
+ .with(/.+deprecated.+replaced by other_apache2.+/i)
+ expect(@knife.ui).to receive(:warn)
+ .with(/use --force.+download.+/i)
+ @knife.run
+ end
+ end
+
+ context "when" do
+ before do
+ @cookbook_data = { "version" => @version,
+ "file" => "http://example.com/apache2_#{@version_us}.tgz" }
+ @temp_file = double( path: "/tmp/apache2_#{@version_us}.tgz" )
+ @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
+ end
+
+ context "downloading the latest version" do
+ before do
+ expect(@noauth_rest).to receive(:get)
+ .with(@current_data["latest_version"])
+ .and_return(@cookbook_data)
+ expect(@noauth_rest).to receive(:streaming_request)
+ .with(@cookbook_data["file"])
+ .and_return(@temp_file)
+ end
+
+ context "and it is deprecated and with --force" do
+ before do
+ @current_data["deprecated"] = true
+ @knife.config[:force] = true
+ end
+
+ it "should download the latest version" do
+ expect(@knife.ui).to receive(:warn)
+ .with(/.+deprecated.+replaced by other_apache2.+/i)
+ expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
+ @knife.run
+ expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
+ expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
+ end
+
+ end
+
+ it "should download the latest version" do
+ expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
+ @knife.run
+ expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
+ expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
+ end
+
+ context "with -f or --file" do
+ before do
+ @file = "/opt/chef/cookbooks/apache2.tar.gz"
+ @knife.config[:file] = @file
+ expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
+ end
+
+ it "should download the cookbook to the desired file" do
+ @knife.run
+ expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
+ expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
+ end
+ end
+
+ it "should provide an accessor to the version" do
+ allow(FileUtils).to receive(:cp).and_return(true)
+ expect(@knife.version).to eq(@version)
+ @knife.run
+ end
+ end
+
+ context "downloading a cookbook of a specific version" do
+ before do
+ @version = "1.0.1"
+ @version_us = @version.tr ".", "_"
+ @cookbook_data = { "version" => @version,
+ "file" => "http://example.com/apache2_#{@version_us}.tgz" }
+ @temp_file = double(path: "/tmp/apache2_#{@version_us}.tgz")
+ @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
+ @knife.name_args << @version
+ end
+
+ it "should download the desired version" do
+ expect(@noauth_rest).to receive(:get)
+ .with("#{@cookbook_api_url}/apache2/versions/#{@version_us}")
+ .and_return(@cookbook_data)
+ expect(@noauth_rest).to receive(:streaming_request)
+ .with(@cookbook_data["file"])
+ .and_return(@temp_file)
+ expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
+ @knife.run
+ expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
+ expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
+ end
+ end
+
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/supermarket_install_spec.rb b/knife/spec/unit/knife/supermarket_install_spec.rb
new file mode 100644
index 0000000000..6ebbbc005c
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_install_spec.rb
@@ -0,0 +1,203 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/supermarket_install"
+require "mixlib/archive"
+
+describe Chef::Knife::SupermarketInstall do
+ let(:knife) { Chef::Knife::SupermarketInstall.new }
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:downloader) { {} }
+ let(:archive) { double(Mixlib::Archive, extract: true) }
+ let(:repo) do
+ double(sanity_check: true, reset_to_default_state: true,
+ prepare_to_import: true, finalize_updates_to: true,
+ merge_updates_from: true)
+ end
+ let(:install_path) do
+ if ChefUtils.windows?
+ "C:/tmp/chef"
+ else
+ "/var/tmp/chef"
+ end
+ end
+
+ before(:each) do
+ require "chef/knife/core/cookbook_scm_repo"
+
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ knife.config = {}
+ knife.config[:cookbook_path] = [ install_path ]
+
+ allow(knife).to receive(:stderr).and_return(stderr)
+ allow(knife).to receive(:stdout).and_return(stdout)
+
+ # Assume all external commands would have succeed. :(
+ allow(File).to receive(:unlink)
+ allow(File).to receive(:rmtree)
+ allow(knife).to receive(:shell_out!).and_return(true)
+ allow(Mixlib::Archive).to receive(:new).and_return(archive)
+
+ # SupermarketDownload Setup
+ allow(knife).to receive(:download_cookbook_to).and_return(downloader)
+ allow(downloader).to receive(:version) do
+ if knife.name_args.size == 2
+ knife.name_args[1]
+ else
+ "0.3.0"
+ end
+ end
+
+ # Stubs for CookbookSCMRepo
+ allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo)
+ end
+
+ describe "run" do
+ it "raises an error if a cookbook name is not provided" do
+ knife.name_args = []
+ expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.")
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "raises an error if more than two arguments are given" do
+ knife.name_args = %w{foo bar baz}
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "raises an error if the second argument is not a version" do
+ knife.name_args = %w{getting-started 1pass}
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "raises an error if the second argument is a four-digit version" do
+ knife.name_args = ["getting-started", "0.0.0.1"]
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "raises an error if the second argument is a one-digit version" do
+ knife.name_args = %w{getting-started 1}
+ expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "installs the specified version if second argument is a three-digit version" do
+ knife.name_args = ["getting-started", "0.1.0"]
+ knife.config[:no_deps] = true
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0")
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0")
+ knife.run
+ end
+
+ it "installs the specified version if second argument is a two-digit version" do
+ knife.name_args = ["getting-started", "0.1"]
+ knife.config[:no_deps] = true
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1")
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1")
+ knife.run
+ end
+
+ it "installs the latest version if only a cookbook name is given" do
+ knife.name_args = ["getting-started"]
+ knife.config[:no_deps] = true
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
+ knife.run
+ end
+
+ it "does not create/reset git branches if use_current_branch is set" do
+ knife.name_args = ["getting-started"]
+ knife.config[:use_current_branch] = true
+ knife.config[:no_deps] = true
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
+ expect(repo).not_to receive(:prepare_to_import)
+ expect(repo).not_to receive(:reset_to_default_state)
+ knife.run
+ end
+
+ it "does not raise an error if cookbook_path is a string" do
+ knife.config[:cookbook_path] = install_path
+ knife.config[:no_deps] = true
+ knife.name_args = ["getting-started"]
+ upstream_file = File.join(install_path, "getting-started.tar.gz")
+ expect(knife).to receive(:download_cookbook_to).with(upstream_file)
+ expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
+ expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
+ expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
+ expect { knife.run }.not_to raise_error
+ end
+ end # end of run
+
+ let(:metadata) { Chef::Cookbook::Metadata.new }
+ let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") }
+ let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") }
+
+ describe "preferred_metadata" do
+ before do
+ allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata)
+ allow(File).to receive(:exist?).and_return(false)
+ knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen")
+ knife.instance_variable_set(:@install_path, install_path)
+ end
+
+ it "returns a populated Metadata object if metadata.rb exists" do
+ allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
+ expect(metadata).to receive(:from_file).with(rb_metadata_path)
+ knife.preferred_metadata
+ end
+
+ it "returns a populated Metadata object if metadata.json exists" do
+ allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
+ # expect(IO).to receive(:read).with(json_metadata_path)
+ allow(IO).to receive(:read)
+ expect(metadata).to receive(:from_json)
+ knife.preferred_metadata
+ end
+
+ it "prefers metadata.rb over metadata.json" do
+ allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
+ allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
+ allow(IO).to receive(:read)
+ expect(metadata).to receive(:from_file).with(rb_metadata_path)
+ expect(metadata).not_to receive(:from_json)
+ knife.preferred_metadata
+ end
+
+ it "rasies an error if it finds no metadata file" do
+ expect { knife.preferred_metadata }.to raise_error { |error|
+ expect(error).to be_a(Chef::Exceptions::MetadataNotFound)
+ expect(error.cookbook_name).to eq("post-punk-kitchen")
+ expect(error.install_path).to eq(install_path)
+ }
+ end
+
+ end
+end
diff --git a/knife/spec/unit/knife/supermarket_list_spec.rb b/knife/spec/unit/knife/supermarket_list_spec.rb
new file mode 100644
index 0000000000..bbaf733f44
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_list_spec.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>)
+# Copyright:: Copyright (c) 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 "chef/knife/supermarket_list"
+require "knife_spec_helper"
+
+describe Chef::Knife::SupermarketList do
+ let(:knife) { described_class.new }
+ let(:noauth_rest) { double("no auth rest") }
+ let(:stdout) { StringIO.new }
+ let(:cookbooks_data) {
+ [
+ { "cookbook_name" => "1password", "cookbook_maintainer" => "jtimberman", "cookbook_description" => "Installs 1password", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/1password" },
+ { "cookbook_name" => "301", "cookbook_maintainer" => "markhuge", "cookbook_description" => "Installs/Configures 301", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/301" },
+ { "cookbook_name" => "3cx", "cookbook_maintainer" => "obay", "cookbook_description" => "Installs/Configures 3cx", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/3cx" },
+ { "cookbook_name" => "7dtd", "cookbook_maintainer" => "gregf", "cookbook_description" => "Installs/Configures the 7 Days To Die server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7dtd" },
+ { "cookbook_name" => "7-zip", "cookbook_maintainer" => "sneal", "cookbook_description" => "Installs/Configures the 7-zip file archiver", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7-zip" },
+ ]
+ }
+
+ let(:response_text) {
+ {
+ "start" => 0,
+ "total" => 5,
+ "items" => cookbooks_data,
+ }
+ }
+
+ describe "run" do
+ before do
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife).to receive(:noauth_rest).and_return(noauth_rest)
+ expect(noauth_rest).to receive(:get).and_return(response_text)
+ knife.configure_chef
+ end
+
+ it "should display all supermarket cookbooks" do
+ knife.run
+ cookbooks_data.each do |item|
+ expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
+ end
+ end
+
+ describe "with -w or --with-uri" do
+ it "should display the cookbook uris" do
+ knife.config[:with_uri] = true
+ knife.run
+ cookbooks_data.each do |item|
+ expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
+ expect(stdout.string).to match(/#{item["cookbook"]}\s/)
+ end
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/supermarket_search_spec.rb b/knife/spec/unit/knife/supermarket_search_spec.rb
new file mode 100644
index 0000000000..18092f52c8
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_search_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>)
+# Copyright:: Copyright (c) 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 "chef/knife/supermarket_search"
+require "knife_spec_helper"
+
+describe Chef::Knife::SupermarketSearch do
+ let(:knife) { described_class.new }
+ let(:noauth_rest) { double("no auth rest") }
+ let(:stdout) { StringIO.new }
+ let(:cookbooks_data) {
+ [
+ { "cookbook_name" => "mysql", "cookbook_maintainer" => "sous-chefs", "cookbook_description" => "Provides mysql_service, mysql_config, and mysql_client resources", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql" },
+ { "cookbook_name" => "mw_mysql", "cookbook_maintainer" => "car", "cookbook_description" => "Installs/Configures mw_mysql", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mw_mysql" },
+ { "cookbook_name" => "L7-mysql", "cookbook_maintainer" => "szelcsanyi", "cookbook_description" => "Installs/Configures MySQL server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/l7-mysql" },
+ { "cookbook_name" => "mysql-sys", "cookbook_maintainer" => "ovaistariq", "cookbook_description" => "Installs the mysql-sys tool. Description of the tool is available here https://github.com/MarkLeith/mysql-sys", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql-sys" },
+ { "cookbook_name" => "cg_mysql", "cookbook_maintainer" => "phai", "cookbook_description" => "Installs/Configures mysql with master and slave", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/cg_mysql" },
+ ]
+ }
+
+ let(:response_text) {
+ {
+ "start" => 0,
+ "total" => 5,
+ "items" => cookbooks_data,
+ }
+ }
+
+ let(:empty_response_text) {
+ {
+ "start" => 0,
+ "total" => 0,
+ "items" => [],
+ }
+ }
+
+ describe "run" do
+ before do
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife).to receive(:noauth_rest).and_return(noauth_rest)
+ knife.configure_chef
+ end
+
+ context "when name_args is present" do
+ before do
+ expect(noauth_rest).to receive(:get).and_return(response_text)
+ end
+
+ it "should display cookbooks with given name value" do
+ knife.name_args = ["mysql"]
+ knife.run
+ cookbooks_data.each do |item|
+ expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
+ end
+ end
+ end
+
+ context "when name_args is empty string" do
+ before do
+ expect(noauth_rest).to receive(:get).and_return(empty_response_text)
+ end
+
+ it "display nothing with name arg empty string" do
+ knife.name_args = [""]
+ knife.run
+ expect(stdout.string).to eq("\n")
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/supermarket_share_spec.rb b/knife/spec/unit/knife/supermarket_share_spec.rb
new file mode 100644
index 0000000000..d362edf3c0
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_share_spec.rb
@@ -0,0 +1,208 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/supermarket_share"
+require "chef/cookbook_uploader"
+require "chef/knife/core/cookbook_site_streaming_uploader"
+
+describe Chef::Knife::SupermarketShare do
+
+ before(:each) do
+ @knife = Chef::Knife::SupermarketShare.new
+ # Merge default settings in.
+ @knife.merge_configs
+ @knife.name_args = %w{cookbook_name AwesomeSausage}
+
+ @cookbook = Chef::CookbookVersion.new("cookbook_name")
+
+ @cookbook_loader = double("Chef::CookbookLoader")
+ allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(true)
+ allow(@cookbook_loader).to receive(:[]).and_return(@cookbook)
+ allow(Chef::CookbookLoader).to receive(:new).and_return(@cookbook_loader)
+
+ @noauth_rest = double(Chef::ServerAPI)
+ allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest)
+
+ @cookbook_uploader = Chef::CookbookUploader.new("herpderp", rest: "norest")
+ allow(Chef::CookbookUploader).to receive(:new).and_return(@cookbook_uploader)
+ allow(@cookbook_uploader).to receive(:validate_cookbooks).and_return(true)
+ allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return(Dir.mktmpdir)
+
+ allow(@knife).to receive(:shell_out!).and_return(true)
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+
+ before(:each) do
+ allow(@knife).to receive(:do_upload).and_return(true)
+ @category_response = {
+ "name" => "cookbook_name",
+ "category" => "Testing Category",
+ }
+ @bad_category_response = {
+ "error_code" => "NOT_FOUND",
+ "error_messages" => [
+ "Resource does not exist.",
+ ],
+ }
+ end
+
+ it "should set true to config[:dry_run] as default" do
+ expect(@knife.config[:dry_run]).to be_falsey
+ end
+
+ it "should should print usage and exit when given no arguments" do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should not fail when given only 1 argument and can determine category" do
+ @knife.name_args = ["cookbook_name"]
+ expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response)
+ expect(@knife).to receive(:do_upload)
+ @knife.run
+ end
+
+ it "should use a default category when given only 1 argument and cannot determine category" do
+ @knife.name_args = ["cookbook_name"]
+ expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Net::HTTPClientException.new("404 Not Found", OpenStruct.new(code: "404")) }
+ expect(@knife).to receive(:do_upload)
+ expect { @knife.run }.to_not raise_error
+ end
+
+ it "should print error and exit when given only 1 argument and Chef::ServerAPI throws an exception" do
+ @knife.name_args = ["cookbook_name"]
+ expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" }
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should check if the cookbook exists" do
+ expect(@cookbook_loader).to receive(:cookbook_exists?)
+ @knife.run
+ end
+
+ it "should exit and log to error if the cookbook doesn't exist" do
+ allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(false)
+ expect(@knife.ui).to receive(:error)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ if File.exist?("/usr/bin/gnutar") || File.exist?("/bin/gnutar")
+ it "should use gnutar to make a tarball of the cookbook" do
+ expect(@knife).to receive(:shell_out!) do |args|
+ expect(args.to_s).to match(/gnutar -czf/)
+ end
+ @knife.run
+ end
+ else
+ it "should make a tarball of the cookbook" do
+ expect(@knife).to receive(:shell_out!) do |args|
+ expect(args.to_s).to match(/tar -czf/)
+ end
+ @knife.run
+ end
+ end
+
+ it "should exit and log to error when the tarball creation fails" do
+ allow(@knife).to receive(:shell_out!).and_raise(Chef::Exceptions::Exec)
+ expect(@knife.ui).to receive(:error)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should upload the cookbook and clean up the tarball" do
+ expect(@knife).to receive(:do_upload)
+ expect(FileUtils).to receive(:rm_rf)
+ @knife.run
+ end
+
+ context "when the --dry-run flag is specified" do
+ before do
+ allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return("/var/tmp/dummy")
+ @knife.config = { dry_run: true }
+ @so = instance_double("Mixlib::ShellOut")
+ allow(@knife).to receive(:shell_out!).and_return(@so)
+ allow(@so).to receive(:stdout).and_return("file")
+ end
+
+ it "should list files in the tarball" do
+ allow(@knife).to receive(:tar_cmd).and_return("footar")
+ expect(@knife).to receive(:shell_out!).with("footar -czf #{@cookbook.name}.tgz #{@cookbook.name}", { cwd: "/var/tmp/dummy" })
+ expect(@knife).to receive(:shell_out!).with("footar -tzf #{@cookbook.name}.tgz", { cwd: "/var/tmp/dummy" })
+ @knife.run
+ end
+
+ it "does not upload the cookbook" do
+ expect(@knife).not_to receive(:do_upload)
+ @knife.run
+ end
+ end
+ end
+
+ describe "do_upload" do
+
+ before(:each) do
+ @upload_response = double("Net::HTTPResponse")
+ allow(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:post).and_return(@upload_response)
+
+ allow(File).to receive(:open).and_return(true)
+ end
+
+ it 'should post the cookbook to "https://supermarket.chef.io"' do
+ response_text = Chef::JSONCompat.to_json({ uri: "https://supermarket.chef.io/cookbooks/cookbook_name" })
+ allow(@upload_response).to receive(:body).and_return(response_text)
+ allow(@upload_response).to receive(:code).and_return(201)
+ expect(Chef::Knife::Core::CookbookSiteStreamingUploader).to receive(:post).with(/supermarket\.chef\.io/, anything, anything, anything)
+ @knife.run
+ end
+
+ it "should alert the user when a version already exists" do
+ response_text = Chef::JSONCompat.to_json({ error_messages: ["Version already exists"] })
+ allow(@upload_response).to receive(:body).and_return(response_text)
+ allow(@upload_response).to receive(:code).and_return(409)
+ expect { @knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to match(/ERROR(.+)cookbook already exists/)
+ end
+
+ it "should pass any errors on to the user" do
+ response_text = Chef::JSONCompat.to_json({ error_messages: ["You're holding it wrong"] })
+ allow(@upload_response).to receive(:body).and_return(response_text)
+ allow(@upload_response).to receive(:code).and_return(403)
+ expect { @knife.run }.to raise_error(SystemExit)
+ expect(@stderr.string).to match("ERROR(.*)You're holding it wrong")
+ end
+
+ it "should print the body if no errors are exposed on failure" do
+ response_text = Chef::JSONCompat.to_json({ system_error: "Your call was dropped", reason: "There's a map for that" })
+ allow(@upload_response).to receive(:body).and_return(response_text)
+ allow(@upload_response).to receive(:code).and_return(500)
+ expect(@knife.ui).to receive(:error).with(/#{Regexp.escape(response_text)}/) # .ordered
+ expect(@knife.ui).to receive(:error).with(/Unknown error/) # .ordered
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/supermarket_unshare_spec.rb b/knife/spec/unit/knife/supermarket_unshare_spec.rb
new file mode 100644
index 0000000000..63682a663c
--- /dev/null
+++ b/knife/spec/unit/knife/supermarket_unshare_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Stephen Delano (<stephen@chef.io>)
+# Author:: Tim Hinderliter (<tim@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/knife/supermarket_unshare"
+
+describe Chef::Knife::SupermarketUnshare do
+
+ before(:each) do
+ @knife = Chef::Knife::SupermarketUnshare.new
+ @knife.name_args = ["cookbook_name"]
+ allow(@knife).to receive(:confirm).and_return(true)
+
+ @rest = double("Chef::ServerAPI")
+ allow(@rest).to receive(:delete).and_return(true)
+ allow(@knife).to receive(:rest).and_return(@rest)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ describe "run" do
+
+ describe "with no cookbook argument" do
+ it "should print the usage and exit" do
+ @knife.name_args = []
+ expect(@knife.ui).to receive(:fatal)
+ expect(@knife).to receive(:show_usage)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ it "should confirm you want to unshare the cookbook" do
+ expect(@knife).to receive(:confirm)
+ @knife.run
+ end
+
+ it "should send a delete request to the cookbook site" do
+ expect(@rest).to receive(:delete)
+ @knife.run
+ end
+
+ it "should log an error and exit when forbidden" do
+ exception = double('403 "Forbidden"', code: "403")
+ allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('403 "Forbidden"', exception))
+ expect(@knife.ui).to receive(:error)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it "should re-raise any non-forbidden errors on delete" do
+ exception = double('500 "Application Error"', code: "500")
+ allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('500 "Application Error"', exception))
+ expect { @knife.run }.to raise_error(Net::HTTPClientException)
+ end
+
+ it "should log a success message" do
+ expect(@knife.ui).to receive(:info)
+ @knife.run
+ end
+
+ end
+
+end
diff --git a/knife/spec/unit/knife/tag_create_spec.rb b/knife/spec/unit/knife/tag_create_spec.rb
new file mode 100644
index 0000000000..290b925f4e
--- /dev/null
+++ b/knife/spec/unit/knife/tag_create_spec.rb
@@ -0,0 +1,23 @@
+require "knife_spec_helper"
+
+describe Chef::Knife::TagCreate do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::TagCreate.new
+ @knife.name_args = [ Chef::Config[:node_name], "happytag" ]
+
+ @node = Chef::Node.new
+ allow(@node).to receive :save
+ allow(Chef::Node).to receive(:load).and_return @node
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+ it "can create tags on a node" do
+ @knife.run
+ expect(@node.tags).to eq(["happytag"])
+ expect(@stderr.string).to match(/created tags happytag.+node webmonkey.example.com/i)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/tag_delete_spec.rb b/knife/spec/unit/knife/tag_delete_spec.rb
new file mode 100644
index 0000000000..dd01fba50f
--- /dev/null
+++ b/knife/spec/unit/knife/tag_delete_spec.rb
@@ -0,0 +1,25 @@
+require "knife_spec_helper"
+
+describe Chef::Knife::TagDelete do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::TagDelete.new
+ @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
+
+ @node = Chef::Node.new
+ allow(@node).to receive :save
+ @node.tags << "sadtag" << "happytag"
+ allow(Chef::Node).to receive(:load).and_return @node
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ end
+
+ describe "run" do
+ it "can delete tags on a node" do
+ expect(@node.tags).to eq(%w{sadtag happytag})
+ @knife.run
+ expect(@node.tags).to eq(["happytag"])
+ expect(@stderr.string).to match(/deleted.+sadtag/i)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/tag_list_spec.rb b/knife/spec/unit/knife/tag_list_spec.rb
new file mode 100644
index 0000000000..5da7803e09
--- /dev/null
+++ b/knife/spec/unit/knife/tag_list_spec.rb
@@ -0,0 +1,23 @@
+require "knife_spec_helper"
+
+describe Chef::Knife::TagList do
+ before(:each) do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ @knife = Chef::Knife::TagList.new
+ @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
+
+ @node = Chef::Node.new
+ allow(@node).to receive :save
+ @node.tags << "sadtag" << "happytag"
+ allow(Chef::Node).to receive(:load).and_return @node
+ end
+
+ describe "run" do
+ it "can list tags on a node" do
+ expected = %w{sadtag happytag}
+ expect(@node.tags).to eq(expected)
+ expect(@knife).to receive(:output).with(expected)
+ @knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/user_create_spec.rb b/knife/spec/unit/knife/user_create_spec.rb
new file mode 100644
index 0000000000..1257d92ee9
--- /dev/null
+++ b/knife/spec/unit/knife/user_create_spec.rb
@@ -0,0 +1,256 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::UserCreate.load_deps
+
+describe Chef::Knife::UserCreate do
+
+ let(:knife) { Chef::Knife::UserCreate.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ let(:stderr) do
+ StringIO.new
+ end
+
+ let(:stdout) do
+ StringIO.new
+ end
+
+ before(:each) do
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stderr)
+ allow(knife.ui).to receive(:warn)
+ end
+
+ let(:chef_root_rest_v0) { double("Chef::ServerAPI") }
+
+ context "when USERNAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { "username" }
+ end
+ end
+
+ context "when FIRST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { %w{some_user some_display_name} }
+ let(:fieldname) { "first name" }
+ end
+ end
+
+ context "when LAST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { %w{some_user some_display_name some_first_name} }
+ let(:fieldname) { "last name" }
+ end
+ end
+
+ context "when EMAIL isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { %w{some_user some_display_name some_first_name some_last_name} }
+ let(:fieldname) { "email" }
+ end
+ end
+
+ describe "with prompt password" do
+ let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com} }
+
+ before :each do
+ @user = double("Chef::User")
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.config[:passwordprompt] = true
+ knife.name_args = name_args
+ end
+
+ it "creates an user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ expect(knife.ui).to receive(:ask).with("Please enter the user's password: ", echo: false).and_return("password")
+ knife.run
+ end
+ end
+
+ context "when all mandatory fields are validly specified" do
+ before do
+ @user = double("Chef::User")
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.name_args = %w{some_user some_display_name some_first_name some_last_name some_email some_password}
+ end
+
+ it "sets all the mandatory fields" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ knife.run
+ expect(knife.user.username).to eq("some_user")
+ expect(knife.user.display_name).to eq("some_display_name")
+ expect(knife.user.first_name).to eq("some_first_name")
+ expect(knife.user.last_name).to eq("some_last_name")
+ expect(knife.user.email).to eq("some_email")
+ end
+
+ context "when user_key and prevent_keygen are passed" do
+ before do
+ knife.config[:user_key] = "some_key"
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/You cannot pass --user-key and --prevent-keygen/)
+ end
+ end
+
+ context "when --prevent-keygen is passed" do
+ before do
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "does not set user.create_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ knife.run
+ expect(knife.user.create_key).to be_falsey
+ end
+ end
+
+ context "when --prevent-keygen is not passed" do
+ it "sets user.create_key to true" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ knife.run
+ expect(knife.user.create_key).to be_truthy
+ end
+ end
+
+ context "when --user-key is passed" do
+ before do
+ knife.config[:user_key] = "some_key"
+ allow(File).to receive(:read).and_return("some_key")
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets user.public_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ knife.run
+ expect(knife.user.public_key).to eq("some_key")
+ end
+ end
+
+ context "when --user-key is not passed" do
+ it "does not set user.public_key" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ knife.run
+ expect(knife.user.public_key).to be_nil
+ end
+ end
+
+ describe "with user_name, first_name, last_name, email and password" do
+ let(:name_args) { %w{some_user some_display_name some_first_name some_last_name test@email.com some_password} }
+
+ before :each do
+ @user = double("Chef::User")
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.name_args = name_args
+ end
+
+ it "creates an user" do
+ expect(knife.ui).to receive(:msg).with(@key)
+ knife.run
+ end
+
+ context "with --orgname" do
+ before :each do
+ knife.config[:orgname] = "ramsay"
+ @uri = "http://www.example.com/1"
+ allow(@user).to receive(:[]).with("uri").and_return(@uri)
+ end
+
+ let(:request_body) {
+ { user: "some_user" }
+ }
+
+ it "creates an user, associates a user, and adds it to the admins group" do
+
+ expect(root_rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user)
+ expect(root_rest).to receive(:put).with("users/some_user/association_requests/1", { response: "accept" })
+ knife.run
+ end
+ end
+ end
+
+ describe "user user_name, --email, --password" do
+ let(:name_args) { %w{some_user} }
+
+ before :each do
+ @user = double("Chef::User")
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:post).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(@user).to receive(:[]).with("private_key").and_return(@key)
+ knife.name_args = name_args
+ knife.config[:email] = "test@email.com"
+ knife.config[:password] = "some_password"
+ end
+
+ it "creates an user" do
+ expect(knife.ui).to receive(:msg).with(@key)
+ knife.run
+ end
+
+ context "with --orgname" do
+ before :each do
+ knife.config[:orgname] = "ramsay"
+ @uri = "http://www.example.com/1"
+ allow(@user).to receive(:[]).with("uri").and_return(@uri)
+ end
+
+ let(:request_body) {
+ { user: "some_user" }
+ }
+
+ it "creates an user, associates a user, and adds it to the admins group" do
+
+ expect(root_rest).to receive(:post).with("organizations/ramsay/association_requests", request_body).and_return(@user)
+ expect(root_rest).to receive(:put).with("users/some_user/association_requests/1", { response: "accept" })
+ knife.run
+ end
+ end
+
+ end
+
+ end # when all mandatory fields are validly specified
+end
diff --git a/knife/spec/unit/knife/user_delete_spec.rb b/knife/spec/unit/knife/user_delete_spec.rb
new file mode 100644
index 0000000000..57d4072c50
--- /dev/null
+++ b/knife/spec/unit/knife/user_delete_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+Chef::Knife::UserDelete.load_deps
+
+describe Chef::Knife::UserDelete do
+ subject(:knife) { Chef::Knife::UserDelete.new }
+
+ let(:non_admin_member_org) { Chef::Org.new("non-admin-member") }
+ let(:solo_admin_member_org) { Chef::Org.new("solo-admin-member") }
+ let(:shared_admin_member_org) { Chef::Org.new("shared-admin-member") }
+
+ let(:removable_orgs) { [non_admin_member_org, shared_admin_member_org] }
+ let(:non_removable_orgs) { [solo_admin_member_org] }
+
+ let(:admin_memberships) { [ removable_orgs, non_removable_orgs ] }
+ let(:username) { "test_user" }
+
+ let(:rest) { double("Chef::ServerAPI") }
+ let(:orgs) { [non_admin_member_org, solo_admin_member_org, shared_admin_member_org] }
+ let(:knife) { Chef::Knife::UserDelete.new }
+
+ let(:orgs_data) do
+ [{ "organization" => { "name" => "non-admin-member" } },
+ { "organization" => { "name" => "solo-admin-member" } },
+ { "organization" => { "name" => "shared-admin-member" } },
+ ]
+ end
+
+ before(:each) do
+ allow(Chef::ServerAPI).to receive(:new).and_return(rest)
+ knife.name_args << username
+ knife.config[:yes] = true
+ end
+
+ context "when invoked" do
+ before do
+ allow(knife).to receive(:admin_group_memberships).and_return(admin_memberships)
+ end
+
+ context "with --no-disassociate-user" do
+ before(:each) do
+ knife.config[:no_disassociate_user] = true
+ end
+
+ it "should bypass all checks and go directly to user deletion" do
+ expect(knife).to receive(:delete_user).with(username)
+ knife.run
+ end
+ end
+
+ context "without --no-disassociate-user" do
+ before do
+ allow(knife).to receive(:org_memberships).and_return(orgs)
+ end
+
+ context "and with --remove-from-admin-groups" do
+ let(:non_removable_orgs) { [ solo_admin_member_org ] }
+ before(:each) do
+ knife.config[:remove_from_admin_groups] = true
+ end
+
+ context "when an associated user the only organization admin" do
+ let(:non_removable_orgs) { [ solo_admin_member_org ] }
+
+ it "refuses to proceed with because the user is the only admin" do
+ expect(knife).to receive(:error_exit_cant_remove_admin_membership!).and_call_original
+ expect { knife.run }.to raise_error SystemExit
+ end
+ end
+
+ context "when an associated user is one of many organization admins" do
+ let(:non_removable_orgs) { [] }
+
+ it "should remove the user from the group, the org, and then and delete the user" do
+ expect(knife).to receive(:disassociate_user)
+ expect(knife).to receive(:remove_from_admin_groups)
+ expect(knife).to receive(:delete_user)
+ expect(knife).to receive(:error_exit_cant_remove_admin_membership!).exactly(0).times
+ expect(knife).to receive(:error_exit_admin_group_member!).exactly(0).times
+ knife.run
+ end
+
+ end
+ end
+
+ context "and without --remove-from-admin-groups" do
+ before(:each) do
+ knife.config[:remove_from_admin_groups] = false
+ end
+
+ context "when an associated user is in admins group" do
+ let(:removable_orgs) { [ shared_admin_member_org ] }
+ let(:non_removable_orgs) { [ ] }
+ it "refuses to proceed with because the user is an admin" do
+ # Default setup
+ expect(knife).to receive(:error_exit_admin_group_member!).and_call_original
+ expect { knife.run }.to raise_error SystemExit
+ end
+ end
+ end
+
+ end
+ end
+
+ context "#admin_group_memberships" do
+ before do
+ expect(non_admin_member_org).to receive(:user_member_of_group?).and_return false
+
+ expect(solo_admin_member_org).to receive(:user_member_of_group?).and_return true
+ expect(solo_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return true
+
+ expect(shared_admin_member_org).to receive(:user_member_of_group?).and_return true
+ expect(shared_admin_member_org).to receive(:actor_delete_would_leave_admins_empty?).and_return false
+
+ end
+
+ it "returns an array of organizations in which the user is an admin, and an array of orgs which block removal" do
+ expect(knife.admin_group_memberships(orgs, username)).to eq [ [solo_admin_member_org, shared_admin_member_org], [solo_admin_member_org]]
+ end
+ end
+
+ context "#delete_user" do
+ it "attempts to delete the user from the system via DELETE to the /users endpoint" do
+ expect(rest).to receive(:delete).with("users/#{username}")
+ knife.delete_user(username)
+ end
+ end
+
+ context "#disassociate_user" do
+ it "attempts to remove dissociate the user from each org" do
+ removable_orgs.each { |org| expect(org).to receive(:dissociate_user).with(username) }
+ knife.disassociate_user(removable_orgs, username)
+ end
+ end
+
+ context "#remove_from_admin_groups" do
+ it "attempts to remove the given user from the organizations' groups" do
+ removable_orgs.each { |org| expect(org).to receive(:remove_user_from_group).with("admins", username) }
+ knife.remove_from_admin_groups(removable_orgs, username)
+ end
+ end
+
+ context "#org_memberships" do
+ it "should make a REST request to return the list of organizations that the user is a member of" do
+ expect(rest).to receive(:get).with("users/test_user/organizations").and_return orgs_data
+ result = knife.org_memberships(username)
+ result.each_with_index do |v, x|
+ expect(v.to_hash).to eq(orgs[x].to_hash)
+ end
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/user_edit_spec.rb b/knife/spec/unit/knife/user_edit_spec.rb
new file mode 100644
index 0000000000..12e2f19561
--- /dev/null
+++ b/knife/spec/unit/knife/user_edit_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::UserEdit do
+ let(:knife) { Chef::Knife::UserEdit.new }
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before(:each) do
+ @stderr = StringIO.new
+ @stdout = StringIO.new
+ allow(knife.ui).to receive(:stderr).and_return(@stderr)
+ allow(knife.ui).to receive(:stdout).and_return(@stdout)
+ knife.name_args = [ "my_user2" ]
+ knife.config[:disable_editing] = true
+ end
+
+ it "loads and edits the user" do
+ data = { "username" => "my_user2" }
+ edited_data = { "username" => "edit_user2" }
+ result = {}
+ @key = "You don't come into cooking to get rich - Ramsay"
+ allow(result).to receive(:[]).with("private_key").and_return(@key)
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(root_rest).to receive(:get).with("users/my_user2").and_return(data)
+ expect(knife).to receive(:get_updated_user).with(data).and_return(edited_data)
+ expect(root_rest).to receive(:put).with("users/my_user2", edited_data).and_return(result)
+ knife.run
+ end
+
+ it "prints usage and exits when a user name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/knife/spec/unit/knife/user_list_spec.rb b/knife/spec/unit/knife/user_list_spec.rb
new file mode 100644
index 0000000000..01013de352
--- /dev/null
+++ b/knife/spec/unit/knife/user_list_spec.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Steven Danna
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+Chef::Knife::UserList.load_deps
+
+describe Chef::Knife::UserList do
+
+ let(:knife) { Chef::Knife::UserList.new }
+ let(:users) do
+ {
+ "user1" => "http//test/users/user1",
+ "user2" => "http//test/users/user2",
+ }
+ end
+
+ before :each do
+ @rest = double("Chef::ServerAPI")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@rest)
+ allow(@rest).to receive(:get).with("users").and_return(users)
+ end
+
+ describe "with no arguments" do
+ it "lists all non users" do
+ expect(knife.ui).to receive(:output).with(%w{user1 user2})
+ knife.run
+ end
+
+ end
+
+ describe "with all_users argument" do
+ before do
+ knife.config[:all_users] = true
+ end
+
+ it "lists all users including hidden users" do
+ expect(knife.ui).to receive(:output).with(%w{user1 user2})
+ knife.run
+ end
+ end
+
+ it "lists the users" do
+ expect(knife).to receive(:format_list_for_display)
+ knife.run
+ end
+
+ describe "with options with_uri argument" do
+ before do
+ knife.config[:with_uri] = true
+ end
+
+ it "lists all users including hidden users" do
+ expect(knife.ui).to receive(:output).with(users)
+ knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/user_password_spec.rb b/knife/spec/unit/knife/user_password_spec.rb
new file mode 100644
index 0000000000..139ed242de
--- /dev/null
+++ b/knife/spec/unit/knife/user_password_spec.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Snehal Dwivedi (<sdwivedi@msystechnologies.com>)
+# Copyright:: Copyright 2014-2016 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 "knife_spec_helper"
+require "chef/org"
+
+Chef::Knife::UserDelete.load_deps
+
+describe Chef::Knife::UserPassword do
+
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @knife = Chef::Knife::UserPassword.new
+ @user_name = "foobar"
+ @password = "abc123"
+ @user = double("Chef::User")
+ allow(@user).to receive(:root_rest).and_return(root_rest)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ describe "should change user's password" do
+ before :each do
+ @knife.name_args << @user_name << @password
+ end
+
+ it "with username and password" do
+ result = { "password" => [], "recovery_authentication_enabled" => true }
+ allow(@user).to receive(:[]).with("organization")
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(@user.root_rest).to receive(:put).with("users/#{@user_name}", result)
+ expect(@knife.ui).to receive(:msg).with("Authentication info updated for #{@user_name}.")
+
+ @knife.run
+ end
+ end
+
+ describe "should not change user's password" do
+
+ it "ails with an informative message" do
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal).with("You must pass two arguments")
+ expect(@knife.ui).to receive(:fatal).with("Note that --enable-external-auth cannot be passed with a password")
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+ end
+end
diff --git a/knife/spec/unit/knife/user_reregister_spec.rb b/knife/spec/unit/knife/user_reregister_spec.rb
new file mode 100644
index 0000000000..9178996abf
--- /dev/null
+++ b/knife/spec/unit/knife/user_reregister_spec.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+
+describe Chef::Knife::UserReregister do
+ let(:knife) { Chef::Knife::UserReregister.new }
+ let(:user_mock) { double("user_mock", private_key: "private_key") }
+ let(:stdout) { StringIO.new }
+
+ before do
+ Chef::Knife::UserReregister.load_deps
+ knife.name_args = [ "a_user" ]
+ allow(Chef::UserV1).to receive(:load).and_return(user_mock)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stdout)
+ allow(user_mock).to receive(:username).and_return("a_user")
+ end
+
+ it "prints usage and exits when a user name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "reregisters the user and prints the key" do
+ expect(user_mock).to receive(:reregister).and_return(user_mock)
+ knife.run
+ expect(stdout.string).to match( /private_key/ )
+ end
+
+ it "writes the private key to a file when --file is specified" do
+ expect(user_mock).to receive(:reregister).and_return(user_mock)
+ knife.config[:file] = "/tmp/a_file"
+ filehandle = StringIO.new
+ expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
+ knife.run
+ expect(filehandle.string).to eq("private_key")
+ end
+end
diff --git a/knife/spec/unit/knife/user_show_spec.rb b/knife/spec/unit/knife/user_show_spec.rb
new file mode 100644
index 0000000000..3bcbbcb648
--- /dev/null
+++ b/knife/spec/unit/knife/user_show_spec.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 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 "knife_spec_helper"
+require "chef/org"
+
+Chef::Knife::UserShow.load_deps
+
+describe Chef::Knife::UserShow do
+ let(:knife) { Chef::Knife::UserShow.new }
+ let(:user_mock) { double("user_mock") }
+ let(:root_rest) { double("Chef::ServerAPI") }
+
+ before :each do
+ @user_name = "foobar"
+ @password = "abc123"
+ @user = double("Chef::User")
+ allow(@user).to receive(:root_rest).and_return(root_rest)
+ # allow(Chef::User).to receive(:new).and_return(@user)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ describe "withot organisation argument" do
+ before do
+ knife.name_args = [ "my_user" ]
+ allow(user_mock).to receive(:username).and_return("my_user")
+ end
+
+ it "should load the user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/my_user")
+ knife.run
+ end
+
+ it "loads and displays the user" do
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ expect(@user.root_rest).to receive(:get).with("users/my_user")
+ expect(knife).to receive(:format_for_display)
+ knife.run
+ end
+
+ it "prints usage and exits when a user name is not provided" do
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+ end
+
+ describe "with organisation argument" do
+ before :each do
+ @user_name = "foobar"
+ @org_name = "abc_org"
+ knife.name_args << @user_name << @org_name
+ @org = double("Chef::Org")
+ allow(Chef::Org).to receive(:new).and_return(@org)
+ @key = "You don't come into cooking to get rich - Ramsay"
+ end
+
+ let(:orgs) do
+ [@org]
+ end
+
+ it "should load the user with organisation" do
+
+ result = { "organizations" => [] }
+ knife.config[:with_orgs] = true
+
+ expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_root]).and_return(root_rest)
+ allow(@org).to receive(:[]).with("organization").and_return({ "name" => "test" })
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}").and_return(result)
+ expect(@user.root_rest).to receive(:get).with("users/#{@user_name}/organizations").and_return(orgs)
+ knife.run
+ end
+ end
+end
diff --git a/knife/spec/unit/knife_spec.rb b/knife/spec/unit/knife_spec.rb
new file mode 100644
index 0000000000..f3315f3cf5
--- /dev/null
+++ b/knife/spec/unit/knife_spec.rb
@@ -0,0 +1,634 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Tim Hinderliter (<tim@chef.io>)
+# Copyright:: Copyright (c) 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.
+#
+
+# Fixtures for subcommand loading live in this namespace
+module KnifeSpecs
+end
+
+require "knife_spec_helper"
+require "uri"
+require "chef/knife/core/gem_glob_loader"
+
+describe Chef::Knife do
+
+ let(:stderr) { StringIO.new }
+
+ let(:knife) { Chef::Knife.new }
+
+ let(:config_location) { File.expand_path("~/.chef/config.rb") }
+
+ let(:config_loader) do
+ instance_double("WorkstationConfigLoader",
+ load: nil, no_config_found?: false,
+ config_location: config_location,
+ chef_config_dir: "/etc/chef")
+ end
+
+ before(:each) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ Chef::Config[:node_name] = "webmonkey.example.com"
+
+ allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
+ allow(config_loader).to receive(:explicit_config_file=)
+ allow(config_loader).to receive(:profile=)
+
+ # Prevent gratuitous code reloading:
+ allow(Chef::Knife).to receive(:load_commands)
+ allow(knife.ui).to receive(:puts)
+ allow(knife.ui).to receive(:print)
+ allow(Chef::Log).to receive(:init)
+ allow(Chef::Log).to receive(:level)
+ %i{debug info warn error crit}.each do |level_sym|
+ allow(Chef::Log).to receive(level_sym)
+ end
+ allow(Chef::Knife).to receive(:puts)
+ end
+
+ after(:each) do
+ Chef::Knife.reset_config_loader!
+ end
+
+ it "does not reset Chef::Config[:verbosity to nil if config[:verbosity] is nil" do
+ Chef::Config[:verbosity] = 2
+ Chef::Knife.new
+ expect(Chef::Config[:verbosity]).to eq(2)
+ end
+
+ describe "after loading a subcommand" do
+ before do
+ Chef::Knife.reset_subcommands!
+
+ if KnifeSpecs.const_defined?(:TestNameMapping)
+ KnifeSpecs.send(:remove_const, :TestNameMapping)
+ end
+
+ if KnifeSpecs.const_defined?(:TestExplicitCategory)
+ KnifeSpecs.send(:remove_const, :TestExplicitCategory)
+ end
+
+ Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_name_mapping.rb"))
+ Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_explicit_category.rb"))
+ end
+
+ it "has a category based on its name" do
+ expect(KnifeSpecs::TestNameMapping.subcommand_category).to eq("test")
+ end
+
+ it "has an explicitly defined category if set" do
+ expect(KnifeSpecs::TestExplicitCategory.subcommand_category).to eq("cookbook site")
+ end
+
+ it "can reference the subcommand by its snake cased name" do
+ expect(Chef::Knife.subcommands["test_name_mapping"]).to equal(KnifeSpecs::TestNameMapping)
+ end
+
+ it "lists subcommands by category" do
+ expect(Chef::Knife.subcommands_by_category["test"]).to include("test_name_mapping")
+ end
+
+ it "lists subcommands by category when the subcommands have explicit categories" do
+ expect(Chef::Knife.subcommands_by_category["cookbook site"]).to include("test_explicit_category")
+ end
+
+ it "has empty dependency_loader list by default" do
+ expect(KnifeSpecs::TestNameMapping.dependency_loaders).to be_empty
+ end
+ end
+
+ describe "after loading all subcommands" do
+ before do
+ Chef::Knife.reset_subcommands!
+ Chef::Knife.load_commands
+ end
+
+ it "references a subcommand class by its snake cased name" do
+ class SuperAwesomeCommand < Chef::Knife
+ end
+
+ Chef::Knife.load_commands
+
+ expect(Chef::Knife.subcommands).to have_key("super_awesome_command")
+ expect(Chef::Knife.subcommands["super_awesome_command"]).to eq(SuperAwesomeCommand)
+ end
+
+ it "records the location of ChefFS-based commands correctly" do
+ class AwesomeCheffsCommand < Chef::ChefFS::Knife
+ end
+
+ Chef::Knife.load_commands
+ expect(Chef::Knife.subcommand_files["awesome_cheffs_command"]).to eq([__FILE__])
+ end
+
+ it "guesses a category from a given ARGV" do
+ Chef::Knife.subcommands_by_category["cookbook"] << :cookbook
+ Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site
+ expect(Chef::Knife.guess_category(%w{cookbook foo bar baz})).to eq("cookbook")
+ expect(Chef::Knife.guess_category(%w{cookbook site foo bar baz})).to eq("cookbook site")
+ expect(Chef::Knife.guess_category(%w{cookbook site --help})).to eq("cookbook site")
+ end
+
+ it "finds a subcommand class based on ARGV" do
+ Chef::Knife.subcommands["cookbook_site_install"] = :CookbookSiteInstall
+ Chef::Knife.subcommands["cookbook"] = :Cookbook
+ expect(Chef::Knife.subcommand_class_from(%w{cookbook site install --help foo bar baz})).to eq(:CookbookSiteInstall)
+ end
+
+ it "special case sets the subcommand_loader to GemGlobLoader when running rehash" do
+ Chef::Knife.subcommands["rehash"] = :Rehash
+ expect(Chef::Knife.subcommand_class_from(%w{rehash })).to eq(:Rehash)
+ expect(Chef::Knife.subcommand_loader).to be_a(Chef::Knife::SubcommandLoader::GemGlobLoader)
+ end
+
+ end
+
+ describe "the headers include X-Remote-Request-Id" do
+
+ let(:headers) do
+ { "Accept" => "application/json",
+ "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
+ "X-Chef-Version" => Chef::VERSION,
+ "Host" => "api.opscode.piab",
+ "X-REMOTE-REQUEST-ID" => request_id,
+ }
+ end
+
+ let(:request_id) { "1234" }
+
+ let(:request_mock) { {} }
+
+ let(:rest) do
+ allow(Net::HTTP).to receive(:new).and_return(http_client)
+ allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id)
+ allow(Chef::Config).to receive(:chef_server_url).and_return("https://api.opscode.piab")
+ command = Chef::Knife.run(%w{test yourself})
+ rest = command.noauth_rest
+ rest
+ end
+
+ let!(:http_client) do
+ http_client = Net::HTTP.new(url.host, url.port)
+ allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response)
+ http_client
+ end
+
+ let(:url) { URI.parse("https://api.opscode.piab") }
+
+ let(:http_response) do
+ http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
+ allow(http_response).to receive(:read_body)
+ allow(http_response).to receive(:body).and_return(body)
+ http_response["Content-Length"] = body.bytesize.to_s
+ http_response
+ end
+
+ let(:body) { "ninja" }
+
+ before(:each) do
+ Chef::Config[:chef_server_url] = "https://api.opscode.piab"
+ if KnifeSpecs.const_defined?(:TestYourself)
+ KnifeSpecs.send :remove_const, :TestYourself
+ end
+ Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb"))
+ Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) }
+ end
+
+ it "confirms that the headers include X-Remote-Request-Id" do
+ expect(Net::HTTP::Get).to receive(:new).with("/monkey", headers).and_return(request_mock)
+ rest.get("monkey")
+ end
+ end
+
+ describe "when running a command" do
+ before(:each) do
+ if KnifeSpecs.const_defined?(:TestYourself)
+ KnifeSpecs.send :remove_const, :TestYourself
+ end
+ Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb"))
+ Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) }
+ end
+
+ it "merges the global knife CLI options" do
+ extra_opts = {}
+ extra_opts[:editor] = { long: "--editor EDITOR",
+ description: "Set the editor to use for interactive commands",
+ short: "-e EDITOR",
+ default: "/usr/bin/vim" }
+
+ # there is special hackery to return the subcommand instance going on here.
+ command = Chef::Knife.run(%w{test yourself}, extra_opts)
+ editor_opts = command.options[:editor]
+ expect(editor_opts[:long]).to eq("--editor EDITOR")
+ expect(editor_opts[:description]).to eq("Set the editor to use for interactive commands")
+ expect(editor_opts[:short]).to eq("-e EDITOR")
+ expect(editor_opts[:default]).to eq("/usr/bin/vim")
+ end
+
+ it "creates an instance of the subcommand and runs it" do
+ command = Chef::Knife.run(%w{test yourself})
+ expect(command).to be_an_instance_of(KnifeSpecs::TestYourself)
+ expect(command.ran).to be_truthy
+ end
+
+ it "passes the command specific args to the subcommand" do
+ command = Chef::Knife.run(%w{test yourself with some args})
+ expect(command.name_args).to eq(%w{with some args})
+ end
+
+ it "excludes the command name from the name args when parts are joined with underscores" do
+ command = Chef::Knife.run(%w{test_yourself with some args})
+ expect(command.name_args).to eq(%w{with some args})
+ end
+
+ it "exits if no subcommand matches the CLI args" do
+ stdout = StringIO.new
+
+ allow(Chef::Knife.ui).to receive(:stderr).and_return(stderr)
+ allow(Chef::Knife.ui).to receive(:stdout).and_return(stdout)
+ expect(Chef::Knife.ui).to receive(:fatal)
+ expect { Chef::Knife.run(%w{fuuu uuuu fuuuu}) }.to raise_error(SystemExit) { |e| expect(e.status).not_to eq(0) }
+ end
+
+ it "loads lazy dependencies" do
+ Chef::Knife.run(%w{test yourself})
+ expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy
+ end
+
+ it "loads lazy dependencies from multiple deps calls" do
+ other_deps_loaded = false
+ KnifeSpecs::TestYourself.class_eval do
+ deps { other_deps_loaded = true }
+ end
+
+ Chef::Knife.run(%w{test yourself})
+ expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy
+ expect(other_deps_loaded).to be_truthy
+ end
+
+ describe "working with unmerged configuration in #config_source" do
+ let(:command) { KnifeSpecs::TestYourself.new([]) }
+
+ before do
+ KnifeSpecs::TestYourself.option(:opt_with_default,
+ short: "-D VALUE",
+ default: "default-value")
+ end
+ # This supports a use case used by plugins, where the pattern
+ # seems to follow:
+ # cmd = KnifeCommand.new
+ # cmd.config[:config_key] = value
+ # cmd.run
+ #
+ # This bypasses Knife::run and the `merge_configs` call it
+ # performs - config_source should break when that happens.
+ context "when config is fed in directly without a merge" do
+ it "retains the value but returns nil as a config source" do
+ command.config[:test1] = "value"
+ expect(command.config[:test1]).to eq "value"
+ expect(command.config_source(:test1)).to eq nil
+ end
+ end
+
+ end
+ describe "merging configuration options" do
+ before do
+ KnifeSpecs::TestYourself.option(:opt_with_default,
+ short: "-D VALUE",
+ default: "default-value")
+ end
+
+ it "sets the default log_location to STDERR for Chef::Log warnings" do
+ knife_command = KnifeSpecs::TestYourself.new([])
+ knife_command.configure_chef
+ expect(Chef::Config[:log_location]).to eq(STDERR)
+ end
+
+ it "sets the default log_level to warn so we can issue Chef::Log.warn" do
+ knife_command = KnifeSpecs::TestYourself.new([])
+ knife_command.configure_chef
+ expect(Chef::Config[:log_level]).to eql(:warn)
+ end
+
+ it "prefers the default value from option definition if no config or command line value is present and reports the source as default" do
+ knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
+ knife_command.configure_chef
+ expect(knife_command.config[:opt_with_default]).to eq("default-value")
+ expect(knife_command.config_source(:opt_with_default)).to eq(:cli_default)
+ end
+
+ it "prefers a value in Chef::Config[:knife] to the default and reports the source as config" do
+ Chef::Config[:knife][:opt_with_default] = "from-knife-config"
+ knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
+ knife_command.configure_chef
+ expect(knife_command.config[:opt_with_default]).to eq("from-knife-config")
+ expect(knife_command.config_source(:opt_with_default)).to eq(:config)
+ end
+
+ it "prefers a value from command line over Chef::Config and the default and reports the source as CLI" do
+ knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"])
+ knife_command.configure_chef
+ expect(knife_command.config[:opt_with_default]).to eq("from-cli")
+ expect(knife_command.config_source(:opt_with_default)).to eq(:cli)
+ end
+
+ it "merges `listen` config to Chef::Config" do
+ knife_command = Chef::Knife.run(%w{test yourself --no-listen}, Chef::Application::Knife.options)
+ expect(Chef::Config[:listen]).to be(false)
+ expect(knife_command.config_source(:listen)).to eq(:cli)
+ end
+
+ it "merges Chef::Config[:knife] values into the config hash even if they have no cli keys" do
+ Chef::Config[:knife][:opt_with_no_cli_key] = "from-knife-config"
+ knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
+ knife_command.configure_chef
+ expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-config")
+ expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config)
+ end
+
+ it "merges Chef::Config[:knife] default values into the config hash even if they have no cli keys" do
+ Chef::Config.config_context :knife do
+ default :opt_with_no_cli_key, "from-knife-default"
+ end
+ knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
+ knife_command.configure_chef
+ expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-default")
+ expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config_default)
+ end
+
+ context "verbosity is one" do
+ let(:fake_config) { "/does/not/exist/knife.rb" }
+
+ before do
+ knife.config[:verbosity] = 1
+ knife.config[:config_file] = fake_config
+ config_loader = double("Chef::WorkstationConfigLoader", load: true, no_config_found?: false, chef_config_dir: "/etc/chef", config_location: fake_config)
+ allow(config_loader).to receive(:explicit_config_file=).with(fake_config).and_return(fake_config)
+ allow(config_loader).to receive(:profile=)
+ allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
+ end
+
+ it "prints the path to the configuration file used" do
+ stdout, stderr, stdin = StringIO.new, StringIO.new, StringIO.new
+ knife.ui = Chef::Knife::UI.new(stdout, stderr, stdin, {})
+ expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}")
+ knife.configure_chef
+ end
+ end
+
+ # -VV (2) is debug, -VVV (3) is trace
+ [ 2, 3 ].each do |verbosity|
+ it "does not humanize the exception if Chef::Config[:verbosity] is #{verbosity}" do
+ Chef::Config[:verbosity] = verbosity
+ allow(knife).to receive(:run).and_raise(Exception)
+ expect(knife).not_to receive(:humanize_exception)
+ expect { knife.run_with_pretty_exceptions }.to raise_error(Exception)
+ end
+ end
+ end
+
+ describe "setting arbitrary configuration with --config-option" do
+
+ let(:stdout) { StringIO.new }
+
+ let(:stderr) { StringIO.new }
+
+ let(:stdin) { StringIO.new }
+
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) }
+
+ let(:subcommand) do
+ KnifeSpecs::TestYourself.options = Chef::Application::Knife.options.merge(KnifeSpecs::TestYourself.options)
+ KnifeSpecs::TestYourself.new(%w{--config-option badly_formatted_arg}).tap do |cmd|
+ cmd.ui = ui
+ end
+ end
+
+ it "sets arbitrary configuration via --config-option" do
+ Chef::Knife.run(%w{test yourself --config-option arbitrary_config_thing=hello}, Chef::Application::Knife.options)
+ expect(Chef::Config[:arbitrary_config_thing]).to eq("hello")
+ end
+
+ it "handles errors in arbitrary configuration" do
+ expect(subcommand).to receive(:exit).with(1)
+ subcommand.configure_chef
+ expect(stderr.string).to include("ERROR: Unparsable config option \"badly_formatted_arg\"")
+ expect(stdout.string).to include(subcommand.opt_parser.to_s)
+ end
+ end
+
+ end
+
+ describe "when first created" do
+
+ let(:knife) {
+ Kernel.load "spec/data/knife_subcommand/test_yourself.rb"
+ KnifeSpecs::TestYourself.new(%w{with some args -s scrogramming})
+ }
+
+ it "it parses the options passed to it" do
+ expect(knife.config[:scro]).to eq("scrogramming")
+ end
+
+ it "extracts its command specific args from the full arg list" do
+ expect(knife.name_args).to eq(%w{with some args})
+ end
+
+ it "does not have lazy dependencies loaded" do
+ skip "unstable with randomization... prolly needs more isolation"
+
+ expect(knife.class.test_deps_loaded).not_to be_truthy
+ end
+ end
+
+ describe "when formatting exceptions" do
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:stdin) { StringIO.new }
+
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
+
+ before do
+ knife.ui = ui
+ expect(knife).to receive(:exit).with(100)
+ end
+
+ it "formats 401s nicely" do
+ response = Net::HTTPUnauthorized.new("1.1", "401", "Unauthorized")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no syncronize your clock?"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("401 Unauthorized", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: Failed to authenticate to/)
+ expect(stderr.string).to match(/Response: y u no syncronize your clock\?/)
+ end
+
+ it "formats 403s nicely" do
+ response = Net::HTTPForbidden.new("1.1", "403", "Forbidden")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response))
+ allow(knife).to receive(:username).and_return("sadpanda")
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/)
+ expect(stderr.string).to match(/Response: y u no administrator/)
+ end
+
+ context "when proxy servers are set" do
+ before do
+ ENV["http_proxy"] = "xyz"
+ end
+
+ after do
+ ENV.delete("http_proxy")
+ end
+
+ it "formats proxy errors nicely" do
+ response = Net::HTTPForbidden.new("1.1", "403", "Forbidden")
+ response.instance_variable_set(:@read, true)
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response))
+ allow(knife).to receive(:username).and_return("sadpanda")
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/)
+ expect(stderr.string).to match(/ERROR: There are proxy servers configured, your server url may need to be added to NO_PROXY./)
+ expect(stderr.string).to match(/Response: y u no administrator/)
+ end
+ end
+
+ it "formats 400s nicely" do
+ response = Net::HTTPBadRequest.new("1.1", "400", "Bad Request")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u search wrong"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("400 Bad Request", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: The data in your request was invalid/)
+ expect(stderr.string).to match(/Response: y u search wrong/)
+ end
+
+ it "formats 404s nicely" do
+ response = Net::HTTPNotFound.new("1.1", "404", "Not Found")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nothing to see here"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("404 Not Found", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: The object you are looking for could not be found/)
+ expect(stderr.string).to match(/Response: nothing to see here/)
+ end
+
+ it "formats 406s (non-supported API version error) nicely" do
+ response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+
+ # set the header
+ response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(min_version: "0", max_version: "1", request_version: "10000000")
+
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("406 Not Acceptable", response))
+
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/The request that .* sent was using API version 10000000./)
+ expect(stderr.string).to match(/The server you sent the request to supports a min API version of 0 and a max API version of 1./)
+ expect(stderr.string).to match(/Please either update your .* or the server to be a compatible set./)
+ end
+
+ it "formats 500s nicely" do
+ response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("500 Internal Server Error", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: internal server error/)
+ expect(stderr.string).to match(/Response: sad trombone/)
+ end
+
+ it "formats 502s nicely" do
+ response = Net::HTTPBadGateway.new("1.1", "502", "Bad Gateway")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sadder trombone"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("502 Bad Gateway", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: bad gateway/)
+ expect(stderr.string).to match(/Response: sadder trombone/)
+ end
+
+ it "formats 503s nicely" do
+ response = Net::HTTPServiceUnavailable.new("1.1", "503", "Service Unavailable")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "saddest trombone"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("503 Service Unavailable", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: Service temporarily unavailable/)
+ expect(stderr.string).to match(/Response: saddest trombone/)
+ end
+
+ it "formats other HTTP errors nicely" do
+ response = Net::HTTPPaymentRequired.new("1.1", "402", "Payment Required")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nobugfixtillyoubuy"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("402 Payment Required", response))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: Payment Required/)
+ expect(stderr.string).to match(/Response: nobugfixtillyoubuy/)
+ end
+
+ it "formats NameError and NoMethodError nicely" do
+ allow(knife).to receive(:run).and_raise(NameError.new("Undefined constant FUUU"))
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(/ERROR: .* encountered an unexpected error/)
+ expect(stderr.string).to match(/This may be a bug in the 'knife' .* command or plugin/)
+ expect(stderr.string).to match(/Exception: NameError: Undefined constant FUUU/)
+ end
+
+ it "formats missing private key errors nicely" do
+ allow(knife).to receive(:run).and_raise(Chef::Exceptions::PrivateKeyMissing.new("key not there"))
+ allow(knife).to receive(:api_key).and_return("/home/root/.chef/no-key-here.pem")
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to match(%r{ERROR: Your private key could not be loaded from /home/root/.chef/no-key-here.pem})
+ expect(stderr.string).to match(/Check your configuration file and ensure that your private key is readable/)
+ end
+
+ it "formats connection refused errors nicely" do
+ allow(knife).to receive(:run).and_raise(Errno::ECONNREFUSED.new("y u no shut up"))
+ knife.run_with_pretty_exceptions
+ # Errno::ECONNREFUSED message differs by platform
+ # *nix = Errno::ECONNREFUSED: Connection refused
+ # win32: Errno::ECONNREFUSED: No connection could be made because the target machine actively refused it.
+ expect(stderr.string).to match(/ERROR: Network Error: .* - y u no shut up/)
+ expect(stderr.string).to match(/Check your .* configuration and network settings/)
+ end
+
+ it "formats SSL errors nicely and suggests to use `knife ssl check` and `knife ssl fetch`" do
+ error = OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed")
+ allow(knife).to receive(:run).and_raise(error)
+
+ knife.run_with_pretty_exceptions
+
+ expected_message = <<~MSG
+ ERROR: Could not establish a secure connection to the server.
+ Use `.* ssl check` to troubleshoot your SSL configuration.
+ If your server uses a self-signed certificate, you can use
+ `.* ssl fetch` to make .* trust the server's certificates.
+ MSG
+ expect(stderr.string).to match(expected_message)
+ end
+
+ end
+
+end
diff --git a/lib/chef/action_collection.rb b/lib/chef/action_collection.rb
index 1ac47630a9..82a4ebb037 100644
--- a/lib/chef/action_collection.rb
+++ b/lib/chef/action_collection.rb
@@ -87,13 +87,11 @@ class Chef
attr_reader :action_records
attr_reader :pending_updates
attr_reader :run_context
- attr_reader :consumers
attr_reader :events
def initialize(events, run_context = nil, action_records = [])
@action_records = action_records
@pending_updates = []
- @consumers = []
@events = events
@run_context = run_context
end
@@ -118,17 +116,17 @@ class Chef
self.class.new(events, run_context, subrecords)
end
+ def resources
+ action_records.map(&:new_resource)
+ end
+
# This hook gives us the run_context immediately after it is created so that we can wire up this object to it.
#
- # This also causes the action_collection_registration event to fire, all consumers that have not yet registered with the
- # action_collection must register via this callback. This is the latest point before resources actually start to get
- # evaluated.
- #
# (see EventDispatch::Base#)
#
def cookbook_compilation_start(run_context)
run_context.action_collection = self
- # fire the action_colleciton_registration hook after cookbook_compilation_start -- last chance for consumers to register
+ # this hook is now poorly named since it is just a callback that lets other consumers snag a reference to the action_collection
run_context.events.enqueue(:action_collection_registration, self)
@run_context = run_context
end
@@ -139,7 +137,7 @@ class Chef
# @params object [Object] callers should call with `self`
#
def register(object)
- consumers << object
+ Chef::Log.warn "the action collection no longer requires registration at #{caller[0]}"
end
# End of an unsuccessful converge used to fire off detect_unprocessed_resources.
@@ -147,8 +145,6 @@ class Chef
# (see EventDispatch::Base#)
#
def converge_failed(exception)
- return if consumers.empty?
-
detect_unprocessed_resources
end
@@ -159,8 +155,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_action_start(new_resource, action, notification_type = nil, notifier = nil)
- return if consumers.empty?
-
pending_updates << ActionRecord.new(new_resource, action, pending_updates.length)
end
@@ -170,8 +164,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_current_state_loaded(new_resource, action, current_resource)
- return if consumers.empty?
-
current_record.current_resource = current_resource
end
@@ -181,8 +173,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_after_state_loaded(new_resource, action, after_resource)
- return if consumers.empty?
-
current_record.after_resource = after_resource
end
@@ -191,8 +181,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_up_to_date(new_resource, action)
- return if consumers.empty?
-
current_record.status = :up_to_date
end
@@ -201,8 +189,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_skipped(resource, action, conditional)
- return if consumers.empty?
-
current_record.status = :skipped
current_record.conditional = conditional
end
@@ -212,8 +198,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_updated(new_resource, action)
- return if consumers.empty?
-
current_record.status = :updated
end
@@ -222,8 +206,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_failed(new_resource, action, exception)
- return if consumers.empty?
-
current_record.status = :failed
current_record.exception = exception
current_record.error_description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception).for_json
@@ -234,8 +216,6 @@ class Chef
# (see EventDispatch::Base#)
#
def resource_completed(new_resource)
- return if consumers.empty?
-
current_record.elapsed_time = new_resource.elapsed_time
# Verify if the resource has sensitive data and create a new blank resource with only
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 117f498831..356c4a4a30 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -310,7 +310,7 @@ class Chef
logger.info "Forking #{ChefUtils::Dist::Infra::PRODUCT} instance to converge..."
pid = fork do
# Want to allow forked processes to finish converging when
- # TERM singal is received (exit gracefully)
+ # TERM signal is received (exit gracefully)
trap("TERM") do
logger.debug("SIGTERM received during converge," +
" finishing converge to exit normally (send SIGINT to terminate immediately)")
diff --git a/lib/chef/application/base.rb b/lib/chef/application/base.rb
index ad8e8b69c2..b2f98d5f2f 100644
--- a/lib/chef/application/base.rb
+++ b/lib/chef/application/base.rb
@@ -297,6 +297,21 @@ class Chef::Application::Base < Chef::Application
long: "--named-run-list NAMED_RUN_LIST",
description: "Use a policyfile's named run list instead of the default run list."
+ option :slow_report,
+ long: "--[no-]slow-report [COUNT]",
+ description: "List the slowest resources at the end of the run (default: 10).",
+ boolean: true,
+ default: false,
+ proc: lambda { |argument|
+ if argument.nil?
+ true
+ elsif argument == false
+ false
+ else
+ Integer(argument)
+ end
+ }
+
IMMEDIATE_RUN_SIGNAL = "1".freeze
RECONFIGURE_SIGNAL = "H".freeze
@@ -368,7 +383,7 @@ class Chef::Application::Base < Chef::Application
FileUtils.cp(url, path)
elsif URI::DEFAULT_PARSER.make_regexp.match?(url)
File.open(path, "wb") do |f|
- open(url) do |r|
+ URI.open(url) do |r|
f.write(r.read)
end
end
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
deleted file mode 100644
index 7906ce6eaa..0000000000
--- a/lib/chef/application/knife.rb
+++ /dev/null
@@ -1,234 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "../application"
-require "mixlib/log"
-require "ohai/config"
-module Net
- autoload :HTTP, "net/http"
-end
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef::Application::Knife < Chef::Application
-
- NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., knife SUB-COMMAND)\n".freeze
-
- banner "Usage: knife sub-command (options)"
-
- option :config_file,
- short: "-c CONFIG",
- long: "--config CONFIG",
- description: "The configuration file to use.",
- proc: lambda { |path| File.expand_path(path, Dir.pwd) }
-
- option :config_option,
- long: "--config-option OPTION=VALUE",
- description: "Override a single configuration option.",
- proc: lambda { |option, existing|
- (existing ||= []) << option
- existing
- }
-
- verbosity_level = 0
- option :verbosity,
- short: "-V",
- long: "--verbose",
- description: "More verbose output. Use twice (-VV) for additional verbosity and three times (-VVV) for maximum verbosity.",
- proc: Proc.new { verbosity_level += 1 },
- default: 0
-
- option :color,
- long: "--[no-]color",
- boolean: true,
- default: true,
- description: "Use colored output, defaults to enabled."
-
- option :environment,
- short: "-E ENVIRONMENT",
- long: "--environment ENVIRONMENT",
- description: "Set the #{ChefUtils::Dist::Infra::PRODUCT} environment (except for in searches, where this will be flagrantly ignored)."
-
- option :editor,
- short: "-e EDITOR",
- long: "--editor EDITOR",
- description: "Set the editor to use for interactive commands.",
- default: ENV["EDITOR"]
-
- option :disable_editing,
- short: "-d",
- long: "--disable-editing",
- description: "Do not open EDITOR, just accept the data as is.",
- boolean: true,
- default: false
-
- option :help,
- short: "-h",
- long: "--help",
- description: "Show this help message.",
- on: :tail,
- boolean: true
-
- option :node_name,
- short: "-u USER",
- long: "--user USER",
- description: "#{ChefUtils::Dist::Server::PRODUCT} API client username."
-
- option :client_key,
- short: "-k KEY",
- long: "--key KEY",
- description: "#{ChefUtils::Dist::Server::PRODUCT} API client key.",
- proc: lambda { |path| File.expand_path(path, Dir.pwd) }
-
- option :chef_server_url,
- short: "-s URL",
- long: "--server-url URL",
- description: "#{ChefUtils::Dist::Server::PRODUCT} URL."
-
- option :yes,
- short: "-y",
- long: "--yes",
- description: "Say yes to all prompts for confirmation."
-
- option :defaults,
- long: "--defaults",
- description: "Accept default values for all questions."
-
- option :print_after,
- long: "--print-after",
- description: "Show the data after a destructive operation."
-
- option :format,
- short: "-F FORMAT",
- long: "--format FORMAT",
- description: "Which format to use for output.",
- in: %w{summary text json yaml pp},
- default: "summary"
-
- option :local_mode,
- short: "-z",
- long: "--local-mode",
- description: "Point knife commands at local repository instead of #{ChefUtils::Dist::Server::PRODUCT}.",
- boolean: true
-
- option :chef_zero_host,
- long: "--chef-zero-host HOST",
- description: "Host to start #{ChefUtils::Dist::Zero::PRODUCT} on."
-
- option :chef_zero_port,
- long: "--chef-zero-port PORT",
- description: "Port (or port range) to start #{ChefUtils::Dist::Zero::PRODUCT} on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
-
- option :listen,
- long: "--[no-]listen",
- description: "Whether a local mode (-z) server binds to a port.",
- boolean: false
-
- option :version,
- short: "-v",
- long: "--version",
- description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
- boolean: true,
- proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
- exit: 0
-
- option :fips,
- long: "--[no-]fips",
- description: "Enable FIPS mode.",
- boolean: true,
- default: nil
-
- option :profile,
- long: "--profile PROFILE",
- description: "The credentials profile to select."
-
- # Run knife
- def run
- ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
- Mixlib::Log::Formatter.show_time = false
- validate_and_parse_options
- quiet_traps
- Chef::Knife.run(ARGV, options)
- exit 0
- end
-
- private
-
- def quiet_traps
- trap("TERM") do
- exit 1
- end
-
- trap("INT") do
- exit 2
- end
- end
-
- def validate_and_parse_options
- # Checking ARGV validity *before* parse_options because parse_options
- # mangles ARGV in some situations
- if no_command_given?
- print_help_and_exit(1, NO_COMMAND_GIVEN)
- elsif no_subcommand_given?
- if want_help? || want_version?
- print_help_and_exit(0)
- else
- print_help_and_exit(2, NO_COMMAND_GIVEN)
- end
- end
- end
-
- def no_subcommand_given?
- ARGV[0] =~ /^-/
- end
-
- def no_command_given?
- ARGV.empty?
- end
-
- def want_help?
- ARGV[0] =~ /^(--help|-h)$/
- end
-
- def want_version?
- ARGV[0] =~ /^(--version|-v)$/
- end
-
- def print_help_and_exit(exitcode = 1, fatal_message = nil)
- Chef::Log.error(fatal_message) if fatal_message
-
- begin
- parse_options
- rescue OptionParser::InvalidOption => e
- puts "#{e}\n"
- end
-
- if want_help?
- puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{Chef::VERSION}"
- puts
- puts "Docs: #{ChefUtils::Dist::Org::KNIFE_DOCS}"
- puts "Patents: #{ChefUtils::Dist::Org::PATENTS}"
- puts
- end
-
- puts opt_parser
- puts
- Chef::Knife.list_commands
- exit exitcode
- end
-
-end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
deleted file mode 100644
index 8975556f75..0000000000
--- a/lib/chef/application/windows_service.rb
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Author:: Christopher Maier (<maier@lambda.local>)
-# Copyright:: Copyright (c) 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 "../../chef"
-require_relative "../monologger"
-require_relative "../application"
-require_relative "../client"
-require_relative "../config"
-require_relative "../handler/error_report"
-require_relative "../log"
-require_relative "../http"
-require "mixlib/cli" unless defined?(Mixlib::CLI)
-require "socket" unless defined?(Socket)
-require "uri" unless defined?(URI)
-require "win32/daemon"
-require_relative "../mixin/shell_out"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Application
- class WindowsService < ::Win32::Daemon
- include Mixlib::CLI
- include Chef::Mixin::ShellOut
-
- option :config_file,
- short: "-c CONFIG",
- long: "--config CONFIG",
- default: "#{Chef::Config.etc_chef_dir}/client.rb",
- description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs."
-
- option :log_location,
- short: "-L LOGLOCATION",
- long: "--logfile LOGLOCATION",
- description: "Set the log file location."
-
- option :splay,
- short: "-s SECONDS",
- long: "--splay SECONDS",
- description: "The splay time for running at intervals, in seconds.",
- proc: lambda { |s| s.to_i }
-
- option :interval,
- short: "-i SECONDS",
- long: "--interval SECONDS",
- description: "Set the number of seconds to wait between #{ChefUtils::Dist::Infra::PRODUCT} runs.",
- proc: lambda { |s| s.to_i }
-
- DEFAULT_LOG_LOCATION ||= "#{Chef::Config.c_chef_dir}/client.log".freeze
-
- def service_init
- @service_action_mutex = Mutex.new
- @service_signal = ConditionVariable.new
-
- reconfigure
- Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} Service initialized")
- end
-
- def service_main(*startup_parameters)
- # Chef::Config is initialized during service_init
- # Set the initial timeout to splay sleep time
- timeout = rand Chef::Config[:splay]
-
- while running?
- # Grab the service_action_mutex to make a chef-client run
- @service_action_mutex.synchronize do
-
- Chef::Log.info("Next #{ChefUtils::Dist::Infra::CLIENT} run will happen in #{timeout} seconds")
- @service_signal.wait(@service_action_mutex, timeout)
-
- # Continue only if service is RUNNING
- next if state != RUNNING
-
- # Reconfigure each time through to pick up any changes in the client file
- Chef::Log.info("Reconfiguring with startup parameters")
- reconfigure(startup_parameters)
- timeout = Chef::Config[:interval]
-
- # Honor splay sleep config
- timeout += rand Chef::Config[:splay]
-
- # run chef-client only if service is in RUNNING state
- next if state != RUNNING
-
- Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} service is starting a #{ChefUtils::Dist::Infra::CLIENT} run...")
- run_chef_client
- rescue SystemExit => e
- # Do not raise any of the errors here in order to
- # prevent service crash
- Chef::Log.error("#{e.class}: #{e}")
- rescue Exception => e
- Chef::Log.error("#{e.class}: #{e}")
-
- end
- end
-
- # Daemon class needs to have all the signal callbacks return
- # before service_main returns.
- Chef::Log.trace("Giving signal callbacks some time to exit...")
- sleep 1
- Chef::Log.trace("Exiting service...")
- end
-
- ################################################################################
- # Control Signal Callback Methods
- ################################################################################
-
- def service_stop
- run_warning_displayed = false
- Chef::Log.info("STOP request from operating system.")
- loop do
- # See if a run is in flight
- if @service_action_mutex.try_lock
- # Run is not in flight. Wake up service_main to exit.
- @service_signal.signal
- @service_action_mutex.unlock
- break
- else
- unless run_warning_displayed
- Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening on this system.")
- Chef::Log.info("Service will stop when run is completed.")
- run_warning_displayed = true
- end
-
- Chef::Log.trace("Waiting for #{ChefUtils::Dist::Infra::PRODUCT} run...")
- sleep 1
- end
- end
- Chef::Log.info("Service is stopping....")
- end
-
- def service_pause
- Chef::Log.info("PAUSE request from operating system.")
-
- # We don't need to wake up the service_main if it's waiting
- # since this is a PAUSE signal.
-
- if @service_action_mutex.locked?
- Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening.")
- Chef::Log.info("Service will pause once it's completed.")
- else
- Chef::Log.info("Service is pausing....")
- end
- end
-
- def service_resume
- # We don't need to wake up the service_main if it's waiting
- # since this is a RESUME signal.
-
- Chef::Log.info("RESUME signal received from the OS.")
- Chef::Log.info("Service is resuming....")
- end
-
- def service_shutdown
- Chef::Log.info("SHUTDOWN signal received from the OS.")
-
- # Treat shutdown similar to stop.
-
- service_stop
- end
-
- ################################################################################
- # Internal Methods
- ################################################################################
-
- private
-
- # Initializes Chef::Client instance and runs it
- def run_chef_client
- # The chef client will be started in a new process. We have used shell_out to start the chef-client.
- # The log_location and config_file of the parent process is passed to the new chef-client process.
- # We need to add the --no-fork, as by default it is set to fork=true.
-
- Chef::Log.info "Starting #{ChefUtils::Dist::Infra::CLIENT} in a new process"
- # Pass config params to the new process
- config_params = " --no-fork"
- config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil?
- # log_location might be an event logger and if so we cannot pass as a command argument
- # but shed no tears! If the logger is an event logger, it must have been configured
- # as such in the config file and chef-client will use that when no arg is passed here
- config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String)
-
- # Starts a new process and waits till the process exits
-
- result = shell_out(
- "#{ChefUtils::Dist::Infra::CLIENT}.bat #{config_params}",
- timeout: Chef::Config[:windows_service][:watchdog_timeout],
- logger: Chef::Log
- )
- Chef::Log.trace (result.stdout).to_s
- Chef::Log.trace (result.stderr).to_s
- rescue Mixlib::ShellOut::CommandTimeout => e
- Chef::Log.error "#{ChefUtils::Dist::Infra::CLIENT} timed out\n(#{e})"
- Chef::Log.error(<<-EOF)
- Your #{ChefUtils::Dist::Infra::CLIENT} run timed out. You can increase the time #{ChefUtils::Dist::Infra::CLIENT} is given
- to complete by configuring windows_service.watchdog_timeout in your client.rb.
- EOF
- rescue Mixlib::ShellOut::ShellCommandFailed => e
- Chef::Log.warn "Not able to start #{ChefUtils::Dist::Infra::CLIENT} in new process (#{e})"
- rescue => e
- Chef::Log.error e
- ensure
- # Once process exits, we log the current process' pid
- Chef::Log.info "Child process exited (pid: #{Process.pid})"
- end
-
- def apply_config(config_file_path)
- Chef::Config.from_file(config_file_path)
- Chef::Config.merge!(config)
- end
-
- # Lifted from Chef::Application, with addition of optional startup parameters
- # for playing nicely with Windows Services
- def reconfigure(startup_parameters = [])
- configure_chef startup_parameters
- configure_logging
-
- Chef::Config[:chef_server_url] = config[:chef_server_url] if config.key? :chef_server_url
- unless Chef::Config[:exception_handlers].any? { |h| Chef::Handler::ErrorReport === h }
- Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
- end
-
- Chef::Config[:interval] ||= 1800
- end
-
- # Lifted from application.rb
- # See application.rb for related comments.
-
- def configure_logging
- Chef::Log.init(MonoLogger.new(resolve_log_location))
- if want_additional_logger?
- configure_stdout_logger
- end
- Chef::Log.level = resolve_log_level
- end
-
- def configure_stdout_logger
- stdout_logger = MonoLogger.new(STDOUT)
- stdout_logger.formatter = Chef::Log.logger.formatter
- Chef::Log.loggers << stdout_logger
- end
-
- # Based on config and whether or not STDOUT is a tty, should we setup a
- # secondary logger for stdout?
- def want_additional_logger?
- ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize]
- end
-
- # Use of output formatters is assumed if `force_formatter` is set or if
- # `force_logger` is not set
- def using_output_formatter?
- Chef::Config[:force_formatter] || !Chef::Config[:force_logger]
- end
-
- def auto_log_level?
- Chef::Config[:log_level] == :auto
- end
-
- def resolve_log_location
- # STDOUT is the default log location, but makes no sense for a windows service
- Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location]
- end
-
- # if log_level is `:auto`, convert it to :warn (when using output formatter)
- # or :info (no output formatter). See also +using_output_formatter?+
- def resolve_log_level
- if auto_log_level?
- if using_output_formatter?
- :warn
- else
- :info
- end
- else
- Chef::Config[:log_level]
- end
- end
-
- def configure_chef(startup_parameters)
- # Bit of a hack ahead:
- # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX".
- # It is also possible to specify startup parameters separately, either via the Services manager
- # or by using the registry (I think).
-
- # In order to accommodate all possible sources of parameterization, we first parse any command line
- # arguments. We then parse any startup parameters. This works, because Mixlib::CLI reuses its internal
- # 'config' hash; thus, anything in startup parameters will override any command line parameters that
- # might be set via the service's binary_path_name
- #
- # All these parameters then get layered on top of those from Chef::Config
-
- parse_options # Operates on ARGV by default
- parse_options startup_parameters
-
- begin
- case config[:config_file]
- when %r{^(http|https)://}
- Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) }
- else
- ::File.open(config[:config_file]) { |f| apply_config(f.path) }
- end
- rescue Errno::ENOENT
- Chef::Log.warn("*****************************************")
- Chef::Log.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.")
- Chef::Log.warn("*****************************************")
-
- Chef::Config.merge!(config)
- rescue SocketError
- Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}")
- rescue Chef::Exceptions::ConfigurationError => error
- Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
- rescue Exception => error
- Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
- end
- end
-
- end
- end
-end
-
-# To run this file as a service, it must be called as a script from within
-# the Windows Service framework. In that case, kick off the main loop!
-if __FILE__ == $0
- Chef::Application::WindowsService.mainloop
-end
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
deleted file mode 100644
index 4f0de26411..0000000000
--- a/lib/chef/application/windows_service_manager.rb
+++ /dev/null
@@ -1,205 +0,0 @@
-#
-# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright (c) 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.
-#
-
-if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
- require "win32/service"
-end
-require_relative "../config"
-require "mixlib/cli" unless defined?(Mixlib::CLI)
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Application
- #
- # This class is used to create and manage a windows service.
- # Service should be created using Daemon class from
- # win32/service gem.
- # For an example see: Chef::Application::WindowsService
- #
- # Outside programs are expected to use this class to manage
- # windows services.
- #
- class WindowsServiceManager
- include Mixlib::CLI
-
- option :action,
- short: "-a ACTION",
- long: "--action ACTION",
- default: "status",
- description: "Action to carry out on #{ChefUtils::Dist::Infra::SHORT}-service (install, uninstall, status, start, stop, pause, or resume)."
-
- option :config_file,
- short: "-c CONFIG",
- long: "--config CONFIG",
- default: "#{ChefConfig::Config.c_chef_dir}/client.rb",
- description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs."
-
- option :log_location,
- short: "-L LOGLOCATION",
- long: "--logfile LOGLOCATION",
- description: "Set the log file location for #{ChefUtils::Dist::Infra::SHORT}-service."
-
- option :help,
- short: "-h",
- long: "--help",
- description: "Show this help message.",
- on: :tail,
- boolean: true,
- show_options: true,
- exit: 0
-
- option :version,
- short: "-v",
- long: "--version",
- description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.",
- boolean: true,
- proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" },
- exit: 0
-
- def initialize(service_options)
- # having to call super in initialize is the most annoying
- # anti-pattern :(
- super()
-
- raise ArgumentError, "Service definition is not provided" if service_options.nil?
-
- required_options = %i{service_name service_display_name service_description service_file_path}
-
- required_options.each do |req_option|
- unless service_options.key?(req_option)
- raise ArgumentError, "Service definition doesn't contain required option #{req_option}"
- end
- end
-
- @service_name = service_options[:service_name]
- @service_display_name = service_options[:service_display_name]
- @service_description = service_options[:service_description]
- @service_file_path = service_options[:service_file_path]
- @service_start_name = service_options[:run_as_user]
- @password = service_options[:run_as_password]
- @delayed_start = service_options[:delayed_start]
- @dependencies = service_options[:dependencies]
- end
-
- def run(params = ARGV)
- parse_options(params)
-
- case config[:action]
- when "install"
- if service_exists?
- puts "Service #{@service_name} already exists on the system."
- else
- ruby = File.join(RbConfig::CONFIG["bindir"], "ruby")
-
- opts = ""
- opts << " -c #{config[:config_file]}" if config[:config_file]
- opts << " -L #{config[:log_location]}" if config[:log_location]
-
- # Quote the full paths to deal with possible spaces in the path name.
- # Also ensure all forward slashes are backslashes
- cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
-
- ::Win32::Service.new(
- service_name: @service_name,
- display_name: @service_display_name,
- description: @service_description,
- # Prior to 0.8.5, win32-service creates interactive services by default,
- # and we don't want that, so we need to override the service type.
- service_type: ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
- start_type: ::Win32::Service::SERVICE_AUTO_START,
- binary_path_name: cmd,
- service_start_name: @service_start_name,
- password: @password,
- dependencies: @dependencies
- )
- unless @delayed_start.nil?
- ::Win32::Service.configure(
- service_name: @service_name,
- delayed_start: @delayed_start
- )
- end
- puts "Service '#{@service_name}' has successfully been installed."
- end
- when "status"
- if !service_exists?
- puts "Service #{@service_name} doesn't exist on the system."
- else
- puts "State of #{@service_name} service is: #{current_state}"
- end
- when "start"
- # TODO: allow override of startup parameters here?
- take_action("start", RUNNING)
- when "stop"
- take_action("stop", STOPPED)
- when "uninstall", "delete"
- take_action("stop", STOPPED)
- unless service_exists?
- puts "Service #{@service_name} doesn't exist on the system."
- else
- ::Win32::Service.delete(@service_name)
- puts "Service #{@service_name} deleted"
- end
- when "pause"
- take_action("pause", PAUSED)
- when "resume"
- take_action("resume", RUNNING)
- end
- end
-
- private
-
- # Just some state constants
- STOPPED = "stopped".freeze
- RUNNING = "running".freeze
- PAUSED = "paused".freeze
-
- def service_exists?
- ::Win32::Service.exists?(@service_name)
- end
-
- def take_action(action = nil, desired_state = nil)
- if service_exists?
- if current_state != desired_state
- ::Win32::Service.send(action, @service_name)
- wait_for_state(desired_state)
- puts "Service '#{@service_name}' is now '#{current_state}'."
- else
- puts "Service '#{@service_name}' is already '#{desired_state}'."
- end
- else
- puts "Cannot '#{action}' service '#{@service_name}'"
- puts "Service #{@service_name} doesn't exist on the system."
- end
- end
-
- def current_state
- ::Win32::Service.status(@service_name).current_state
- end
-
- # Helper method that waits for a status to change its state since state
- # changes aren't usually instantaneous.
- def wait_for_state(desired_state)
- while current_state != desired_state
- puts "One moment... #{current_state}"
- sleep 1
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/applications.rb b/lib/chef/applications.rb
index a30b765c77..8f7f418d3f 100644
--- a/lib/chef/applications.rb
+++ b/lib/chef/applications.rb
@@ -1,4 +1,3 @@
require_relative "application/client"
-require_relative "application/knife"
require_relative "application/solo"
require_relative "application/apply"
diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb
index 1e3aa137cd..16d9bd93f4 100644
--- a/lib/chef/chef_fs/command_line.rb
+++ b/lib/chef/chef_fs/command_line.rb
@@ -19,6 +19,9 @@
require_relative "file_system"
require_relative "file_system/exceptions"
require_relative "../util/diff"
+require "chef-utils/parallel_map" unless defined?(ChefUtils::ParallelMap)
+
+using ChefUtils::ParallelMap
class Chef
module ChefFS
@@ -140,7 +143,7 @@ class Chef
end
def self.diff(pattern, old_root, new_root, recurse_depth, get_content)
- Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.list_pairs(pattern, old_root, new_root)) do |old_entry, new_entry|
+ Chef::ChefFS::FileSystem.list_pairs(pattern, old_root, new_root).parallel_map do |old_entry, new_entry|
diff_entries(old_entry, new_entry, recurse_depth, get_content)
end.flatten(1)
end
@@ -153,7 +156,7 @@ class Chef
if recurse_depth == 0
[ [ :common_subdirectories, old_entry, new_entry ] ]
else
- Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry)) do |old_child, new_child|
+ Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry).parallel_map do |old_child, new_child|
Chef::ChefFS::CommandLine.diff_entries(old_child, new_child, recurse_depth ? recurse_depth - 1 : nil, get_content)
end.flatten(1)
end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 37e72f379b..7e7a58e312 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -255,7 +255,7 @@ class Chef
end
def self.regexp_escape_characters
- [ "[", '\\', "^", "$", ".", "|", "?", "*", "+", "(", ")", "{", "}" ]
+ [ "[", "\\", "^", "$", ".", "|", "?", "*", "+", "(", ")", "{", "}" ]
end
def self.pattern_to_regexp(pattern)
@@ -281,7 +281,7 @@ class Chef
exact = nil
regexp << "."
else
- if part[0, 1] == '\\' && part.length == 2
+ if part[0, 1] == "\\" && part.length == 2
# backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex)
exact << part[1, 1] unless exact.nil?
if regexp_escape_characters.include?(part[1, 1])
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
index aadfc9807c..5c4278c100 100644
--- a/lib/chef/chef_fs/file_system.rb
+++ b/lib/chef/chef_fs/file_system.rb
@@ -18,7 +18,9 @@
require_relative "path_utils"
require_relative "file_system/exceptions"
-require_relative "parallelizer"
+require "chef-utils/parallel_map" unless defined?(ChefUtils::ParallelMap)
+
+using ChefUtils::ParallelMap
class Chef
module ChefFS
@@ -70,8 +72,8 @@ class Chef
# Otherwise, go through all children and find any matches
elsif entry.dir?
- results = Parallelizer.parallelize(entry.children) { |child| Chef::ChefFS::FileSystem.list(child, pattern) }
- results.flatten(1).each(&block)
+ results = entry.children.parallel_map { |child| Chef::ChefFS::FileSystem.list(child, pattern) }
+ results.flat_each(&block)
end
end
end
@@ -138,7 +140,7 @@ class Chef
def self.copy_to(pattern, src_root, dest_root, recurse_depth, options, ui = nil, format_path = nil)
found_result = false
error = false
- parallel_do(list_pairs(pattern, src_root, dest_root)) do |src, dest|
+ list_pairs(pattern, src_root, dest_root).parallel_each do |src, dest|
found_result = true
new_dest_parent = get_or_create_parent(dest, options, ui, format_path)
child_error = copy_entries(src, dest, new_dest_parent, recurse_depth, options, ui, format_path)
@@ -292,7 +294,7 @@ class Chef
end
end
else
- ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui
+ ui.output("Not deleting extra entry #{dest_path} (purge is off)") if ui
end
end
@@ -319,7 +321,7 @@ class Chef
end
# Directory creation is recursive.
if recurse_depth != 0
- parallel_do(src_entry.children) do |src_child|
+ src_entry.children.parallel_each do |src_child|
new_dest_child = new_dest_dir.child(src_child.name)
child_error = copy_entries(src_child, new_dest_child, new_dest_dir, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path)
error ||= child_error
@@ -356,7 +358,7 @@ class Chef
if dest_entry.dir?
# If both are directories, recurse into their children
if recurse_depth != 0
- parallel_do(child_pairs(src_entry, dest_entry)) do |src_child, dest_child|
+ child_pairs(src_entry, dest_entry).parallel_each do |src_child, dest_child|
child_error = copy_entries(src_child, dest_child, dest_entry, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path)
error ||= child_error
end
@@ -423,9 +425,6 @@ class Chef
parent
end
- def parallel_do(enum, options = {}, &block)
- Chef::ChefFS::Parallelizer.parallel_do(enum, options, &block)
- end
end
end
end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
deleted file mode 100644
index ba993beee4..0000000000
--- a/lib/chef/chef_fs/knife.rb
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require "pathname" unless defined?(Pathname)
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- module ChefFS
- class Knife < Chef::Knife
- # Workaround for CHEF-3932
- def self.deps
- super do
- require_relative "../config"
- require_relative "parallelizer"
- require_relative "config"
- require_relative "file_pattern"
- require_relative "path_utils"
- yield
- end
- end
-
- def self.inherited(c)
- super
-
- # Ensure we always get to do our includes, whether subclass calls deps or not
- c.deps do
- end
- end
-
- option :repo_mode,
- long: "--repo-mode MODE",
- description: "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything"
-
- option :chef_repo_path,
- long: "--chef-repo-path PATH",
- description: "Overrides the location of #{ChefUtils::Dist::Infra::PRODUCT} repo. Default is specified by chef_repo_path in the config"
-
- option :concurrency,
- long: "--concurrency THREADS",
- description: "Maximum number of simultaneous requests to send (default: 10)"
-
- def configure_chef
- super
- Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
- Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency]
-
- # --chef-repo-path forcibly overrides all other paths
- if config[:chef_repo_path]
- Chef::Config[:chef_repo_path] = config[:chef_repo_path]
- Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
- Chef::Config.delete("#{variable_name}_path".to_sym)
- end
- end
-
- @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config, ui)
-
- Chef::ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1
- end
-
- def chef_fs
- @chef_fs_config.chef_fs
- end
-
- def create_chef_fs
- @chef_fs_config.create_chef_fs
- end
-
- def local_fs
- @chef_fs_config.local_fs
- end
-
- def create_local_fs
- @chef_fs_config.create_local_fs
- end
-
- def pattern_args
- @pattern_args ||= pattern_args_from(name_args)
- end
-
- def pattern_args_from(args)
- args.map { |arg| pattern_arg_from(arg) }
- end
-
- def pattern_arg_from(arg)
- inferred_path = nil
- if Chef::ChefFS::PathUtils.is_absolute?(arg)
- # We should be able to use this as-is - but the user might have incorrectly provided
- # us with a path that is based off of the OS root path instead of the Chef-FS root.
- # Do a quick and dirty sanity check.
- if possible_server_path = @chef_fs_config.server_path(arg)
- ui.warn("The absolute path provided is suspicious: #{arg}")
- ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
- ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
- end
- # Use the original path because we can't be sure.
- inferred_path = arg
- elsif arg[0, 1] == "~"
- # Let's be nice and fix it if possible - but warn the user.
- ui.warn("A path relative to a user home directory has been provided: #{arg}")
- ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
- inferred_path = @chef_fs_config.server_path(arg)
- ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
- elsif Pathname.new(arg).absolute?
- # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
- # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
- ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
- ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
- inferred_path = @chef_fs_config.server_path(arg)
- ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
- elsif @chef_fs_config.base_path.nil?
- # These are all relative paths. We can't resolve and root paths unless we are in the
- # chef repo.
- ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
- ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
- exit(1)
- else
- inferred_path = Chef::ChefFS::PathUtils.join(@chef_fs_config.base_path, arg)
- end
- Chef::ChefFS::FilePattern.new(inferred_path)
- end
-
- def format_path(entry)
- @chef_fs_config.format_path(entry)
- end
-
- def parallelize(inputs, options = {}, &block)
- Chef::ChefFS::Parallelizer.parallelize(inputs, options, &block)
- end
-
- def discover_repo_dir(dir)
- %w{.chef cookbooks data_bags environments roles}.each do |subdir|
- return dir if File.directory?(File.join(dir, subdir))
- end
- # If this isn't it, check the parent
- parent = File.dirname(dir)
- if parent && parent != dir
- discover_repo_dir(parent)
- else
- nil
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/parallelizer.rb b/lib/chef/chef_fs/parallelizer.rb
deleted file mode 100644
index c4d17a842d..0000000000
--- a/lib/chef/chef_fs/parallelizer.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-require_relative "parallelizer/parallel_enumerable"
-
-class Chef
- module ChefFS
- # Tries to balance several guarantees, in order of priority:
- # - don't get deadlocked
- # - provide results in desired order
- # - provide results as soon as they are available
- # - process input as soon as possible
- class Parallelizer
- @@parallelizer = nil
- @@threads = 0
-
- def self.threads=(value)
- @@threads = value
- @@parallelizer.resize(value) if @@parallelizer
- end
-
- def self.parallelizer
- @@parallelizer ||= Parallelizer.new(@@threads)
- end
-
- def self.parallelize(enumerable, options = {}, &block)
- parallelizer.parallelize(enumerable, options, &block)
- end
-
- def self.parallel_do(enumerable, options = {}, &block)
- parallelizer.parallel_do(enumerable, options, &block)
- end
-
- def initialize(num_threads)
- @tasks = Queue.new
- @threads = []
- @stop_thread = {}
- resize(num_threads)
- end
-
- def num_threads
- @threads.size
- end
-
- def parallelize(enumerable, options = {}, &block)
- ParallelEnumerable.new(@tasks, enumerable, options, &block)
- end
-
- def parallel_do(enumerable, options = {}, &block)
- ParallelEnumerable.new(@tasks, enumerable, options.merge(ordered: false), &block).wait
- end
-
- def stop(wait = true, timeout = nil)
- resize(0, wait, timeout)
- end
-
- def resize(to_threads, wait = true, timeout = nil)
- if to_threads < num_threads
- threads_to_stop = @threads[to_threads..num_threads - 1]
- @threads = @threads.slice(0, to_threads)
- threads_to_stop.each do |thread|
- @stop_thread[thread] = true
- end
-
- if wait
- start_time = Time.now
- threads_to_stop.each do |thread|
- thread_timeout = timeout ? timeout - (Time.now - start_time) : nil
- thread.join(thread_timeout)
- end
- end
-
- else
- num_threads.upto(to_threads - 1) do |i|
- @threads[i] = Thread.new(&method(:worker_loop))
- end
- end
- end
-
- def kill
- @threads.each do |thread|
- Thread.kill(thread)
- @stop_thread.delete(thread)
- end
- @threads = []
- end
-
- private
-
- def worker_loop
- until @stop_thread[Thread.current]
- begin
- task = @tasks.pop
- task.call
- rescue
- puts "ERROR #{$!}"
- puts $!.backtrace
- end
- end
- ensure
- @stop_thread.delete(Thread.current)
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/parallelizer/flatten_enumerable.rb b/lib/chef/chef_fs/parallelizer/flatten_enumerable.rb
deleted file mode 100644
index 84db2c2053..0000000000
--- a/lib/chef/chef_fs/parallelizer/flatten_enumerable.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-class Chef
- module ChefFS
- class Parallelizer
- class FlattenEnumerable
- include Enumerable
-
- def initialize(enum, levels = nil)
- @enum = enum
- @levels = levels
- end
-
- attr_reader :enum
- attr_reader :levels
-
- def each(&block)
- enum.each do |value|
- flatten(value, levels, &block)
- end
- end
-
- private
-
- def flatten(value, levels, &block)
- if levels != 0 && value.respond_to?(:each) && !value.is_a?(String)
- value.each do |child|
- flatten(child, levels.nil? ? levels : levels - 1, &block)
- end
- else
- yield(value)
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
deleted file mode 100644
index 5fafc5c88f..0000000000
--- a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
+++ /dev/null
@@ -1,278 +0,0 @@
-require_relative "flatten_enumerable"
-
-class Chef
- module ChefFS
- class Parallelizer
- class ParallelEnumerable
- include Enumerable
-
- # options:
- # :ordered [true|false] - whether the output should stay in the same order
- # as the input (even though it may not actually be processed in that
- # order). Default: true
- # :stop_on_exception [true|false] - if true, when an exception occurs in either
- # input or output, we wait for any outstanding processing to complete,
- # but will not process any new inputs. Default: false
- # :main_thread_processing [true|false] - whether the main thread pulling
- # on each() is allowed to process inputs. Default: true
- # NOTE: If you set this to false, parallelizer.kill will stop each()
- # in its tracks, so you need to know for sure that won't happen.
- def initialize(parent_task_queue, input_enumerable, options = {}, &block)
- @parent_task_queue = parent_task_queue
- @input_enumerable = input_enumerable
- @options = options
- @block = block
-
- @unconsumed_input = Queue.new
- @in_process = {}
- @unconsumed_output = Queue.new
- end
-
- attr_reader :parent_task_queue
- attr_reader :input_enumerable
- attr_reader :options
- attr_reader :block
-
- def each
- each_with_input do |output, index, input, type|
- yield output
- end
- end
-
- def each_with_index
- each_with_input do |output, index, input|
- yield output, index
- end
- end
-
- def each_with_input
- exception = nil
- each_with_exceptions do |output, index, input, type|
- if type == :exception
- if @options[:ordered] == false
- exception ||= output
- else
- raise output
- end
- else
- yield output, index, input
- end
- end
- raise exception if exception
- end
-
- def each_with_exceptions(&block)
- if @options[:ordered] == false
- each_with_exceptions_unordered(&block)
- else
- each_with_exceptions_ordered(&block)
- end
- end
-
- def wait
- exception = nil
- each_with_exceptions_unordered do |output, index, input, type|
- exception ||= output if type == :exception
- end
- raise exception if exception
- end
-
- # Enumerable methods
- def restricted_copy(enumerable)
- ParallelEnumerable.new(@parent_task_queue, enumerable, @options, &@block)
- end
-
- alias :original_count :count
-
- def count(*args, &block)
- if args.size == 0 && block.nil?
- @input_enumerable.count
- else
- original_count(*args, &block)
- end
- end
-
- def first(n = nil)
- if n
- restricted_copy(@input_enumerable.first(n)).to_a
- else
- first(1)[0]
- end
- end
-
- def drop(n)
- restricted_copy(@input_enumerable.drop(n)).to_a
- end
-
- def flatten(levels = nil)
- FlattenEnumerable.new(self, levels)
- end
-
- def take(n)
- restricted_copy(@input_enumerable.take(n)).to_a
- end
-
- if Enumerable.method_defined?(:lazy)
- class RestrictedLazy
- def initialize(parallel_enumerable, actual_lazy)
- @parallel_enumerable = parallel_enumerable
- @actual_lazy = actual_lazy
- end
-
- def drop(*args, &block)
- input = @parallel_enumerable.input_enumerable.lazy.drop(*args, &block)
- @parallel_enumerable.restricted_copy(input)
- end
-
- def take(*args, &block)
- input = @parallel_enumerable.input_enumerable.lazy.take(*args, &block)
- @parallel_enumerable.restricted_copy(input)
- end
-
- def method_missing(method, *args, &block)
- @actual_lazy.send(:method, *args, &block)
- end
- end
-
- alias :original_lazy :lazy
-
- def lazy
- RestrictedLazy.new(self, original_lazy)
- end
- end
-
- private
-
- def each_with_exceptions_unordered
- if @each_running
- raise "each() called on parallel enumerable twice simultaneously! Bad mojo"
- end
-
- @each_running = true
- begin
- # Grab all the inputs, yielding any responses during enumeration
- # in case the enumeration itself takes time
- begin
- @input_enumerable.each_with_index do |input, index|
- @unconsumed_input.push([ input, index ])
- @parent_task_queue.push(method(:process_one))
-
- stop_processing_input = false
- until @unconsumed_output.empty?
- output, index, input, type = @unconsumed_output.pop
- yield output, index, input, type
- if type == :exception && @options[:stop_on_exception]
- stop_processing_input = true
- break
- end
- end
-
- if stop_processing_input
- break
- end
- end
- rescue
- # We still want to wait for the rest of the outputs to process
- @unconsumed_output.push([$!, nil, nil, :exception])
- if @options[:stop_on_exception]
- @unconsumed_input.clear
- end
- end
-
- until finished?
- # yield thread to others (for 1.8.7)
- if @unconsumed_output.empty?
- sleep(0.01)
- end
-
- yield @unconsumed_output.pop until @unconsumed_output.empty?
-
- # If no one is working on our tasks and we're allowed to
- # work on them in the main thread, process an input to
- # move things forward.
- if @in_process.size == 0 && !(@options[:main_thread_processing] == false)
- process_one
- end
- end
- rescue
- # If we exited early, perhaps due to any? finding a result, we want
- # to make sure and throw away any extra results (gracefully) so that
- # the next enumerator can start over.
- unless finished?
- stop
- end
- raise
- ensure
- @each_running = false
- end
- end
-
- def each_with_exceptions_ordered
- next_to_yield = 0
- unconsumed = {}
- each_with_exceptions_unordered do |output, index, input, type|
- unconsumed[index] = [ output, input, type ]
- while unconsumed[next_to_yield]
- input_output = unconsumed.delete(next_to_yield)
- yield input_output[0], next_to_yield, input_output[1], input_output[2]
- next_to_yield += 1
- end
- end
- input_exception = unconsumed.delete(nil)
- if input_exception
- yield input_exception[0], next_to_yield, input_exception[1], input_exception[2]
- end
- end
-
- def stop
- @unconsumed_input.clear
- sleep(0.05) while @in_process.size > 0
- @unconsumed_output.clear
- end
-
- #
- # This is thread safe only if called from the main thread pulling on each().
- # The order of these checks is important, as well, to be thread safe.
- # 1. If @unconsumed_input.empty? is true, then we will never have any more
- # work legitimately picked up.
- # 2. If @in_process == 0, then there is no work in process, and because of when unconsumed_input is empty, it will never go back up, because
- # this is called after the input enumerator is finished. Note that switching #2 and #1
- # could cause a race, because in_process is incremented *before* consuming input.
- # 3. If @unconsumed_output.empty? is true, then we are done with outputs.
- # Thus, 1+2 means no more output will ever show up, and 3 means we've passed all
- # existing outputs to the user.
- #
- def finished?
- @unconsumed_input.empty? && @in_process.size == 0 && @unconsumed_output.empty?
- end
-
- def process_one
- @in_process[Thread.current] = true
- begin
- begin
- input, index = @unconsumed_input.pop(true)
- process_input(input, index)
- rescue ThreadError
- end
- ensure
- @in_process.delete(Thread.current)
- end
- end
-
- def process_input(input, index)
- begin
- output = @block.call(input)
- @unconsumed_output.push([ output, index, input, :result ])
- rescue StandardError, ScriptError
- if @options[:stop_on_exception]
- @unconsumed_input.clear
- end
- @unconsumed_output.push([ $!, index, input, :exception ])
- end
-
- index
- end
- end
- end
- end
-end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 094b59fc35..3a312701d2 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -858,11 +858,17 @@ class Chef
def profiling_prereqs!
require "ruby-prof"
- rescue LoadError
- raise "You must have the ruby-prof gem installed in order to use --profile-ruby"
+ rescue LoadError => e
+ raise "You must have the ruby-prof gem installed in order to use --profile-ruby: #{e.message}"
end
def start_profiling
+ if Chef::Config[:slow_report]
+ require_relative "handler/slow_report"
+
+ Chef::Config.report_handlers << Chef::Handler::SlowReport.new(Chef::Config[:slow_report])
+ end
+
return unless Chef::Config[:profile_ruby]
profiling_prereqs!
diff --git a/lib/chef/compliance/default_attributes.rb b/lib/chef/compliance/default_attributes.rb
index eb50c3a5e9..9196944b92 100644
--- a/lib/chef/compliance/default_attributes.rb
+++ b/lib/chef/compliance/default_attributes.rb
@@ -1,5 +1,5 @@
# Author:: Stephan Renatus <srenatus@chef.io>
-# Copyright:: (c) 2016-2019, Chef Software Inc. <legal@chef.io>
+# Copyright:: Copyright (c) Chef Software Inc. <legal@chef.io>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,8 +27,8 @@ class Chef
# Controls what is done with the resulting report after the Chef InSpec run.
# Accepts a single string value or an array of multiple values.
- # Accepted values: 'chef-server-automate', 'chef-automate', 'json-file', 'audit-enforcer'
- "reporter" => "json-file",
+ # Accepted values: 'chef-server-automate', 'chef-automate', 'json-file', 'audit-enforcer', 'cli'
+ "reporter" => %w{json-file cli},
# Controls if Chef InSpec profiles should be fetched from Chef Automate or Chef Infra Server
# in addition to the default fetch locations provided by Chef Inspec.
@@ -38,11 +38,12 @@ class Chef
# Allow for connections to HTTPS endpoints using self-signed ssl certificates.
"insecure" => nil,
- # Controls verbosity of Chef InSpec runner.
+ # Controls verbosity of Chef InSpec runner. See less output when true.
"quiet" => true,
# Chef Inspec Compliance profiles to be used for scan of node.
- # See README.md for details
+ # See Compliance Phase documentation for further details:
+ # https://docs.chef.io/chef_compliance_phase/#compliance-phase-configuration
"profiles" => {},
# Extra inputs passed to Chef InSpec to allow finer-grained control over behavior.
@@ -83,7 +84,15 @@ class Chef
# The array of results per control will be truncated at this limit to avoid large reports that cannot be
# processed by Chef Automate. A summary of removed results will be sent with each impacted control.
- "control_results_limit" => 50
+ "control_results_limit" => 50,
+
+ # If enabled, a hash representation of the Chef Infra node object will be sent to Chef InSpec in an input
+ # named `chef_node`.
+ "chef_node_attribute_enabled" => false,
+
+ # Should the built-in compliance phase run. True and false force the behavior. Nil does magic based on if you have
+ # profiles defined but do not have the audit cookbook enabled.
+ "compliance_phase" => false
)
end
end
diff --git a/lib/chef/compliance/fetcher/automate.rb b/lib/chef/compliance/fetcher/automate.rb
index c86da331c2..47dc7f2e6a 100644
--- a/lib/chef/compliance/fetcher/automate.rb
+++ b/lib/chef/compliance/fetcher/automate.rb
@@ -7,8 +7,8 @@ class Chef
class Automate < ::InspecPlugins::Compliance::Fetcher
name "chef-automate"
- # it positions itself before `compliance` fetcher
- # only load it, if you want to use audit cookbook in Chef Solo with Chef Automate
+ # Positions this fetcher before Chef InSpec's `compliance` fetcher.
+ # Only load this file if you want to use Compliance Phase in Chef Solo with Chef Automate.
priority 502
CONFIG = {
@@ -32,12 +32,12 @@ class Chef
profile_fetch_url = target[:url]
else
# verifies that the target e.g base/ssh exists
- base_path = "/compliance/profiles/#{uri.host}#{uri.path}"
-
+ profile = sanitize_profile_name(uri)
+ owner, id = profile.split("/")
profile_path = if target.respond_to?(:key?) && target.key?(:version)
- "#{base_path}/version/#{target[:version]}/tar"
+ "/compliance/profiles/#{owner}/#{id}/version/#{target[:version]}/tar"
else
- "#{base_path}/tar"
+ "/compliance/profiles/#{owner}/#{id}/tar"
end
url = URI(Chef::Config[:data_collector][:server_url])
@@ -46,13 +46,6 @@ class Chef
config["token"] = Chef::Config[:data_collector][:token]
- if config["token"].nil?
- raise Inspec::FetcherFailure,
- "No data-collector token set, which is required by the chef-automate fetcher. " \
- "Set the `data_collector.token` configuration parameter in your client.rb " \
- 'or use the "chef-server-automate" reporter which does not require any ' \
- "data-collector settings and uses #{ChefUtils::Dist::Server::PRODUCT} to fetch profiles."
- end
end
new(profile_fetch_url, config)
@@ -60,6 +53,17 @@ class Chef
nil
end
+ # returns a parsed url for `admin/profile` or `compliance://admin/profile`
+ # TODO: remove in future, copied from inspec to support older versions of inspec
+ def self.sanitize_profile_name(profile)
+ uri = if URI(profile).scheme == "compliance"
+ URI(profile)
+ else
+ URI("compliance://#{profile}")
+ end
+ uri.to_s.sub(%r{^compliance:\/\/}, "")
+ end
+
def to_s
"#{ChefUtils::Dist::Automate::PRODUCT} for #{ChefUtils::Dist::Solo::PRODUCT} Fetcher"
end
diff --git a/lib/chef/compliance/fetcher/chef_server.rb b/lib/chef/compliance/fetcher/chef_server.rb
index db56aa82a7..96a2213b69 100644
--- a/lib/chef/compliance/fetcher/chef_server.rb
+++ b/lib/chef/compliance/fetcher/chef_server.rb
@@ -95,11 +95,11 @@ class Chef
def handle_http_error_code(code)
case code
when /401|403/
- Chef::Log.error "Auth issue: see audit cookbook TROUBLESHOOTING.md"
+ Chef::Log.error "Auth issue: see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting)."
when /404/
Chef::Log.error "Object does not exist on remote server."
when /413/
- Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see audit cookbook TROUBLESHOOTING.md OR https://docs.chef.io/config_rb_server.html"
+ Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting) or the Chef Infra Server configuration documentation (https://docs.chef.io/server/config_rb_server/)"
when /429/
Chef::Log.error "This error typically means the data sent was larger than #{ChefUtils::Dist::Automate::PRODUCT}'s limit (4 MB). Run InSpec locally to identify any controls producing large diffs."
end
diff --git a/lib/chef/compliance/reporter/automate.rb b/lib/chef/compliance/reporter/automate.rb
index ed5c4837d0..c40631771d 100644
--- a/lib/chef/compliance/reporter/automate.rb
+++ b/lib/chef/compliance/reporter/automate.rb
@@ -28,19 +28,28 @@ class Chef
@token = Chef::Config[:data_collector][:token]
end
- # Method used in order to send the inspec report to the data_collector server
- def send_report(report)
- unless @entity_uuid && @run_id
- Chef::Log.error "entity_uuid(#{@entity_uuid}) or run_id(#{@run_id}) can't be nil, not sending report to #{ChefUtils::Dist::Automate::PRODUCT}"
- return false
+ def validate_config!
+ unless @entity_uuid
+ # TODO - this is a weird leakage of naming from the parent class
+ # but entity_uuid is never an attribute that the user can see;
+ # it is sourced from chef_guid, which we don't technically know about in this class -
+ # but telling the operator about a missing chef_guid is more helpful than telling
+ # them about a missing field they've never heard of. Better would be a dock link
+ # that described how to fix this situation.
+ raise "CMPL004: automate_reporter: chef_guid is not available and must be provided. Aborting because we cannot report the scan."
+ end
+
+ unless @run_id
+ raise "CMPL005: automate_reporter: run_id is not available, aborting because we cannot report the scan."
end
unless @url && @token
- Chef::Log.warn "data_collector.token and data_collector.server_url must be defined in client.rb!"
- Chef::Log.warn "Further information: https://github.com/chef-cookbooks/audit#direct-reporting-to-chef-automate"
- return false
+ raise "CMPL006: data_collector.token and data_collector.server_url must be configured in client.rb! Further information: https://docs.chef.io/automate/data_collection/#configure-your-chef-infra-client-to-send-data-to-chef-automate-without-chef-infra-server"
end
+ end
+ # Method used in order to send the inspec report to the data_collector server
+ def send_report(report)
headers = {
"Content-Type" => "application/json",
"x-data-collector-auth" => "version=1.0",
diff --git a/lib/chef/compliance/reporter/chef_server_automate.rb b/lib/chef/compliance/reporter/chef_server_automate.rb
index be59a4cf69..569fb22426 100644
--- a/lib/chef/compliance/reporter/chef_server_automate.rb
+++ b/lib/chef/compliance/reporter/chef_server_automate.rb
@@ -7,6 +7,8 @@ class Chef
# Used to send inspec reports to Chef Automate server via Chef Server
#
class ChefServerAutomate < Chef::Compliance::Reporter::Automate
+ attr_reader :url
+
def initialize(opts)
@entity_uuid = opts[:entity_uuid]
@run_id = opts[:run_id]
@@ -28,11 +30,6 @@ class Chef
end
def send_report(report)
- unless @entity_uuid && @run_id
- Chef::Log.error "entity_uuid(#{@entity_uuid}) or run_id(#{@run_id}) can't be nil, not sending report to #{ChefUtils::Dist::Automate::PRODUCT}"
- return false
- end
-
automate_report = truncate_controls_results(enriched_report(report), @control_results_limit)
report_size = Chef::JSONCompat.to_json(automate_report, validate_utf8: false).bytesize
@@ -49,6 +46,16 @@ class Chef
false
end
+ def validate_config!
+ unless @entity_uuid
+ raise "CMPL007: chef_server_automate reporter: chef_guid is not available and must be provided. Aborting because we cannot report the scan"
+ end
+
+ unless @run_id
+ raise "CMPL008: chef_server_automate reporter: run_id is not available, aborting because we cannot report the scan."
+ end
+ end
+
def http_client
config = if @insecure
Chef::Config.merge(ssl_verify_mode: :verify_none)
@@ -74,11 +81,11 @@ class Chef
def handle_http_error_code(code)
case code
when /401|403/
- Chef::Log.error "Auth issue: see audit cookbook TROUBLESHOOTING.md"
+ Chef::Log.error "Auth issue: see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting)."
when /404/
Chef::Log.error "Object does not exist on remote server."
when /413/
- Chef::Log.error "You most likely hit the erchef request size in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see audit cookbook TROUBLESHOOTING.md OR https://docs.chef.io/config_rb_server.html"
+ Chef::Log.error "You most likely hit the request size limit in #{ChefUtils::Dist::Server::PRODUCT} that defaults to ~2MB. To increase this limit see the Compliance Phase troubleshooting documentation (http://docs.chef.io/chef_compliance_phase/#troubleshooting) or the Chef Infra Server configuration documentation (https://docs.chef.io/server/config_rb_server/)"
when /429/
Chef::Log.error "This error typically means the data sent was larger than #{ChefUtils::Dist::Automate::PRODUCT}'s limit (4 MB). Run InSpec locally to identify any controls producing large diffs."
end
diff --git a/lib/chef/compliance/reporter/cli.rb b/lib/chef/compliance/reporter/cli.rb
new file mode 100644
index 0000000000..7061e5751c
--- /dev/null
+++ b/lib/chef/compliance/reporter/cli.rb
@@ -0,0 +1,77 @@
+class Chef
+ module Compliance
+ module Reporter
+ class Cli
+ def send_report(report)
+ # iterate over each profile and control
+ output = ["\nCompliance report:"]
+ report[:profiles].each do |profile|
+ next if profile[:controls].nil?
+
+ output << " * #{profile[:title]}"
+ profile[:controls].each do |control|
+ next if control[:results].nil?
+
+ output << "#{" " * 6}#{control[:title]}"
+ control[:results].each do |result|
+ output << format_result(result)
+ end
+ end
+ end
+ output << "\n"
+ puts output.join("\n")
+ end
+
+ def validate_config!
+ true
+ end
+
+ private
+
+ # pastel.decorate is a lightweight replacement for highline.color
+ def pastel
+ @pastel ||= begin
+ require "pastel" unless defined?(Pastel)
+ Pastel.new
+ end
+ end
+
+ def format_result(result)
+ output = []
+ found = false
+ if result[:status] == "failed"
+ if result[:code_desc]
+ found = true
+ output << pastel.red("#{" " * 9}- #{result[:code_desc]}")
+ end
+ if result[:message]
+ if found
+ result[:message].split(/\n/).reject(&:empty?).each do |m|
+ output << pastel.red("#{" " * 12}#{m}")
+ end
+ else
+ result[:message].split(/\n/).reject(&:empty?).each do |m|
+ output << pastel.red("#{" " * 9}#{m}")
+ end
+ end
+ found = true
+ end
+ unless found
+ output << pastel.red("#{" " * 9}- #{result[:status]}")
+ end
+ else
+ found = false
+ if result[:code_desc]
+ found = true
+ output << pastel.green("#{" " * 9}+ #{result[:code_desc]}")
+ end
+ unless found
+ output << pastel.green("#{" " * 9}+ #{result[:status]}")
+ end
+ end
+ output
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/compliance/reporter/compliance_enforcer.rb b/lib/chef/compliance/reporter/compliance_enforcer.rb
index 1c63e43b28..47b3a4d2df 100644
--- a/lib/chef/compliance/reporter/compliance_enforcer.rb
+++ b/lib/chef/compliance/reporter/compliance_enforcer.rb
@@ -14,6 +14,10 @@ class Chef
end
true
end
+
+ def validate_config!
+ true
+ end
end
end
end
diff --git a/lib/chef/compliance/reporter/json_file.rb b/lib/chef/compliance/reporter/json_file.rb
index 471d9f64b1..69091e3c7b 100644
--- a/lib/chef/compliance/reporter/json_file.rb
+++ b/lib/chef/compliance/reporter/json_file.rb
@@ -1,4 +1,5 @@
require_relative "../../json_compat"
+require_relative "../../log"
class Chef
module Compliance
@@ -9,10 +10,16 @@ class Chef
end
def send_report(report)
+ Chef::Log.info "Writing compliance report to #{@path}"
FileUtils.mkdir_p(File.dirname(@path), mode: 0700)
-
File.write(@path, Chef::JSONCompat.to_json(report))
end
+
+ def validate_config!
+ if @path.nil? || @path.class != String || @path.empty?
+ raise "CMPL009: json_file reporter: node['audit']['json_file']['location'] must contain a file path"
+ end
+ end
end
end
end
diff --git a/lib/chef/compliance/runner.rb b/lib/chef/compliance/runner.rb
index 677349df3e..ec68fe141a 100644
--- a/lib/chef/compliance/runner.rb
+++ b/lib/chef/compliance/runner.rb
@@ -1,27 +1,35 @@
autoload :Inspec, "inspec"
require_relative "default_attributes"
-require_relative "reporter/automate"
-require_relative "reporter/chef_server_automate"
-require_relative "reporter/compliance_enforcer"
-require_relative "reporter/json_file"
class Chef
module Compliance
class Runner < EventDispatch::Base
extend Forwardable
- attr_accessor :run_id, :recipes
+ SUPPORTED_REPORTERS = %w{chef-automate chef-server-automate json-file audit-enforcer cli}.freeze
+ SUPPORTED_FETCHERS = %w{chef-automate chef-server}.freeze
+
+ attr_accessor :run_id
attr_reader :node
def_delegators :node, :logger
def enabled?
- audit_cookbook_present = recipes.include?("audit::default")
+ return false if @node.nil?
+
+ # Did we parse the libraries file from the audit cookbook? This class dates back to when Chef Automate was
+ # renamed from Chef Visibility in 2017, so should capture all modern versions of the audit cookbook.
+ audit_cookbook_present = defined?(::Reporter::ChefAutomate)
- logger.info("#{self.class}##{__method__}: #{Inspec::Dist::PRODUCT_NAME} profiles? #{inspec_profiles.any?}")
- logger.info("#{self.class}##{__method__}: audit cookbook? #{audit_cookbook_present}")
+ logger.debug("#{self.class}##{__method__}: #{Inspec::Dist::PRODUCT_NAME} profiles? #{inspec_profiles.any?}")
+ logger.debug("#{self.class}##{__method__}: audit cookbook? #{audit_cookbook_present}")
+ logger.debug("#{self.class}##{__method__}: compliance phase attr? #{node["audit"]["compliance_phase"]}")
- inspec_profiles.any? && !audit_cookbook_present
+ if node["audit"]["compliance_phase"].nil?
+ inspec_profiles.any? && !audit_cookbook_present
+ else
+ node["audit"]["compliance_phase"]
+ end
end
def node=(node)
@@ -37,22 +45,30 @@ class Chef
self.run_id = run_status.run_id
end
- def run_list_expanded(run_list_expansion)
- self.recipes = run_list_expansion.recipes
+ def converge_start(run_context)
+ # With all attributes - including cookbook - loaded, we now have enough data to validate
+ # configuration. Because the converge is best coupled with the associated compliance run, these validations
+ # will raise (and abort the converge) if the compliance phase configuration is incorrect/will
+ # prevent compliance phase from completing and submitting its report to all configured reporters.
+ # can abort the converge if the compliance phase configuration (node attributes and client config)
+ load_and_validate!
end
def run_completed(_node, _run_status)
return unless enabled?
- logger.info("#{self.class}##{__method__}: enabling Compliance Phase")
+ logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
report
end
def run_failed(_exception, _run_status)
- return unless enabled?
+ # If the run has failed because our own validation of compliance
+ # phase configuration has failed, we don't want to submit a report
+ # because we're still not configured correctly.
+ return unless enabled? && @validation_passed
- logger.info("#{self.class}##{__method__}: enabling Compliance Phase")
+ logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
report
end
@@ -61,7 +77,6 @@ class Chef
DEPRECATED_CONFIG_VALUES = %w{
attributes_save
- chef_node_attribute_enabled
fail_if_not_present
inspec_gem_source
inspec_version
@@ -79,7 +94,11 @@ class Chef
end
end
- def report(report = generate_report)
+ def report(report = nil)
+ logger.info "Starting Chef Infra Compliance Phase"
+ report ||= generate_report
+ # This is invoked at report-time instead of with the normal validations at node loaded,
+ # because we want to ensure that it is visible in the output - and not lost in back-scroll.
warn_for_deprecated_config_values!
if report.empty?
@@ -87,15 +106,23 @@ class Chef
return
end
- Array(node["audit"]["reporter"]).each do |reporter|
- send_report(reporter, report)
+ Array(node["audit"]["reporter"]).each do |reporter_type|
+ logger.info "Reporting to #{reporter_type}"
+ @reporters[reporter_type].send_report(report)
end
+ logger.info "Chef Infra Compliance Phase Complete"
end
def inspec_opts
+ inputs = node["audit"]["attributes"].to_h
+ if node["audit"]["chef_node_attribute_enabled"]
+ inputs["chef_node"] = node.to_h
+ inputs["chef_node"]["chef_environment"] = node.chef_environment
+ end
+
{
backend_cache: node["audit"]["inspec_backend_cache"],
- inputs: node["audit"]["attributes"],
+ inputs: inputs,
logger: logger,
output: node["audit"]["quiet"] ? ::File::NULL : STDOUT,
report: true,
@@ -108,10 +135,8 @@ class Chef
def inspec_profiles
profiles = node["audit"]["profiles"]
-
- # TODO: Custom exception class here?
unless profiles.respond_to?(:map) && profiles.all? { |_, p| p.respond_to?(:transform_keys) && p.respond_to?(:update) }
- raise "#{Inspec::Dist::PRODUCT_NAME} profiles specified in an unrecognized format, expected a hash of hashes."
+ raise "CMPL010: #{Inspec::Dist::PRODUCT_NAME} profiles specified in an unrecognized format, expected a hash of hashes."
end
profiles.map do |name, profile|
@@ -127,8 +152,6 @@ class Chef
require_relative "fetcher/chef_server"
when nil
# intentionally blank
- else
- raise "Invalid value specified for Compliance Phase's fetcher: '#{node["audit"]["fetcher"]}'. Valid values are 'chef-automate', 'chef-server', or nil."
end
end
@@ -178,6 +201,8 @@ class Chef
# extracts relevant node data
def node_info
+ chef_server_uri = URI(Chef::Config[:chef_server_url])
+
runlist_roles = node.run_list.select { |item| item.type == :role }.map(&:name)
runlist_recipes = node.run_list.select { |item| item.type == :recipe }.map(&:name)
{
@@ -199,51 +224,83 @@ class Chef
}
end
- def send_report(reporter, report)
- logger.info "Reporting to #{reporter}"
-
- insecure = node["audit"]["insecure"]
- run_time_limit = node["audit"]["run_time_limit"]
- control_results_limit = node["audit"]["control_results_limit"]
-
- case reporter
+ def reporter(reporter_type)
+ case reporter_type
when "chef-automate"
+ require_relative "reporter/automate"
opts = {
+ control_results_limit: node["audit"]["control_results_limit"],
entity_uuid: node["chef_guid"],
- run_id: run_id,
+ insecure: node["audit"]["insecure"],
node_info: node_info,
- insecure: insecure,
- run_time_limit: run_time_limit,
- control_results_limit: control_results_limit,
+ run_id: run_id,
+ run_time_limit: node["audit"]["run_time_limit"],
}
- Chef::Compliance::Reporter::Automate.new(opts).send_report(report)
+ Chef::Compliance::Reporter::Automate.new(opts)
when "chef-server-automate"
- chef_url = node["audit"]["server"] || base_chef_server_url
- chef_org = Chef::Config[:chef_server_url].split("/").last
- if chef_url
- url = construct_url(chef_url, File.join("organizations", chef_org, "data-collector"))
- opts = {
- entity_uuid: node["chef_guid"],
- run_id: run_id,
- node_info: node_info,
- insecure: insecure,
- url: url,
- run_time_limit: run_time_limit,
- control_results_limit: control_results_limit,
- }
- Chef::Compliance::Reporter::ChefServer.new(opts).send_report(report)
- else
- logger.warn "Unable to determine #{ChefUtils::Dist::Server::PRODUCT} url required by #{Inspec::Dist::PRODUCT_NAME} report collector '#{reporter}'. Skipping..."
- end
+ require_relative "reporter/chef_server_automate"
+ opts = {
+ control_results_limit: node["audit"]["control_results_limit"],
+ entity_uuid: node["chef_guid"],
+ insecure: node["audit"]["insecure"],
+ node_info: node_info,
+ run_id: run_id,
+ run_time_limit: node["audit"]["run_time_limit"],
+ url: chef_server_automate_url,
+ }
+ Chef::Compliance::Reporter::ChefServerAutomate.new(opts)
when "json-file"
- path = node["audit"]["json_file"]["location"]
- logger.info "Writing compliance report to #{path}"
- Chef::Compliance::Reporter::JsonFile.new(file: path).send_report(report)
+ require_relative "reporter/json_file"
+ path = node.dig("audit", "json_file", "location")
+ Chef::Compliance::Reporter::JsonFile.new(file: path)
when "audit-enforcer"
- Chef::Compliance::Reporter::ComplianceEnforcer.new.send_report(report)
- else
- logger.warn "#{reporter} is not a supported #{Inspec::Dist::PRODUCT_NAME} report collector"
+ require_relative "reporter/compliance_enforcer"
+ Chef::Compliance::Reporter::ComplianceEnforcer.new
+ when "cli"
+ require_relative "reporter/cli"
+ Chef::Compliance::Reporter::Cli.new
+ end
+ end
+
+ def chef_server_automate_url
+ url = if node["audit"]["server"]
+ URI(node["audit"]["server"])
+ else
+ URI(Chef::Config[:chef_server_url]).tap do |u|
+ u.path = ""
+ end
+ end
+
+ org = Chef::Config[:chef_server_url].split("/").last
+ url.path = File.join(url.path, "organizations/#{org}/data-collector")
+ url
+ end
+
+ # Load the resources required for this runner, and validate configuration
+ # is correct to proceed. Requires node state to be loaded.
+ # Will raise exception if fetcher is not valid, if a reporter is not valid,
+ # or the configuration required by a reporter is not provided.
+ def load_and_validate!
+ return unless enabled?
+
+ @reporters = {}
+ # Note that the docs don't say you can use an array, but our implementation
+ # supports it.
+ Array(node["audit"]["reporter"]).each do |type|
+ unless SUPPORTED_REPORTERS.include? type
+ raise "CMPL003: '#{type}' found in node['audit']['reporter'] is not a supported reporter for Compliance Phase. Supported reporters are: #{SUPPORTED_REPORTERS.join(", ")}. For more information, see the documentation at https://docs.chef.io/chef_compliance_phase#reporters"
+ end
+
+ @reporters[type] = reporter(type)
+ @reporters[type].validate_config!
+ end
+
+ unless (fetcher = node["audit"]["fetcher"]).nil?
+ unless SUPPORTED_FETCHERS.include? fetcher
+ raise "CMPL002: Unrecognized Compliance Phase fetcher (node['audit']['fetcher'] = #{fetcher}). Supported fetchers are: #{SUPPORTED_FETCHERS.join(", ")}, or nil. For more information, see the documentation at https://docs.chef.io/chef_compliance_phase#fetch-profiles"
+ end
end
+ @validation_passed = true
end
end
end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index faed509321..00cf22e6da 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -160,13 +160,13 @@ class Chef
def metadata_filenames
return @metadata_filenames unless @metadata_filenames.empty?
- if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
+ if File.exist?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
@uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
end
- if File.exists?(File.join(cookbook_path, "metadata.json"))
+ if File.exist?(File.join(cookbook_path, "metadata.json"))
@metadata_filenames << File.join(cookbook_path, "metadata.json")
- elsif File.exists?(File.join(cookbook_path, "metadata.rb"))
+ elsif File.exist?(File.join(cookbook_path, "metadata.rb"))
@metadata_filenames << File.join(cookbook_path, "metadata.rb")
elsif uploaded_cookbook_version_file
@metadata_filenames << uploaded_cookbook_version_file
diff --git a/lib/chef/cookbook/gem_installer.rb b/lib/chef/cookbook/gem_installer.rb
index d7c18627de..e892212e32 100644
--- a/lib/chef/cookbook/gem_installer.rb
+++ b/lib/chef/cookbook/gem_installer.rb
@@ -70,7 +70,11 @@ class Chef
unless Chef::Config[:skip_gem_metadata_installation]
# Add additional options to bundle install
cmd = [ "bundle", "install", Chef::Config[:gem_installer_bundler_options] ]
- so = shell_out!(cmd, cwd: dir, env: { "PATH" => path_with_prepended_ruby_bin })
+ env = {
+ "PATH" => path_with_prepended_ruby_bin,
+ "BUNDLE_SILENCE_ROOT_WARNING" => "1",
+ }
+ so = shell_out!(cmd, cwd: dir, env: env)
Chef::Log.info(so.stdout)
end
end
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 53e874d0e8..e0ce5d7f41 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -143,11 +143,9 @@ class Chef
end
def files_remaining_by_cookbook
- @files_remaining_by_cookbook ||= begin
- files_by_cookbook.inject({}) do |memo, (cookbook, files)|
- memo[cookbook] = files.size
- memo
- end
+ @files_remaining_by_cookbook ||= files_by_cookbook.inject({}) do |memo, (cookbook, files)|
+ memo[cookbook] = files.size
+ memo
end
end
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index f7efb2646e..c1d35f7d7e 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -195,10 +195,8 @@ class Chef
def all_files_in_repo_paths
@all_files_in_repo_paths ||=
- begin
- repo_paths.inject([]) do |all_children, repo_path|
- all_children + Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(repo_path), "*")]
- end
+ repo_paths.inject([]) do |all_children, repo_path|
+ all_children + Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(repo_path), "*")]
end
end
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
deleted file mode 100644
index d7226b79b3..0000000000
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ /dev/null
@@ -1,244 +0,0 @@
-#
-# Author:: Stanislav Vitvitskiy
-# Author:: Nuo Yan (nuo@chef.io)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright (c) 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.
-#
-
-autoload :URI, "uri"
-module Net
- autoload :HTTP, "net/http"
-end
-autoload :OpenSSL, "openssl"
-module Mixlib
- module Authentication
- autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
- end
-end
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- # == Chef::CookbookSiteStreamingUploader
- # A streaming multipart HTTP upload implementation. Used to upload cookbooks
- # (in tarball form) to https://supermarket.chef.io
- #
- # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
- class CookbookSiteStreamingUploader
-
- DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }.freeze # rubocop:disable Naming/ConstantName
-
- class << self
-
- def create_build_dir(cookbook)
- tmp_cookbook_path = Tempfile.new("#{ChefUtils::Dist::Infra::SHORT}-#{cookbook.name}-build")
- tmp_cookbook_path.close
- tmp_cookbook_dir = tmp_cookbook_path.path
- File.unlink(tmp_cookbook_dir)
- FileUtils.mkdir_p(tmp_cookbook_dir)
- Chef::Log.trace("Staging at #{tmp_cookbook_dir}")
- checksums_to_on_disk_paths = cookbook.checksums
- cookbook.each_file do |manifest_record|
- path_in_cookbook = manifest_record[:path]
- on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
- dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
- FileUtils.mkdir_p(File.dirname(dest))
- Chef::Log.trace("Staging #{on_disk_path} to #{dest}")
- FileUtils.cp(on_disk_path, dest)
- end
-
- # First, generate metadata
- Chef::Log.trace("Generating metadata")
- kcm = Chef::Knife::CookbookMetadata.new
- kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
- kcm.name_args = [ cookbook.name.to_s ]
- kcm.run
-
- tmp_cookbook_dir
- end
-
- def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
- make_request(:post, to_url, user_id, secret_key_filename, params, headers)
- end
-
- def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
- make_request(:put, to_url, user_id, secret_key_filename, params, headers)
- end
-
- def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
- boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ"
- parts = []
- content_file = nil
-
- secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
-
- unless params.nil? || params.empty?
- params.each do |key, value|
- if value.is_a?(File)
- content_file = value
- filepath = value.path
- filename = File.basename(filepath)
- parts << StringPart.new( "--" + boundary + "\r\n" +
- "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
- "Content-Type: application/octet-stream\r\n\r\n")
- parts << StreamPart.new(value, File.size(filepath))
- parts << StringPart.new("\r\n")
- else
- parts << StringPart.new( "--" + boundary + "\r\n" +
- "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
- parts << StringPart.new(value.to_s + "\r\n")
- end
- end
- parts << StringPart.new("--" + boundary + "--\r\n")
- end
-
- body_stream = MultipartStream.new(parts)
-
- timestamp = Time.now.utc.iso8601
-
- url = URI.parse(to_url)
-
- Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
-
- # We use the body for signing the request if the file parameter
- # wasn't a valid file or wasn't included. Extract the body (with
- # multi-part delimiters intact) to sign the request.
- # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
- # always hash the entire request body. In the file case it would just be
- # expanded multipart text - the entire body of the POST.
- content_body = parts.inject("") { |result, part| result + part.read(0, part.size) }
- content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
-
- signing_options = {
- http_method: http_verb,
- path: url.path,
- user_id: user_id,
- timestamp: timestamp }
- (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
-
- headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
-
- content_file.rewind if content_file
-
- # net/http doesn't like symbols for header keys, so we'll to_s each one just in case
- headers = DefaultHeaders.merge(Hash[*headers.map { |k, v| [k.to_s, v] }.flatten])
-
- req = case http_verb
- when :put
- Net::HTTP::Put.new(url.path, headers)
- when :post
- Net::HTTP::Post.new(url.path, headers)
- end
- req.content_length = body_stream.size
- req.content_type = "multipart/form-data; boundary=" + boundary unless parts.empty?
- req.body_stream = body_stream
-
- http = Chef::HTTP::BasicClient.new(url).http_client
- res = http.request(req)
-
- # alias status to code and to_s to body for test purposes
- # TODO: stop the following madness!
- class << res
- alias :to_s :body
-
- # BUG this makes the response compatible with what response_steps expects to test headers (response.headers[] -> response[])
- def headers # rubocop:disable Lint/NestedMethodDefinition
- self
- end
-
- def status # rubocop:disable Lint/NestedMethodDefinition
- code.to_i
- end
- end
- res
- end
-
- end
-
- class StreamPart
- def initialize(stream, size)
- @stream, @size = stream, size
- end
-
- def size
- @size
- end
-
- # read the specified amount from the stream
- def read(offset, how_much)
- @stream.read(how_much)
- end
- end
-
- class StringPart
- def initialize(str)
- @str = str
- end
-
- def size
- @str.length
- end
-
- # read the specified amount from the string starting at the offset
- def read(offset, how_much)
- @str[offset, how_much]
- end
- end
-
- class MultipartStream
- def initialize(parts)
- @parts = parts
- @part_no = 0
- @part_offset = 0
- end
-
- def size
- @parts.inject(0) { |size, part| size + part.size }
- end
-
- def read(how_much, dst_buf = nil)
- if @part_no >= @parts.size
- dst_buf.replace("") if dst_buf
- return dst_buf
- end
-
- how_much_current_part = @parts[@part_no].size - @part_offset
-
- how_much_current_part = if how_much_current_part > how_much
- how_much
- else
- how_much_current_part
- end
-
- how_much_next_part = how_much - how_much_current_part
-
- current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
-
- # recurse into the next part if the current one was not large enough
- if how_much_next_part > 0
- @part_no += 1
- @part_offset = 0
- next_part = read(how_much_next_part)
- result = current_part + (next_part || "")
- else
- @part_offset += how_much_current_part
- result = current_part
- end
- dst_buf ? dst_buf.replace(result || "") : result
- end
- end
-
- end
-end
diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb
index 235a539b94..21a15f706c 100644
--- a/lib/chef/cookbook_uploader.rb
+++ b/lib/chef/cookbook_uploader.rb
@@ -1,7 +1,6 @@
autoload :Set, "set"
require_relative "exceptions"
-require_relative "knife/cookbook_metadata"
require_relative "digester"
require_relative "cookbook_manifest"
require_relative "cookbook_version"
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 420532585a..6e4f13c291 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -138,11 +138,14 @@ class Chef
end
def recipe_yml_filenames_by_name
- @recipe_ym_filenames_by_name ||= begin
+ @recipe_yml_filenames_by_name ||= begin
name_map = yml_filenames_by_name(files_for("recipes"))
- root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "root_files/recipe.yml" }
+ root_alias = cookbook_manifest.root_files.find { |record|
+ record[:name] == "root_files/recipe.yml" ||
+ record[:name] == "root_files/recipe.yaml"
+ }
if root_alias
- Chef::Log.error("Cookbook #{name} contains both recipe.yml and and recipes/default.yml, ignoring recipes/default.yml") if name_map["default"]
+ Chef::Log.error("Cookbook #{name} contains both recipe.yml and recipes/default.yml, ignoring recipes/default.yml") if name_map["default"]
name_map["default"] = root_alias[:full_path]
end
name_map
@@ -582,8 +585,27 @@ class Chef
records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:full_path]; memo }
end
+ # Filters YAML files from the superset of provided files.
+ # Checks for duplicate basenames with differing extensions (eg yaml v yml)
+ # and raises error if any are detected.
+ # This prevents us from arbitrarily the ".yaml" or ".yml" version when both are present,
+ # because we don't know which is correct.
+ # This method runs in O(n^2) where N = number of yml files present. This number should be consistently
+ # low enough that there's no noticeable perf impact.
def yml_filenames_by_name(records)
- records.select { |record| record[:name] =~ /\.yml$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".yml")] = record[:full_path]; memo }
+ yml_files = records.select { |record| record[:name] =~ /\.(y[a]?ml)$/ }
+ result = yml_files.inject({}) do |acc, record|
+ filename = record[:name]
+ base_dup_name = File.join(File.dirname(filename), File.basename(filename, File.extname(filename)))
+ yml_files.each do |other|
+ if other[:name] =~ /#{(File.extname(filename) == ".yml") ? "#{base_dup_name}.yaml" : "#{base_dup_name}.yml"}$/
+ raise Chef::Exceptions::AmbiguousYAMLFile.new("Cookbook #{name}@#{version} contains ambiguous files: #{filename} and #{other[:name]}. Please update the cookbook to remove the incorrect file.")
+ end
+ end
+ acc[File.basename(record[:name], File.extname(record[:name]))] = record[:full_path]
+ acc
+ end
+ result
end
def file_vendor
diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb
index af0560a640..26bf49a008 100644
--- a/lib/chef/data_bag.rb
+++ b/lib/chef/data_bag.rb
@@ -32,7 +32,8 @@ class Chef
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- VALID_NAME = /^[\.\-[:alnum:]_]+$/.freeze
+ # Regex reference: https://rubular.com/r/oIMySIO4USPm5x
+ VALID_NAME = /^[\-[:alnum:]_]+$/.freeze
RESERVED_NAMES = /^(node|role|environment|client)$/.freeze
def self.validate_name!(name)
diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb
index 94cf2a5317..17a105a84e 100644
--- a/lib/chef/data_bag_item.rb
+++ b/lib/chef/data_bag_item.rb
@@ -36,7 +36,8 @@ class Chef
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- VALID_ID = /^[\.\-[:alnum:]_]+$/.freeze
+ # Regex reference: https://rubular.com/r/oIMySIO4USPm5x
+ VALID_ID = /^[\-[:alnum:]_]+$/.freeze
def self.validate_id!(id_str)
if id_str.nil? || ( id_str !~ VALID_ID )
@@ -44,8 +45,17 @@ class Chef
end
end
- # Define all Hash's instance methods as delegating to @raw_data
- def_delegators(:@raw_data, *(Hash.instance_methods - Object.instance_methods))
+ # delegate missing methods to the @raw_data Hash
+ def method_missing(method_name, *arguments, &block)
+ @raw_data.send(method_name, *arguments, &block)
+ rescue
+ # throw more sensible errors back at the user
+ super
+ end
+
+ def respond_to_missing?(method_name, include_private = false)
+ @raw_data.respond_to?(method_name, include_private) || super
+ end
attr_reader :raw_data
diff --git a/lib/chef/data_collector.rb b/lib/chef/data_collector.rb
index 8d76f8a7b2..0e621f1492 100644
--- a/lib/chef/data_collector.rb
+++ b/lib/chef/data_collector.rb
@@ -104,7 +104,6 @@ class Chef
#
def action_collection_registration(action_collection)
@action_collection = action_collection
- action_collection.register(self)
end
# - Creates and writes our NodeUUID back to the node object
diff --git a/lib/chef/data_collector/run_end_message.rb b/lib/chef/data_collector/run_end_message.rb
index 1900effa26..91cf21e643 100644
--- a/lib/chef/data_collector/run_end_message.rb
+++ b/lib/chef/data_collector/run_end_message.rb
@@ -51,7 +51,7 @@ class Chef
"id" => run_status&.run_id,
"message_version" => "1.1.0",
"message_type" => "run_converge",
- "node" => node || {},
+ "node" => node&.data_for_save || {},
"node_name" => node&.name || data_collector.node_name,
"organization_name" => organization,
"resources" => all_action_records(action_collection),
diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb
index df734c8303..f200f88410 100644
--- a/lib/chef/delayed_evaluator.rb
+++ b/lib/chef/delayed_evaluator.rb
@@ -17,5 +17,9 @@
class Chef
class DelayedEvaluator < Proc
+ def dup
+ # super returns a "Proc" (which seems buggy) so re-wrap it
+ self.class.new(&super) # rubocop:disable Layout/SpaceAroundKeyword
+ end
end
end
diff --git a/lib/chef/deprecated.rb b/lib/chef/deprecated.rb
index 992876c17d..ae205497c4 100644
--- a/lib/chef/deprecated.rb
+++ b/lib/chef/deprecated.rb
@@ -249,6 +249,14 @@ class Chef
target 32
end
+ class UnifiedMode < Base
+ target 33
+ end
+
+ class AttributeWhitelistConfiguration < Base
+ target 34
+ end
+
class Generic < Base
def url
"https://docs.chef.io/chef_deprecations_client/"
diff --git a/lib/chef/dsl/chef_vault.rb b/lib/chef/dsl/chef_vault.rb
index 031627c358..3411f79e41 100644
--- a/lib/chef/dsl/chef_vault.rb
+++ b/lib/chef/dsl/chef_vault.rb
@@ -32,8 +32,8 @@ class Chef
# actually a Chef Vault item. This is controlled via
# +node['chef-vault']['databag_fallback']+.
# @example
- # item = chef_vault_item('secrets', 'bacon')
- # log 'Yeah buddy!' if item['_default']['type']
+ # item = chef_vault_item('secrets', 'bacon')
+ # log 'Yeah buddy!' if item['_default']['type']
# @param [String] bag Name of the data bag to load from.
# @param [String] id Identifier of the data bag item to load.
def chef_vault_item(bag, id)
@@ -51,8 +51,8 @@ class Chef
# the items, so this method strips out the keys for users so that they
# don't have to do it in their recipes.
# @example
- # ids = chef_vault('secrets')
- # log 'Yeah buddy!' if ids[0] == 'bacon'
+ # ids = chef_vault('secrets')
+ # log 'Yeah buddy!' if ids[0] == 'bacon'
# @param [String] bag Name of the data bag to load from.
# @return [Array]
def chef_vault(bag)
@@ -68,8 +68,8 @@ class Chef
# This allows for easy access to current environment secrets inside
# of an item.
# @example
- # item = chef_vault_item_for_environment('secrets', 'bacon')
- # log 'Yeah buddy!' if item['type'] == 'applewood_smoked'
+ # item = chef_vault_item_for_environment('secrets', 'bacon')
+ # log 'Yeah buddy!' if item['type'] == 'applewood_smoked'
# @param [String] bag Name of the data bag to load from.
# @param [String] id Identifier of the data bag item to load.
# @return [Hash]
diff --git a/lib/chef/dsl/declare_resource.rb b/lib/chef/dsl/declare_resource.rb
index 8053098085..df0ee0b719 100644
--- a/lib/chef/dsl/declare_resource.rb
+++ b/lib/chef/dsl/declare_resource.rb
@@ -156,15 +156,7 @@ class Chef
def edit_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
edit_resource!(type, name, created_at: created_at, run_context: run_context, &resource_attrs_block)
rescue Chef::Exceptions::ResourceNotFound
- resource = declare_resource(type, name, created_at: created_at, run_context: run_context)
- if resource_attrs_block
- if defined?(new_resource)
- resource.instance_exec(new_resource, &resource_attrs_block)
- else
- resource.instance_exec(&resource_attrs_block)
- end
- end
- resource
+ declare_resource(type, name, created_at: created_at, run_context: run_context, &resource_attrs_block)
end
# Find existing resources by searching the list of existing resources. Possible
@@ -306,6 +298,8 @@ class Chef
enclosing_provider ||= self if is_a?(Chef::Provider)
+ nr = new_resource if defined?(new_resource)
+
Chef::ResourceBuilder.new(
type: type,
name: name,
@@ -314,7 +308,8 @@ class Chef
run_context: run_context,
cookbook_name: cookbook_name,
recipe_name: recipe_name,
- enclosing_provider: enclosing_provider
+ enclosing_provider: enclosing_provider,
+ new_resource: nr
).build(&resource_attrs_block)
end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index 19d3a6b0bd..e115d5441d 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -45,9 +45,8 @@ class Chef
# Vista + Server 2008 and newer may have reboots pending from CBS
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending')
- elsif platform?("ubuntu")
- # This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that.
- File.exists?("/var/run/reboot-required")
+ elsif platform_family?("debian")
+ File.exist?("/var/run/reboot-required")
else
false
end
diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb
index 02baad2a2d..13ed0de050 100644
--- a/lib/chef/encrypted_data_bag_item/assertions.rb
+++ b/lib/chef/encrypted_data_bag_item/assertions.rb
@@ -30,7 +30,7 @@ class Chef::EncryptedDataBagItem
unless format_version.is_a?(Integer) && format_version >= Chef::Config[:data_bag_decrypt_minimum_version]
raise UnacceptableEncryptedDataBagItemFormat,
"The encrypted data bag item has format version `#{format_version}', " +
- "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'"
+ "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'"
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index f7b706cb2c..f9504967a9 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -221,7 +221,8 @@ class Chef
# Called before convergence starts
def converge_start(run_context); end
- # Callback hook for handlers to register their interest in the action_collection
+ # Callback hook for handlers to grab a reference to the action_collection
+ # (sent before compiling cookbooks, consumers can also find it off the run_context.action_collection)
def action_collection_registration(action_collection); end
# Called when the converge phase is finished.
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index a0afd25208..ba34b22e61 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -174,6 +174,9 @@ class Chef
class CannotDetermineWindowsInstallerType < Package; end
class NoWindowsPackageSource < Package; end
+ # for example, if both recipes/default.yml, recipes/default.yaml are present
+ class AmbiguousYAMLFile < RuntimeError; end
+
# Can not create staging file during file deployment
class FileContentStagingError < RuntimeError
def initialize(errors)
diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb
index cc1b96a84d..744c5f7466 100644
--- a/lib/chef/file_access_control/windows.rb
+++ b/lib/chef/file_access_control/windows.rb
@@ -33,7 +33,7 @@ class Chef
module ClassMethods
# We want to mix these in as class methods
def writable?(path)
- ::File.exists?(path) && Chef::ReservedNames::Win32::File.file_access_check(
+ ::File.exist?(path) && Chef::ReservedNames::Win32::File.file_access_check(
path, Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
)
end
@@ -136,7 +136,7 @@ class Chef
end
def should_update_dacl?
- return true unless ::File.exists?(file) || ::File.symlink?(file)
+ return true unless ::File.exist?(file) || ::File.symlink?(file)
dacl = target_dacl
existing_dacl = existing_descriptor.dacl
@@ -170,7 +170,7 @@ class Chef
end
def should_update_group?
- return true unless ::File.exists?(file) || ::File.symlink?(file)
+ return true unless ::File.exist?(file) || ::File.symlink?(file)
(group = target_group) && (group != existing_descriptor.group)
end
@@ -190,7 +190,7 @@ class Chef
end
def should_update_owner?
- return true unless ::File.exists?(file) || ::File.symlink?(file)
+ return true unless ::File.exist?(file) || ::File.symlink?(file)
(owner = target_owner) && (owner != existing_descriptor.owner)
end
diff --git a/lib/chef/file_cache.rb b/lib/chef/file_cache.rb
index 22060869da..851234596a 100644
--- a/lib/chef/file_cache.rb
+++ b/lib/chef/file_cache.rb
@@ -79,7 +79,7 @@ class Chef
file_path_array = File.split(path)
file_name = file_path_array.pop
- if File.exists?(file) && File.writable?(file)
+ if File.exist?(file) && File.writable?(file)
FileUtils.mv(
file,
File.join(create_cache_path(File.join(file_path_array), true), file_name)
@@ -112,7 +112,7 @@ class Chef
}
)
cache_path = create_cache_path(path, false)
- raise Chef::Exceptions::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exists?(cache_path)
+ raise Chef::Exceptions::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exist?(cache_path)
if read
File.read(cache_path)
@@ -139,7 +139,7 @@ class Chef
}
)
cache_path = create_cache_path(path, false)
- if File.exists?(cache_path)
+ if File.exist?(cache_path)
File.unlink(cache_path)
end
true
@@ -186,7 +186,7 @@ class Chef
}
)
full_path = create_cache_path(path, false)
- if File.exists?(full_path)
+ if File.exist?(full_path)
true
else
false
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 513ac45471..2a3f9faef3 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -56,7 +56,8 @@ class Chef
# Print out deprecations.
unless deprecations.empty?
puts_line ""
- puts_line "Deprecated features used!"
+ puts_line "Deprecation warnings that must be addressed before upgrading to Chef Infra #{Chef::VERSION.to_i + 1}:"
+ puts_line ""
deprecations.each do |message, details|
locations = details[:locations]
if locations.size == 1
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 905a438f56..4010bd72d5 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -64,36 +64,34 @@ class Chef
def recipe_snippet
return nil if dynamic_resource?
- @snippet ||= begin
- if (file = parse_source) && (line = parse_line(file))
- return nil unless ::File.exists?(file)
+ @snippet ||= if (file = parse_source) && (line = parse_line(file))
+ return nil unless ::File.exist?(file)
- lines = IO.readlines(file)
+ lines = IO.readlines(file)
- relevant_lines = ["# In #{file}\n\n"]
+ relevant_lines = ["# In #{file}\n\n"]
- current_line = line - 1
- current_line = 0 if current_line < 0
- nesting = 0
+ current_line = line - 1
+ current_line = 0 if current_line < 0
+ nesting = 0
- loop do
+ loop do
- # low rent parser. try to gracefully handle nested blocks in resources
- nesting += 1 if /\s+do\s*/.match?(lines[current_line])
- nesting -= 1 if /end\s*$/.match?(lines[current_line])
+ # low rent parser. try to gracefully handle nested blocks in resources
+ nesting += 1 if /\s+do\s*/.match?(lines[current_line])
+ nesting -= 1 if /end\s*$/.match?(lines[current_line])
- relevant_lines << format_line(current_line, lines[current_line])
+ relevant_lines << format_line(current_line, lines[current_line])
- break if lines[current_line + 1].nil?
- break if current_line >= (line + 50)
- break if nesting <= 0
+ break if lines[current_line + 1].nil?
+ break if current_line >= (line + 50)
+ break if nesting <= 0
- current_line += 1
- end
- relevant_lines << format_line(current_line + 1, lines[current_line + 1]) if lines[current_line + 1]
- relevant_lines.join("")
- end
- end
+ current_line += 1
+ end
+ relevant_lines << format_line(current_line + 1, lines[current_line + 1]) if lines[current_line + 1]
+ relevant_lines.join("")
+ end
end
def dynamic_resource?
diff --git a/lib/chef/group.rb b/lib/chef/group.rb
new file mode 100644
index 0000000000..72f5b7b474
--- /dev/null
+++ b/lib/chef/group.rb
@@ -0,0 +1,75 @@
+#
+# Copyright:: Copyright (c) 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 "org"
+
+class Chef
+ class Group
+
+ def group(groupname)
+ @group ||= {}
+ @group[groupname] ||= chef_rest.get_rest "organizations/#{name}/groups/#{groupname}"
+ end
+
+ def user_member_of_group?(username, groupname)
+ group = group(groupname)
+ group["actors"].include? username
+ end
+
+ def add_user_to_group(groupname, username)
+ group = group(groupname)
+ body_hash = {
+ groupname: "#{groupname}",
+ actors: {
+ "users" => group["actors"].concat([username]),
+ "groups" => group["groups"],
+ },
+ }
+ chef_rest.put_rest "organizations/#{name}/groups/#{groupname}", body_hash
+ end
+
+ def remove_user_from_group(groupname, username)
+ group = group(groupname)
+ group["actors"].delete(username)
+ body_hash = {
+ groupname: "#{groupname}",
+ actors: {
+ "users" => group["actors"],
+ "groups" => group["groups"],
+ },
+ }
+ chef_rest.put_rest "organizations/#{name}/groups/#{groupname}", body_hash
+ end
+
+ def actor_delete_would_leave_admins_empty?
+ admins = group("admins")
+ if admins["groups"].empty?
+ # exclude 'pivotal' but don't mutate the group since we're caching it
+ if admins["actors"].include? "pivotal"
+ admins["actors"].length <= 2
+ else
+ admins["actors"].length <= 1
+ end
+ else
+ # We don't check recursively. If the admins group contains a group,
+ # and the user is the only member of that group,
+ # we'll still turn up a 'safe to delete'.
+ false
+ end
+ end
+ end
+end
diff --git a/lib/chef/handler.rb b/lib/chef/handler.rb
index 97562ea31b..173d4c4faa 100644
--- a/lib/chef/handler.rb
+++ b/lib/chef/handler.rb
@@ -55,6 +55,12 @@ class Chef
#
class Handler
+ # FIXME: Chef::Handler should probably inherit from EventDispatch::Base
+ # and should wire up to those events rather than the "notifications" system
+ # which is hanging off of Chef::Client. Those "notifications" could then be
+ # deprecated in favor of events, and this class could become decoupled from
+ # the Chef::Client object.
+
def self.handler_for(*args)
if args.include?(:start)
Chef::Config[:start_handlers] ||= []
@@ -207,17 +213,45 @@ class Chef
# The Chef::Node for this client run
def_delegator :@run_status, :node
- ##
- # :method: all_resources
+ # @return Array<Chef::Resource> all resources other than unprocessed
#
- # An Array containing all resources in the chef run's resource_collection
- def_delegator :@run_status, :all_resources
+ def all_resources
+ @all_resources ||= action_collection&.filtered_collection(unprocessed: false)&.resources || []
+ end
- ##
- # :method: updated_resources
+ # @return Array<Chef::Resource> all updated resources
+ #
+ def updated_resources
+ @updated_resources ||= action_collection&.filtered_collection(up_to_date: false, skipped: false, failed: false, unprocessed: false)&.resources || []
+ end
+
+ # @return Array<Chef::Resource> all up_to_date resources
+ #
+ def up_to_date_resources
+ @up_to_date_resources ||= action_collection&.filtered_collection(updated: false, skipped: false, failed: false, unprocessed: false)&.resources || []
+ end
+
+ # @return Array<Chef::Resource> all failed resources
#
- # An Array containing all resources that were updated during the chef run
- def_delegator :@run_status, :updated_resources
+ def failed_resources
+ @failed_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, skipped: false, unprocessed: false)&.resources || []
+ end
+
+ # @return Array<Chef::Resource> all skipped resources
+ #
+ def skipped_resources
+ @skipped_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, failed: false, unprocessed: false)&.resources || []
+ end
+
+ # Unprocessed resources are those which are left over in the outer recipe context when a run fails.
+ # Sub-resources of unprocessed resourced are impossible to capture because they would require processing
+ # the outer resource.
+ #
+ # @return Array<Chef::Resource> all unprocessed resources
+ #
+ def unprocessed_resources
+ @unprocessed_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, failed: false, skipped: false)&.resources || []
+ end
##
# :method: success?
@@ -232,6 +266,10 @@ class Chef
# Did the chef run fail? True if the chef run raised an uncaught exception
def_delegator :@run_status, :failed?
+ def action_collection
+ @run_status.run_context.action_collection
+ end
+
# The main entry point for report handling. Subclasses should override this
# method with their own report handling logic.
def report; end
diff --git a/lib/chef/handler/json_file.rb b/lib/chef/handler/json_file.rb
index 6318c30d74..998f2b0e0e 100644
--- a/lib/chef/handler/json_file.rb
+++ b/lib/chef/handler/json_file.rb
@@ -51,7 +51,7 @@ class Chef
end
def build_report_dir
- unless File.exists?(config[:path])
+ unless File.exist?(config[:path])
FileUtils.mkdir_p(config[:path])
File.chmod(00700, config[:path])
end
diff --git a/lib/chef/handler/slow_report.rb b/lib/chef/handler/slow_report.rb
new file mode 100644
index 0000000000..e5b2f2895f
--- /dev/null
+++ b/lib/chef/handler/slow_report.rb
@@ -0,0 +1,66 @@
+#
+# Copyright:: Copyright (c) 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 "../handler"
+require "tty/table" unless defined?(TTY::Table)
+
+class Chef
+ class Handler
+ class SlowReport < ::Chef::Handler
+ attr_accessor :amount
+
+ def initialize(amount)
+ @amount = Integer(amount) rescue nil
+ @amount ||= 10
+ end
+
+ def report
+ if count == 0
+ puts "\nNo resources to profile\n\n"
+ return
+ end
+
+ top = all_records.sort_by(&:elapsed_time).last(amount).reverse
+ data = top.map { |r| [ r.new_resource.to_s, r.elapsed_time, r.action, r.new_resource.cookbook_name, r.new_resource.recipe_name, stripped_source_line(r.new_resource) ] }
+ puts "\nTop #{count} slowest #{count == 1 ? "resource" : "resources"}:\n\n"
+ table = TTY::Table.new(%w{resource elapsed_time action cookbook recipe source}, data)
+ rendered = table.render do |renderer|
+ renderer.border do
+ mid "-"
+ mid_mid " "
+ end
+ end
+ puts rendered
+ puts "\n"
+ end
+
+ def all_records
+ @all_records ||= action_collection&.filtered_collection(unprocessed: false) || []
+ end
+
+ def count
+ num = all_resources.count
+ num > amount ? amount : num
+ end
+
+ def stripped_source_line(resource)
+ # strip the leading path off of the source line
+ resource.source_line&.gsub(%r{.*/cookbooks/}, "")&.gsub(%r{.*/chef-[0-9\.]+/}, "")
+ end
+ end
+ end
+end
diff --git a/lib/chef/http/ssl_policies.rb b/lib/chef/http/ssl_policies.rb
index d36e747ff6..bc688f13a6 100644
--- a/lib/chef/http/ssl_policies.rb
+++ b/lib/chef/http/ssl_policies.rb
@@ -85,28 +85,41 @@ class Chef
http_client.cert_store.set_default_paths
end
if config.trusted_certs_dir
- certs = Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}"))
+ certs = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}"))
certs.each do |cert_file|
- cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
+ cert = begin
+ OpenSSL::X509::Certificate.new(::File.binread(cert_file))
+ rescue OpenSSL::X509::CertificateError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{cert_file}', original error '#{e.class}: #{e.message}'"
+ end
add_trusted_cert(cert)
end
end
end
def set_client_credentials
- if config[:ssl_client_cert] || config[:ssl_client_key]
- unless config[:ssl_client_cert] && config[:ssl_client_key]
- raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
- end
- unless ::File.exists?(config[:ssl_client_cert])
- raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
- end
- unless ::File.exists?(config[:ssl_client_key])
- raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
- end
+ return unless config[:ssl_client_cert] || config[:ssl_client_key]
+
+ unless config[:ssl_client_cert] && config[:ssl_client_key]
+ raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
+ end
+ unless ::File.exists?(config[:ssl_client_cert])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
+ end
+ unless ::File.exists?(config[:ssl_client_key])
+ raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
+ end
+
+ begin
+ http_client.cert = OpenSSL::X509::Certificate.new(::File.binread(config[:ssl_client_cert]))
+ rescue OpenSSL::X509::CertificateError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{config[:ssl_client_cert]}', original error '#{e.class}: #{e.message}'"
+ end
- http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert]))
- http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key]))
+ begin
+ http_client.key = OpenSSL::PKey::RSA.new(::File.binread(config[:ssl_client_key]))
+ rescue OpenSSL::PKey::RSAError => e
+ raise Chef::Exceptions::ConfigurationError, "Error reading key file '#{config[:ssl_client_key]}', original error '#{e.class}: #{e.message}'"
end
end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
deleted file mode 100644
index ac7a68d0fc..0000000000
--- a/lib/chef/knife.rb
+++ /dev/null
@@ -1,665 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Copyright:: Copyright (c) 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 "forwardable" unless defined?(Forwardable)
-require_relative "version"
-require "mixlib/cli" unless defined?(Mixlib::CLI)
-require "chef-utils/dsl/default_paths" unless defined?(ChefUtils::DSL::DefaultPaths)
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-require_relative "workstation_config_loader"
-require_relative "mixin/convert_to_class_name"
-require_relative "mixin/default_paths"
-require_relative "knife/core/subcommand_loader"
-require_relative "knife/core/ui"
-require_relative "local_mode"
-require_relative "server_api"
-require_relative "http/authenticator"
-require_relative "http/http_request"
-require_relative "http"
-require "pp" unless defined?(PP)
-
-class Chef
- class Knife
-
- Chef::HTTP::HTTPRequest.user_agent = "#{ChefUtils::Dist::Infra::PRODUCT} Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}"
-
- include Mixlib::CLI
- include ChefUtils::DSL::DefaultPaths
- extend Chef::Mixin::ConvertToClassName
- extend Forwardable
-
- # @note Backwards Compat:
- # Ideally, we should not vomit all of these methods into this base class;
- # instead, they should be accessed by hitting the ui object directly.
- def_delegator :@ui, :stdout
- def_delegator :@ui, :stderr
- def_delegator :@ui, :stdin
- def_delegator :@ui, :msg
- def_delegator :@ui, :ask_question
- def_delegator :@ui, :pretty_print
- def_delegator :@ui, :output
- def_delegator :@ui, :format_list_for_display
- def_delegator :@ui, :format_for_display
- def_delegator :@ui, :format_cookbook_list_for_display
- def_delegator :@ui, :edit_data
- def_delegator :@ui, :edit_hash
- def_delegator :@ui, :edit_object
- def_delegator :@ui, :confirm
-
- attr_accessor :name_args
- attr_accessor :ui
-
- # knife acl subcommands are grouped in this category using this constant to verify.
- OPSCODE_HOSTED_CHEF_ACCESS_CONTROL = %w{acl group user}.freeze
-
- # knife opc subcommands are grouped in this category using this constant to verify.
- CHEF_ORGANIZATION_MANAGEMENT = %w{opc}.freeze
-
- # Configure mixlib-cli to always separate defaults from user-supplied CLI options
- def self.use_separate_defaults?
- true
- end
-
- def self.ui
- @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
- end
-
- def self.msg(msg = "")
- ui.msg(msg)
- end
-
- def self.reset_config_loader!
- @@chef_config_dir = nil
- @config_loader = nil
- end
-
- def self.reset_subcommands!
- @@subcommands = {}
- @subcommands_by_category = nil
- end
-
- def self.inherited(subclass)
- super
- unless subclass.unnamed?
- subcommands[subclass.snake_case_name] = subclass
- subcommand_files[subclass.snake_case_name] +=
- if subclass.superclass.to_s == "Chef::ChefFS::Knife"
- # ChefFS-based commands have a superclass that defines an
- # inherited method which calls super. This means that the
- # top of the call stack is not the class definition for
- # our subcommand. Try the second entry in the call stack.
- [path_from_caller(caller[1])]
- else
- [path_from_caller(caller[0])]
- end
- end
- end
-
- # Explicitly set the category for the current command to +new_category+
- # The category is normally determined from the first word of the command
- # name, but some commands make more sense using two or more words
- # @param new_category [String] value to set the category to (see examples)
- #
- # @example Data bag commands would be in the 'data' category by default. To
- # put them in the 'data bag' category:
- # category('data bag')
- def self.category(new_category)
- @category = new_category
- end
-
- def self.subcommand_category
- @category || snake_case_name.split("_").first unless unnamed?
- end
-
- def self.snake_case_name
- convert_to_snake_case(name.split("::").last) unless unnamed?
- end
-
- def self.common_name
- snake_case_name.split("_").join(" ")
- end
-
- # Does this class have a name? (Classes created via Class.new don't)
- def self.unnamed?
- name.nil? || name.empty?
- end
-
- def self.subcommand_loader
- @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir)
- end
-
- def self.load_commands
- @commands_loaded ||= subcommand_loader.load_commands
- end
-
- def self.guess_category(args)
- subcommand_loader.guess_category(args)
- end
-
- def self.subcommand_class_from(args)
- if args.size == 1 && args[0].strip.casecmp("rehash") == 0
- # To prevent issues with the rehash file not pointing to the correct plugins,
- # we always use the glob loader when regenerating the rehash file
- @subcommand_loader = Chef::Knife::SubcommandLoader.gem_glob_loader(chef_config_dir)
- end
- subcommand_loader.command_class_from(args) || subcommand_not_found!(args)
- end
-
- def self.subcommands
- @@subcommands ||= {}
- end
-
- def self.subcommand_files
- @@subcommand_files ||= Hash.new([])
- end
-
- def self.subcommands_by_category
- unless @subcommands_by_category
- @subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
- subcommands.each do |snake_cased, klass|
- @subcommands_by_category[klass.subcommand_category] << snake_cased
- end
- end
- @subcommands_by_category
- end
-
- # Shared with subclasses
- @@chef_config_dir = nil
-
- def self.config_loader
- @config_loader ||= WorkstationConfigLoader.new(nil, Chef::Log)
- end
-
- def self.load_config(explicit_config_file, profile)
- config_loader.explicit_config_file = explicit_config_file
- config_loader.profile = profile
- config_loader.load
-
- ui.warn("No knife configuration file found. See https://docs.chef.io/config_rb/ for details.") if config_loader.no_config_found?
-
- config_loader
- rescue Exceptions::ConfigurationError => e
- ui.error(ui.color("CONFIGURATION ERROR:", :red) + e.message)
- exit 1
- end
-
- def self.chef_config_dir
- @@chef_config_dir ||= config_loader.chef_config_dir
- end
-
- # Run knife for the given +args+ (ARGV), adding +options+ to the list of
- # CLI options that the subcommand knows how to handle.
- #
- # @param args [Array] The arguments. Usually ARGV
- # @param options [Mixlib::CLI option parser hash] These +options+ are how
- # subcommands know about global knife CLI options
- #
- def self.run(args, options = {})
- # Fallback debug logging. Normally the logger isn't configured until we
- # read the config, but this means any logging that happens before the
- # config file is read may be lost. If the KNIFE_DEBUG variable is set, we
- # setup the logger for debug logging to stderr immediately to catch info
- # from early in the setup process.
- if ENV["KNIFE_DEBUG"]
- Chef::Log.init($stderr)
- Chef::Log.level(:debug)
- end
-
- subcommand_class = subcommand_class_from(args)
- subcommand_class.options = options.merge!(subcommand_class.options)
- subcommand_class.load_deps
- instance = subcommand_class.new(args)
- instance.configure_chef
- instance.run_with_pretty_exceptions
- end
-
- def self.dependency_loaders
- @dependency_loaders ||= []
- end
-
- def self.deps(&block)
- dependency_loaders << block
- end
-
- def self.load_deps
- dependency_loaders.each(&:call)
- end
-
- OFFICIAL_PLUGINS = %w{lpar openstack push rackspace vcenter}.freeze
-
- class << self
- def list_commands(preferred_category = nil)
- category_desc = preferred_category ? preferred_category + " " : ""
- msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
- subcommand_loader.list_commands(preferred_category).sort.each do |category, commands|
- next if /deprecated/i.match?(category)
-
- msg "** #{category.upcase} COMMANDS **"
- commands.sort.each do |command|
- subcommand_loader.load_command(command)
- msg subcommands[command].banner if subcommands[command]
- end
- msg
- end
- end
-
- private
-
- # @api private
- def path_from_caller(caller_line)
- caller_line.split(/:\d+/).first
- end
-
- # Error out and print usage. probably because the arguments given by the
- # user could not be resolved to a subcommand.
- # @api private
- def subcommand_not_found!(args)
- ui.fatal("Cannot find subcommand for: '#{args.join(" ")}'")
-
- # Mention rehash when the subcommands cache(plugin_manifest.json) is used
- if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader)
- ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
- end
-
- if CHEF_ORGANIZATION_MANAGEMENT.include?(args[0])
- list_commands("CHEF ORGANIZATION MANAGEMENT")
- elsif category_commands = guess_category(args)
- list_commands(category_commands)
- elsif OFFICIAL_PLUGINS.include?(args[0]) # command was an uninstalled official chef knife plugin
- ui.info("Use `#{ChefUtils::Dist::Infra::EXEC} gem install knife-#{args[0]}` to install the plugin into Chef Workstation")
- else
- list_commands
- end
-
- exit 10
- end
-
- # @api private
- def reset_config_path!
- @@chef_config_dir = nil
- end
-
- end
-
- reset_config_path!
-
- # Create a new instance of the current class configured for the given
- # arguments and options
- def initialize(argv = [])
- super() # having to call super in initialize is the most annoying anti-pattern :(
- @ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, config)
-
- command_name_words = self.class.snake_case_name.split("_")
-
- # Mixlib::CLI ignores the embedded name_args
- @name_args = parse_options(argv)
- @name_args.delete(command_name_words.join("-"))
- @name_args.reject! { |name_arg| command_name_words.delete(name_arg) }
-
- # knife node run_list add requires that we have extra logic to handle
- # the case that command name words could be joined by an underscore :/
- command_name_joined = command_name_words.join("_")
- @name_args.reject! { |name_arg| command_name_joined == name_arg }
-
- # Similar handling for hyphens.
- command_name_joined = command_name_words.join("-")
- @name_args.reject! { |name_arg| command_name_joined == name_arg }
-
- if config[:help]
- msg opt_parser
- exit 1
- end
-
- # Grab a copy before config merge occurs, so that we can later identify
- # where a given config value is sourced from.
- @original_config = config.dup
-
- # copy Mixlib::CLI over so that it can be configured in config.rb/knife.rb
- # config file
- Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity]
- end
-
- def parse_options(args)
- super
- rescue OptionParser::InvalidOption => e
- puts "Error: " + e.to_s
- show_usage
- exit(1)
- end
-
- # This is all set and default mixlib-config values. We only need the default
- # values here (the set values are explicitly mixed in again later), but there is
- # no mixlib-config API to get a Hash back with only the default values.
- #
- # Assumption: since config_file_defaults is the lowest precedence it doesn't matter
- # that we include the set values here, but this is a hack and makes the name of the
- # method a lie. FIXME: make the name not a lie by adding an API to mixlib-config.
- #
- # @api private
- #
- def config_file_defaults
- Chef::Config[:knife].save(true) # this is like "dup" to a (real) Hash, and includes default values (and user set values)
- end
-
- # This is only the user-set mixlib-config values. We do not include the defaults
- # here so that the config defaults do not override the cli defaults.
- #
- # @api private
- #
- def config_file_settings
- Chef::Config[:knife].save(false) # this is like "dup" to a (real) Hash, and does not include default values (just user set values)
- end
-
- # config is merged in this order (inverse of precedence)
- # config_file_defaults - Chef::Config[:knife] defaults from chef-config (XXX: this also includes the settings, but they get overwritten)
- # default_config - mixlib-cli defaults (accessor from mixlib-cli)
- # config_file_settings - Chef::Config[:knife] user settings from the client.rb file
- # config - mixlib-cli settings (accessor from mixlib-cli)
- #
- def merge_configs
- # Update our original_config - if someone has created a knife command
- # instance directly, they are likely ot have set cmd.config values directly
- # as well, at which point our saved original config is no longer up to date.
- @original_config = config.dup
- # other code may have a handle to the config object, so use Hash#replace to deliberately
- # update-in-place.
- config.replace(config_file_defaults.merge(default_config).merge(config_file_settings).merge(config))
- end
-
- #
- # Determine the source of a given configuration key
- #
- # @argument key [Symbol] a configuration key
- # @return [Symbol,NilClass] return the source of the config key,
- # one of:
- # - :cli - this was explicitly provided on the CLI
- # - :config - this came from Chef::Config[:knife] explicitly being set
- # - :cli_default - came from a declared CLI `option`'s `default` value.
- # - :config_default - this came from Chef::Config[:knife]'s defaults
- # - nil - if the key could not be found in any source.
- # This can happen when it is invalid, or has been
- # set directly into #config without then calling #merge_config
- def config_source(key)
- return :cli if @original_config.include? key
- return :config if config_file_settings.key? key
- return :cli_default if default_config.include? key
- return :config_default if config_file_defaults.key? key # must come after :config check
-
- nil
- end
-
- # Catch-all method that does any massaging needed for various config
- # components, such as expanding file paths and converting verbosity level
- # into log level.
- def apply_computed_config
- Chef::Config[:color] = config[:color]
-
- case Chef::Config[:verbosity]
- when 0, nil
- Chef::Config[:log_level] = :warn
- when 1
- Chef::Config[:log_level] = :info
- when 2
- Chef::Config[:log_level] = :debug
- else
- Chef::Config[:log_level] = :trace
- end
-
- Chef::Config[:log_level] = :trace if ENV["KNIFE_DEBUG"]
-
- Chef::Config[:node_name] = config[:node_name] if config[:node_name]
- Chef::Config[:client_key] = config[:client_key] if config[:client_key]
- Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
- Chef::Config[:environment] = config[:environment] if config[:environment]
-
- Chef::Config.local_mode = config[:local_mode] if config.key?(:local_mode)
-
- Chef::Config.listen = config[:listen] if config.key?(:listen)
-
- if Chef::Config.local_mode && !Chef::Config.key?(:cookbook_path) && !Chef::Config.key?(:chef_repo_path)
- Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
- end
- Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host]
- Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
-
- # Expand a relative path from the config directory. Config from command
- # line should already be expanded, and absolute paths will be unchanged.
- if Chef::Config[:client_key] && config[:config_file]
- Chef::Config[:client_key] = File.expand_path(Chef::Config[:client_key], File.dirname(config[:config_file]))
- end
-
- Mixlib::Log::Formatter.show_time = false
- Chef::Log.init(Chef::Config[:log_location])
- Chef::Log.level(Chef::Config[:log_level] || :error)
- end
-
- def configure_chef
- # knife needs to send logger output to STDERR by default
- Chef::Config[:log_location] = STDERR
- config_loader = self.class.load_config(config[:config_file], config[:profile])
- config[:config_file] = config_loader.config_location
-
- # For CLI options like `--config-option key=value`. These have to get
- # parsed and applied separately.
- extra_config_options = config.delete(:config_option)
-
- merge_configs
- apply_computed_config
-
- # This has to be after apply_computed_config so that Mixlib::Log is configured
- Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file]
-
- begin
- Chef::Config.apply_extra_config_options(extra_config_options)
- rescue ChefConfig::UnparsableConfigOption => e
- ui.error e.message
- show_usage
- exit(1)
- end
-
- Chef::Config.export_proxies
- end
-
- def show_usage
- stdout.puts("USAGE: " + opt_parser.to_s)
- end
-
- def run_with_pretty_exceptions(raise_exception = false)
- unless respond_to?(:run)
- ui.error "You need to add a #run method to your knife command before you can use it"
- end
- ENV["PATH"] = default_paths if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity]
- maybe_setup_fips
- Chef::LocalMode.with_server_connectivity do
- run
- end
- rescue Exception => e
- raise if raise_exception || ( Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 2 )
-
- humanize_exception(e)
- exit 100
- end
-
- def humanize_exception(e)
- case e
- when SystemExit
- raise # make sure exit passes through.
- when Net::HTTPClientException, Net::HTTPFatalError
- humanize_http_exception(e)
- when OpenSSL::SSL::SSLError
- ui.error "Could not establish a secure connection to the server."
- ui.info "Use `knife ssl check` to troubleshoot your SSL configuration."
- ui.info "If your server uses a self-signed certificate, you can use"
- ui.info "`knife ssl fetch` to make knife trust the server's certificates."
- ui.info ""
- ui.info "Original Exception: #{e.class.name}: #{e.message}"
- when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
- ui.error "Network Error: #{e.message}"
- ui.info "Check your knife configuration and network settings"
- when NameError, NoMethodError
- ui.error "knife encountered an unexpected error"
- ui.info "This may be a bug in the '#{self.class.common_name}' knife command or plugin"
- ui.info "Please collect the output of this command with the `-VVV` option before filing a bug report."
- ui.info "Exception: #{e.class.name}: #{e.message}"
- when Chef::Exceptions::PrivateKeyMissing
- ui.error "Your private key could not be loaded from #{api_key}"
- ui.info "Check your configuration file and ensure that your private key is readable"
- when Chef::Exceptions::InvalidRedirect
- ui.error "Invalid Redirect: #{e.message}"
- ui.info "Change your server location in config.rb/knife.rb to the server's FQDN to avoid unwanted redirections."
- else
- ui.error "#{e.class.name}: #{e.message}"
- end
- end
-
- def humanize_http_exception(e)
- response = e.response
- case response
- when Net::HTTPUnauthorized
- ui.error "Failed to authenticate to #{server_url} as #{username} with key #{api_key}"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPForbidden
- ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action."
- proxy_env_vars = ENV.to_hash.keys.map(&:downcase) & %w{http_proxy https_proxy ftp_proxy socks_proxy no_proxy}
- unless proxy_env_vars.empty?
- ui.error "There are proxy servers configured, your server url may need to be added to NO_PROXY."
- end
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPBadRequest
- ui.error "The data in your request was invalid"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPNotFound
- ui.error "The object you are looking for could not be found"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPInternalServerError
- ui.error "internal server error"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPBadGateway
- ui.error "bad gateway"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPServiceUnavailable
- ui.error "Service temporarily unavailable"
- ui.info "Response: #{format_rest_error(response)}"
- when Net::HTTPNotAcceptable
- version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
- client_api_version = version_header["request_version"]
- min_server_version = version_header["min_version"]
- max_server_version = version_header["max_version"]
- ui.error "The API version that Knife is using is not supported by the server you sent this request to."
- ui.info "The request that Knife sent was using API version #{client_api_version}."
- ui.info "The server you sent the request to supports a min API version of #{min_server_version} and a max API version of #{max_server_version}."
- ui.info "Please either update your #{ChefUtils::Dist::Infra::PRODUCT} or the server to be a compatible set."
- else
- ui.error response.message
- ui.info "Response: #{format_rest_error(response)}"
- end
- end
-
- def username
- Chef::Config[:node_name]
- end
-
- def api_key
- Chef::Config[:client_key]
- end
-
- # Parses JSON from the error response sent by Chef Server and returns the
- # error message
- #--
- # TODO: this code belongs in Chef::REST
- def format_rest_error(response)
- Array(Chef::JSONCompat.from_json(response.body)["error"]).join("; ")
- rescue Exception
- response.body
- end
-
- # FIXME: yard with @yield
- def create_object(object, pretty_name = nil, object_class: nil)
- output = if object_class
- edit_data(object, object_class: object_class)
- else
- edit_hash(object)
- end
-
- if Kernel.block_given?
- output = yield(output)
- else
- output.save
- end
-
- pretty_name ||= output
-
- msg("Created #{pretty_name}")
-
- output(output) if config[:print_after]
- end
-
- # FIXME: yard with @yield
- def delete_object(klass, name, delete_name = nil)
- confirm("Do you really want to delete #{name}")
-
- if Kernel.block_given?
- object = yield
- else
- object = klass.load(name)
- object.destroy
- end
-
- output(format_for_display(object)) if config[:print_after]
-
- obj_name = delete_name ? "#{delete_name}[#{name}]" : object
- msg("Deleted #{obj_name}")
- end
-
- # helper method for testing if a field exists
- # and returning the usage and proper error if not
- def test_mandatory_field(field, fieldname)
- if field.nil?
- show_usage
- ui.fatal("You must specify a #{fieldname}")
- exit 1
- end
- end
-
- def rest
- @rest ||= begin
- require_relative "server_api"
- Chef::ServerAPI.new(Chef::Config[:chef_server_url])
- end
- end
-
- def noauth_rest
- @rest ||= begin
- require_relative "http/simple_json"
- Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url])
- end
- end
-
- def server_url
- Chef::Config[:chef_server_url]
- end
-
- def maybe_setup_fips
- unless config[:fips].nil?
- Chef::Config[:fips] = config[:fips]
- end
- Chef::Config.init_openssl
- end
- end
-end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
deleted file mode 100644
index 1550c62dc1..0000000000
--- a/lib/chef/knife/bootstrap.rb
+++ /dev/null
@@ -1,1142 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "data_bag_secret_options"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-require "license_acceptance/cli_flags/mixlib_cli"
-module LicenseAcceptance
- autoload :Acceptor, "license_acceptance/acceptor"
-end
-
-class Chef
- class Knife
- class Bootstrap < Knife
- include DataBagSecretOptions
- include LicenseAcceptance::CLIFlags::MixlibCLI
-
- SUPPORTED_CONNECTION_PROTOCOLS ||= %w{ssh winrm}.freeze
- WINRM_AUTH_PROTOCOL_LIST ||= %w{plaintext kerberos ssl negotiate}.freeze
-
- # Common connectivity options
- option :connection_user,
- short: "-U USERNAME",
- long: "--connection-user USERNAME",
- description: "Authenticate to the target host with this user account."
-
- option :connection_password,
- short: "-P PASSWORD",
- long: "--connection-password PASSWORD",
- description: "Authenticate to the target host with this password."
-
- option :connection_port,
- short: "-p PORT",
- long: "--connection-port PORT",
- description: "The port on the target node to connect to."
-
- option :connection_protocol,
- short: "-o PROTOCOL",
- long: "--connection-protocol PROTOCOL",
- description: "The protocol to use to connect to the target node.",
- in: SUPPORTED_CONNECTION_PROTOCOLS
-
- option :max_wait,
- short: "-W SECONDS",
- long: "--max-wait SECONDS",
- description: "The maximum time to wait for the initial connection to be established."
-
- option :session_timeout,
- long: "--session-timeout SECONDS",
- description: "The number of seconds to wait for each connection operation to be acknowledged while running bootstrap.",
- default: 60
-
- # WinRM Authentication
- option :winrm_ssl_peer_fingerprint,
- long: "--winrm-ssl-peer-fingerprint FINGERPRINT",
- description: "SSL certificate fingerprint expected from the target."
-
- option :ca_trust_file,
- short: "-f CA_TRUST_PATH",
- long: "--ca-trust-file CA_TRUST_PATH",
- description: "The Certificate Authority (CA) trust file used for SSL transport."
-
- option :winrm_no_verify_cert,
- long: "--winrm-no-verify-cert",
- description: "Do not verify the SSL certificate of the target node for WinRM.",
- boolean: true
-
- option :winrm_ssl,
- long: "--winrm-ssl",
- description: "Use SSL in the WinRM connection."
-
- option :winrm_auth_method,
- short: "-w AUTH-METHOD",
- long: "--winrm-auth-method AUTH-METHOD",
- description: "The WinRM authentication method to use.",
- in: WINRM_AUTH_PROTOCOL_LIST
-
- option :winrm_basic_auth_only,
- long: "--winrm-basic-auth-only",
- description: "For WinRM basic authentication when using the 'ssl' auth method.",
- boolean: true
-
- # This option was provided in knife bootstrap windows winrm,
- # but it is ignored in knife-windows/WinrmSession, and so remains unimplemented here.
- # option :kerberos_keytab_file,
- # :short => "-T KEYTAB_FILE",
- # :long => "--keytab-file KEYTAB_FILE",
- # :description => "The Kerberos keytab file used for authentication"
-
- option :kerberos_realm,
- short: "-R KERBEROS_REALM",
- long: "--kerberos-realm KERBEROS_REALM",
- description: "The Kerberos realm used for authentication."
-
- option :kerberos_service,
- short: "-S KERBEROS_SERVICE",
- long: "--kerberos-service KERBEROS_SERVICE",
- description: "The Kerberos service used for authentication."
-
- ## SSH Authentication
- option :ssh_gateway,
- short: "-G GATEWAY",
- long: "--ssh-gateway GATEWAY",
- description: "The SSH gateway."
-
- option :ssh_gateway_identity,
- long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
- description: "The SSH identity file used for gateway authentication."
-
- option :ssh_forward_agent,
- short: "-A",
- long: "--ssh-forward-agent",
- description: "Enable SSH agent forwarding.",
- boolean: true
-
- option :ssh_identity_file,
- short: "-i IDENTITY_FILE",
- long: "--ssh-identity-file IDENTITY_FILE",
- description: "The SSH identity file used for authentication."
-
- option :ssh_verify_host_key,
- long: "--ssh-verify-host-key VALUE",
- description: "Verify host key. Default is 'always'.",
- in: %w{always accept_new accept_new_or_local_tunnel never},
- default: "always"
-
- #
- # bootstrap options
- #
-
- # client.rb content via chef-full/bootstrap_context
- option :bootstrap_version,
- long: "--bootstrap-version VERSION",
- description: "The version of #{ChefUtils::Dist::Infra::PRODUCT} to install."
-
- option :channel,
- long: "--channel CHANNEL",
- description: "Install from the given channel. Default is 'stable'.",
- default: "stable",
- in: %w{stable current unstable}
-
- # client.rb content via chef-full/bootstrap_context
- option :bootstrap_proxy,
- long: "--bootstrap-proxy PROXY_URL",
- description: "The proxy server for the node being bootstrapped."
-
- # client.rb content via bootstrap_context
- option :bootstrap_proxy_user,
- long: "--bootstrap-proxy-user PROXY_USER",
- description: "The proxy authentication username for the node being bootstrapped."
-
- # client.rb content via bootstrap_context
- option :bootstrap_proxy_pass,
- long: "--bootstrap-proxy-pass PROXY_PASS",
- description: "The proxy authentication password for the node being bootstrapped."
-
- # client.rb content via bootstrap_context
- option :bootstrap_no_proxy,
- long: "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
- description: "Do not proxy locations for the node being bootstrapped"
-
- # client.rb content via bootstrap_context
- option :bootstrap_template,
- short: "-t TEMPLATE",
- long: "--bootstrap-template TEMPLATE",
- description: "Bootstrap #{ChefUtils::Dist::Infra::PRODUCT} using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
-
- # client.rb content via bootstrap_context
- option :node_ssl_verify_mode,
- long: "--node-ssl-verify-mode [peer|none]",
- description: "Whether or not to verify the SSL cert for all HTTPS requests.",
- proc: Proc.new { |v|
- valid_values = %w{none peer}
- unless valid_values.include?(v)
- raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
- end
-
- v
- }
-
- # bootstrap_context - client.rb
- option :node_verify_api_cert,
- long: "--[no-]node-verify-api-cert",
- description: "Verify the SSL cert for HTTPS requests to the #{ChefUtils::Dist::Server::PRODUCT} API.",
- boolean: true
-
- # runtime - sudo settings (train handles sudo)
- option :use_sudo,
- long: "--sudo",
- description: "Execute the bootstrap via sudo.",
- boolean: true
-
- # runtime - sudo settings (train handles sudo)
- option :preserve_home,
- long: "--sudo-preserve-home",
- description: "Preserve non-root user HOME environment variable with sudo.",
- boolean: true
-
- # runtime - sudo settings (train handles sudo)
- option :use_sudo_password,
- long: "--use-sudo-password",
- description: "Execute the bootstrap via sudo with password.",
- boolean: false
-
- # runtime - client_builder
- option :chef_node_name,
- short: "-N NAME",
- long: "--node-name NAME",
- description: "The node name for your new node."
-
- # runtime - client_builder - set runlist when creating node
- option :run_list,
- short: "-r RUN_LIST",
- long: "--run-list RUN_LIST",
- description: "Comma separated list of roles/recipes to apply.",
- proc: lambda { |o| o.split(/[\s,]+/) },
- default: []
-
- # runtime - client_builder - set policy name when creating node
- option :policy_name,
- long: "--policy-name POLICY_NAME",
- description: "Policyfile name to use (--policy-group must also be given).",
- default: nil
-
- # runtime - client_builder - set policy group when creating node
- option :policy_group,
- long: "--policy-group POLICY_GROUP",
- description: "Policy group name to use (--policy-name must also be given).",
- default: nil
-
- # runtime - client_builder - node tags
- option :tags,
- long: "--tags TAGS",
- description: "Comma separated list of tags to apply to the node.",
- proc: lambda { |o| o.split(/[\s,]+/) },
- default: []
-
- # bootstrap template
- option :first_boot_attributes,
- short: "-j JSON_ATTRIBS",
- long: "--json-attributes",
- description: "A JSON string to be added to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
- proc: lambda { |o| Chef::JSONCompat.parse(o) },
- default: nil
-
- # bootstrap template
- option :first_boot_attributes_from_file,
- long: "--json-attribute-file FILE",
- description: "A JSON file to be used to the first run of #{ChefUtils::Dist::Infra::CLIENT}.",
- proc: lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
- default: nil
-
- # bootstrap template
- # Create ohai hints in /etc/chef/ohai/hints, fname=hintname, content=value
- option :hints,
- long: "--hint HINT_NAME[=HINT_FILE]",
- description: "Specify an Ohai hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
- proc: Proc.new { |hint, accumulator|
- accumulator ||= {}
- name, path = hint.split("=", 2)
- accumulator[name] = path ? Chef::JSONCompat.parse(::File.read(path)) : {}
- accumulator
- }
-
- # bootstrap override: url of a an installer shell script to use in place of omnitruck
- # Note that the bootstrap template _only_ references this out of Chef::Config, and not from
- # the provided options to knife bootstrap, so we set the Chef::Config option here.
- option :bootstrap_url,
- long: "--bootstrap-url URL",
- description: "URL to a custom installation script."
-
- option :bootstrap_product,
- long: "--bootstrap-product PRODUCT",
- description: "Product to install.",
- default: "chef"
-
- option :msi_url, # Windows target only
- short: "-m URL",
- long: "--msi-url URL",
- description: "Location of the #{ChefUtils::Dist::Infra::PRODUCT} MSI. The default templates will prefer to download from this location. The MSI will be downloaded from #{ChefUtils::Dist::Org::WEBSITE} if not provided (Windows).",
- default: ""
-
- # bootstrap override: Do this instead of our own setup.sh from omnitruck. Causes bootstrap_url to be ignored.
- option :bootstrap_install_command,
- long: "--bootstrap-install-command COMMANDS",
- description: "Custom command to install #{ChefUtils::Dist::Infra::PRODUCT}."
-
- # bootstrap template: Run this command first in the bootstrap script
- option :bootstrap_preinstall_command,
- long: "--bootstrap-preinstall-command COMMANDS",
- description: "Custom commands to run before installing #{ChefUtils::Dist::Infra::PRODUCT}."
-
- # bootstrap template
- option :bootstrap_wget_options,
- long: "--bootstrap-wget-options OPTIONS",
- description: "Add options to wget when installing #{ChefUtils::Dist::Infra::PRODUCT}."
-
- # bootstrap template
- option :bootstrap_curl_options,
- long: "--bootstrap-curl-options OPTIONS",
- description: "Add options to curl when install #{ChefUtils::Dist::Infra::PRODUCT}."
-
- # chef_vault_handler
- option :bootstrap_vault_file,
- long: "--bootstrap-vault-file VAULT_FILE",
- description: "A JSON file with a list of vault(s) and item(s) to be updated."
-
- # chef_vault_handler
- option :bootstrap_vault_json,
- long: "--bootstrap-vault-json VAULT_JSON",
- description: "A JSON string with the vault(s) and item(s) to be updated."
-
- # chef_vault_handler
- option :bootstrap_vault_item,
- long: "--bootstrap-vault-item VAULT_ITEM",
- description: 'A single vault and item to update as "vault:item".',
- proc: Proc.new { |i, accumulator|
- (vault, item) = i.split(":")
- accumulator ||= {}
- accumulator[vault] ||= []
- accumulator[vault].push(item)
- accumulator
- }
-
- # Deprecated options. These must be declared after
- # regular options because they refer to the replacement
- # option definitions implicitly.
- deprecated_option :auth_timeout,
- replacement: :max_wait,
- long: "--max-wait SECONDS"
-
- deprecated_option :forward_agent,
- replacement: :ssh_forward_agent,
- boolean: true, long: "--forward-agent"
-
- deprecated_option :host_key_verify,
- replacement: :ssh_verify_host_key,
- boolean: true, long: "--[no-]host-key-verify",
- value_mapper: Proc.new { |verify| verify ? "always" : "never" }
-
- deprecated_option :prerelease,
- replacement: :channel,
- long: "--prerelease",
- boolean: true, value_mapper: Proc.new { "current" }
-
- deprecated_option :ssh_user,
- replacement: :connection_user,
- long: "--ssh-user USERNAME"
-
- deprecated_option :ssh_password,
- replacement: :connection_password,
- long: "--ssh-password PASSWORD"
-
- deprecated_option :ssh_port,
- replacement: :connection_port,
- long: "--ssh-port PASSWORD"
-
- deprecated_option :ssl_peer_fingerprint,
- replacement: :winrm_ssl_peer_fingerprint,
- long: "--ssl-peer-fingerprint FINGERPRINT"
-
- deprecated_option :winrm_user,
- replacement: :connection_user,
- long: "--winrm-user USERNAME", short: "-x USERNAME"
-
- deprecated_option :winrm_password,
- replacement: :connection_password,
- long: "--winrm-password PASSWORD"
-
- deprecated_option :winrm_port,
- replacement: :connection_port,
- long: "--winrm-port PORT"
-
- deprecated_option :winrm_authentication_protocol,
- replacement: :winrm_auth_method,
- long: "--winrm-authentication-protocol PROTOCOL"
-
- deprecated_option :winrm_session_timeout,
- replacement: :session_timeout,
- long: "--winrm-session-timeout MINUTES"
-
- deprecated_option :winrm_ssl_verify_mode,
- replacement: :winrm_no_verify_cert,
- long: "--winrm-ssl-verify-mode MODE"
-
- deprecated_option :winrm_transport, replacement: :winrm_ssl,
- long: "--winrm-transport TRANSPORT",
- value_mapper: Proc.new { |value| value == "ssl" }
-
- attr_reader :connection
-
- deps do
- require "erubis" unless defined?(Erubis)
-
- require "net/ssh" unless defined?(Net::SSH)
- require_relative "../json_compat"
- require_relative "../util/path_helper"
- require_relative "bootstrap/chef_vault_handler"
- require_relative "bootstrap/client_builder"
- require_relative "bootstrap/train_connector"
- end
-
- banner "knife bootstrap [PROTOCOL://][USER@]FQDN (options)"
-
- def client_builder
- @client_builder ||= Chef::Knife::Bootstrap::ClientBuilder.new(
- chef_config: Chef::Config,
- config: config,
- ui: ui
- )
- end
-
- def chef_vault_handler
- @chef_vault_handler ||= Chef::Knife::Bootstrap::ChefVaultHandler.new(
- config: config,
- ui: ui
- )
- end
-
- # Determine if we need to accept the Chef Infra license locally in order to successfully bootstrap
- # the remote node. Remote 'chef-client' run will fail if it is >= 15 and the license is not accepted locally.
- def check_license
- Chef::Log.debug("Checking if we need to accept Chef license to bootstrap node")
- version = config[:bootstrap_version] || Chef::VERSION.split(".").first
- acceptor = LicenseAcceptance::Acceptor.new(logger: Chef::Log, provided: Chef::Config[:chef_license])
- if acceptor.license_required?("chef", version)
- Chef::Log.debug("License acceptance required for chef version: #{version}")
- license_id = acceptor.id_from_mixlib("chef")
- acceptor.check_and_persist(license_id, version)
- Chef::Config[:chef_license] ||= acceptor.acceptance_value
- end
- end
-
- # The default bootstrap template to use to bootstrap a server.
- # This is a public API hook which knife plugins use or inherit and override.
- #
- # @return [String] Default bootstrap template
- def default_bootstrap_template
- if connection.windows?
- "windows-chef-client-msi"
- else
- "chef-full"
- end
- end
-
- def host_descriptor
- Array(@name_args).first
- end
-
- # The server_name is the DNS or IP we are going to connect to, it is not necessarily
- # the node name, the fqdn, or the hostname of the server. This is a public API hook
- # which knife plugins use or inherit and override.
- #
- # @return [String] The DNS or IP that bootstrap will connect to
- def server_name
- if host_descriptor
- @server_name ||= host_descriptor.split("@").reverse[0]
- end
- end
-
- # @return [String] The CLI specific bootstrap template or the default
- def bootstrap_template
- # Allow passing a bootstrap template or use the default
- config[:bootstrap_template] || default_bootstrap_template
- end
-
- def find_template
- template = bootstrap_template
-
- # Use the template directly if it's a path to an actual file
- if File.exist?(template)
- Chef::Log.trace("Using the specified bootstrap template: #{File.dirname(template)}")
- return template
- end
-
- # Otherwise search the template directories until we find the right one
- bootstrap_files = []
- bootstrap_files << File.join(__dir__, "bootstrap/templates", "#{template}.erb")
- bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
- Chef::Util::PathHelper.home(".chef", "bootstrap", "#{template}.erb") { |p| bootstrap_files << p }
- bootstrap_files << Gem.find_files(File.join("chef", "knife", "bootstrap", "#{template}.erb"))
- bootstrap_files.flatten!
-
- template_file = Array(bootstrap_files).find do |bootstrap_template|
- Chef::Log.trace("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
- File.exist?(bootstrap_template)
- end
-
- unless template_file
- ui.info("Can not find bootstrap definition for #{template}")
- raise Errno::ENOENT
- end
-
- Chef::Log.trace("Found bootstrap template: #{template_file}")
-
- template_file
- end
-
- def secret
- @secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
- end
-
- # Establish bootstrap context for template rendering.
- # Requires connection to be a live connection in order to determine
- # the correct platform.
- def bootstrap_context
- @bootstrap_context ||=
- if connection.windows?
- require_relative "core/windows_bootstrap_context"
- Knife::Core::WindowsBootstrapContext.new(config, config[:run_list], Chef::Config, secret)
- else
- require_relative "core/bootstrap_context"
- Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret)
- end
- end
-
- def first_boot_attributes
- @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {}
- end
-
- def render_template
- @config[:first_boot_attributes] = first_boot_attributes
- template_file = find_template
- template = IO.read(template_file).chomp
- Erubis::Eruby.new(template).evaluate(bootstrap_context)
- end
-
- def run
- check_license if ChefUtils::Dist::Org::ENFORCE_LICENSE
-
- plugin_setup!
- validate_name_args!
- validate_protocol!
- validate_first_boot_attributes!
- validate_winrm_transport_opts!
- validate_policy_options!
- plugin_validate_options!
-
- winrm_warn_no_ssl_verification
- warn_on_short_session_timeout
-
- plugin_create_instance!
- $stdout.sync = true
- connect!
- register_client
-
- content = render_template
- bootstrap_path = upload_bootstrap(content)
- perform_bootstrap(bootstrap_path)
- plugin_finalize
- ensure
- connection.del_file!(bootstrap_path) if connection && bootstrap_path
- end
-
- def register_client
- # chef-vault integration must use the new client-side hawtness, otherwise to use the
- # new client-side hawtness, just delete your validation key.
- if chef_vault_handler.doing_chef_vault? ||
- (Chef::Config[:validation_key] &&
- !File.exist?(File.expand_path(Chef::Config[:validation_key])))
-
- unless config[:chef_node_name]
- ui.error("You must pass a node name with -N when bootstrapping with user credentials")
- exit 1
- end
- client_builder.run
- chef_vault_handler.run(client_builder.client)
-
- bootstrap_context.client_pem = client_builder.client_path
- else
- ui.warn "Performing legacy client registration with the validation key at #{Chef::Config[:validation_key]}..."
- ui.warn "Remove the key file or remove the 'validation_key' configuration option from your config.rb (knife.rb) to use more secure user credentials for client registration."
- end
- end
-
- def perform_bootstrap(remote_bootstrap_script_path)
- ui.info("Bootstrapping #{ui.color(server_name, :bold)}")
- cmd = bootstrap_command(remote_bootstrap_script_path)
- r = connection.run_command(cmd) do |data|
- ui.msg("#{ui.color(" [#{connection.hostname}]", :cyan)} #{data}")
- end
- if r.exit_status != 0
- ui.error("The following error occurred on #{server_name}:")
- ui.error(r.stderr)
- exit 1
- end
- end
-
- def connect!
- ui.info("Connecting to #{ui.color(server_name, :bold)} using #{connection_protocol}")
- opts ||= connection_opts.dup
- do_connect(opts)
- rescue Train::Error => e
- # We handle these by message text only because train only loads the
- # transports and protocols that it needs - so the exceptions may not be defined,
- # and we don't want to require files internal to train.
- if e.message =~ /fingerprint (\S+) is unknown for "(.+)"/ # Train::Transports::SSHFailed
- fingerprint = $1
- hostname, ip = $2.split(",")
- # TODO: convert the SHA256 base64 value to hex with colons
- # 'ssh' example output:
- # RSA key fingerprint is e5:cb:c0:e2:21:3b:12:52:f8:ce:cb:00:24:e2:0c:92.
- # ECDSA key fingerprint is 5d:67:61:08:a9:d7:01:fd:5e:ae:7e:09:40:ef:c0:3c.
- # will exit 3 on N
- ui.confirm <<~EOM
- The authenticity of host '#{hostname} (#{ip})' can't be established.
- fingerprint is #{fingerprint}.
-
- Are you sure you want to continue connecting
- EOM
- # FIXME: this should save the key to known_hosts but doesn't appear to be
- config[:ssh_verify_host_key] = :accept_new
- conn_opts = connection_opts(reset: true)
- opts.merge! conn_opts
- retry
- elsif (ssh? && e.cause && e.cause.class == Net::SSH::AuthenticationFailed) || (ssh? && e.class == Train::ClientError && e.reason == :no_ssh_password_or_key_available)
- if connection.password_auth?
- raise
- else
- ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
- password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
- end
-
- opts.merge! force_ssh_password_opts(password)
- retry
- else
- raise
- end
- rescue RuntimeError => e
- if winrm? && e.message == "password is a required option"
- if connection.password_auth?
- raise
- else
- ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
- password = ui.ask("Enter password for #{opts[:user]}@#{server_name}:", echo: false)
- end
-
- opts.merge! force_winrm_password_opts(password)
- retry
- else
- raise
- end
- end
-
- def handle_ssh_error(e); end
-
- # url values override CLI flags, if you provide both
- # we'll use the one that you gave in the URL.
- def connection_protocol
- return @connection_protocol if @connection_protocol
-
- from_url = host_descriptor =~ %r{^(.*)://} ? $1 : nil
- from_knife = config[:connection_protocol]
- @connection_protocol = from_url || from_knife || "ssh"
- end
-
- def do_connect(conn_options)
- @connection = TrainConnector.new(host_descriptor, connection_protocol, conn_options)
- connection.connect!
- rescue Train::UserError => e
- limit ||= 1
- if !conn_options.key?(:pty) && e.reason == :sudo_no_tty
- ui.warn("#{e.message} - trying with pty request")
- conn_options[:pty] = true # ensure we can talk to systems with requiretty set true in sshd config
- retry
- elsif config[:use_sudo_password] && (e.reason == :sudo_password_required || e.reason == :bad_sudo_password) && limit < 3
- ui.warn("Failed to authenticate #{conn_options[:user]} to #{server_name} - #{e.message} \n sudo: #{limit} incorrect password attempt")
- sudo_password = ui.ask("Enter sudo password for #{conn_options[:user]}@#{server_name}:", echo: false)
- limit += 1
- conn_options[:sudo_password] = sudo_password
-
- retry
- else
- raise
- end
- end
-
- # Fail if both first_boot_attributes and first_boot_attributes_from_file
- # are set.
- def validate_first_boot_attributes!
- if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
- raise Chef::Exceptions::BootstrapCommandInputError
- end
-
- true
- end
-
- # FIXME: someone needs to clean this up properly: https://github.com/chef/chef/issues/9645
- # This code is deliberately left without an abstraction around deprecating the config options to avoid knife plugins from
- # using those methods (which will need to be deprecated and break them) via inheritance (ruby does not have a true `private`
- # so the lack of any inheritable implementation is because of that).
- #
- def winrm_auth_method
- config.key?(:winrm_auth_method) ? config[:winrm_auth_method] : config.key?(:winrm_authentications_protocol) ? config[:winrm_authentication_protocol] : "negotiate" # rubocop:disable Style/NestedTernaryOperator
- end
-
- def ssh_verify_host_key
- config.key?(:ssh_verify_host_key) ? config[:ssh_verify_host_key] : config.key?(:host_key_verify) ? config[:host_key_verify] : "always" # rubocop:disable Style/NestedTernaryOperator
- end
-
- # Fail if using plaintext auth without ssl because
- # this can expose keys in plaintext on the wire.
- # TODO test for this method
- # TODO check that the protocol is valid.
- def validate_winrm_transport_opts!
- return true unless winrm?
-
- if Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))
- if winrm_auth_method == "plaintext" &&
- config[:winrm_ssl] != true
- ui.error <<~EOM
- Validatorless bootstrap over unsecure winrm channels could expose your
- key to network sniffing.
- Please use a 'winrm_auth_method' other than 'plaintext',
- or enable ssl on #{server_name} then use the ---winrm-ssl flag
- to connect.
- EOM
-
- exit 1
- end
- end
- true
- end
-
- # fail if the server_name is nil
- def validate_name_args!
- if server_name.nil?
- ui.error("Must pass an FQDN or ip to bootstrap")
- exit 1
- end
- end
-
- # Ensure options are valid by checking policyfile values.
- #
- # The method call will cause the program to exit(1) if:
- # * Only one of --policy-name and --policy-group is specified
- # * Policyfile options are set and --run-list is set as well
- #
- # @return [TrueClass] If options are valid.
- def validate_policy_options!
- if incomplete_policyfile_options?
- ui.error("--policy-name and --policy-group must be specified together")
- exit 1
- elsif policyfile_and_run_list_given?
- ui.error("Policyfile options and --run-list are exclusive")
- exit 1
- end
- end
-
- # Ensure a valid protocol is provided for target host connection
- #
- # The method call will cause the program to exit(1) if:
- # * Conflicting protocols are given via the target URI and the --protocol option
- # * The protocol is not a supported protocol
- #
- # @return [TrueClass] If options are valid.
- def validate_protocol!
- from_cli = config[:connection_protocol]
- if from_cli && connection_protocol != from_cli
- # Hanging indent to align with the ERROR: prefix
- ui.error <<~EOM
- The URL '#{host_descriptor}' indicates protocol is '#{connection_protocol}'
- while the --protocol flag specifies '#{from_cli}'. Please include
- only one or the other.
- EOM
- exit 1
- end
-
- unless SUPPORTED_CONNECTION_PROTOCOLS.include?(connection_protocol)
- ui.error <<~EOM
- Unsupported protocol '#{connection_protocol}'.
-
- Supported protocols are: #{SUPPORTED_CONNECTION_PROTOCOLS.join(" ")}
- EOM
- exit 1
- end
- true
- end
-
- # Validate any additional options
- #
- # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to validate any additional options before any other actions are executed
- #
- # @return [TrueClass] If options are valid or exits
- def plugin_validate_options!
- true
- end
-
- # Create the server that we will bootstrap, if necessary
- #
- # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to call out to an API to build an instance of the server we wish to bootstrap
- #
- # @return [TrueClass] If instance successfully created, or exits
- def plugin_create_instance!
- true
- end
-
- # Perform any setup necessary by the plugin
- #
- # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to create connection objects
- #
- # @return [TrueClass] If instance successfully created, or exits
- def plugin_setup!; end
-
- # Perform any teardown or cleanup necessary by the plugin
- #
- # Plugins that subclass bootstrap, e.g. knife-ec2, can use this method to display a message or perform any cleanup
- #
- # @return [void]
- def plugin_finalize; end
-
- # If session_timeout is too short, it is likely
- # a holdover from "--winrm-session-timeout" which used
- # minutes as its unit, instead of seconds.
- # Warn the human so that they are not surprised.
- #
- def warn_on_short_session_timeout
- if session_timeout && session_timeout <= 15
- ui.warn <<~EOM
- You provided '--session-timeout #{session_timeout}' second(s).
- Did you mean '--session-timeout #{session_timeout * 60}' seconds?
- EOM
- end
- end
-
- def winrm_warn_no_ssl_verification
- return unless winrm?
-
- # REVIEWER NOTE
- # The original check from knife plugin did not include winrm_ssl_peer_fingerprint
- # Reference:
- # https://github.com/chef/knife-windows/blob/92d151298142be4a4750c5b54bb264f8d5b81b8a/lib/chef/knife/winrm_knife_base.rb#L271-L273
- # TODO Seems like we should also do a similar warning if ssh_verify_host == false
- if config[:ca_trust_file].nil? &&
- config[:winrm_no_verify_cert] &&
- config[:winrm_ssl_peer_fingerprint].nil?
- ui.warn <<~WARN
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- SSL validation of HTTPS requests for the WinRM transport is disabled.
- HTTPS WinRM connections are still encrypted, but knife is not able
- to detect forged replies or spoofing attacks.
-
- To work around this issue you can use the flag `--winrm-no-verify-cert`
- or add an entry like this to your knife configuration file:
-
- # Verify all WinRM HTTPS connections
- knife[:winrm_no_verify_cert] = true
-
- You can also specify a ca_trust_file via --ca-trust-file,
- or the expected fingerprint of the target host's certificate
- via --winrm-ssl-peer-fingerprint.
- WARN
- end
- end
-
- # @return a configuration hash suitable for connecting to the remote
- # host via train
- def connection_opts(reset: false)
- return @connection_opts unless @connection_opts.nil? || reset == true
-
- @connection_opts = {}
- @connection_opts.merge! base_opts
- @connection_opts.merge! host_verify_opts
- @connection_opts.merge! gateway_opts
- @connection_opts.merge! sudo_opts
- @connection_opts.merge! winrm_opts
- @connection_opts.merge! ssh_opts
- @connection_opts.merge! ssh_identity_opts
- @connection_opts
- end
-
- def winrm?
- connection_protocol == "winrm"
- end
-
- def ssh?
- connection_protocol == "ssh"
- end
-
- # Common configuration for all protocols
- def base_opts
- port = config_for_protocol(:port)
- user = config_for_protocol(:user)
- {}.tap do |opts|
- opts[:logger] = Chef::Log
- opts[:password] = config[:connection_password] if config.key?(:connection_password)
- opts[:user] = user if user
- opts[:max_wait_until_ready] = config[:max_wait].to_f unless config[:max_wait].nil?
- # TODO - when would we need to provide rdp_port vs port? Or are they not mutually exclusive?
- opts[:port] = port if port
- end
- end
-
- def host_verify_opts
- if winrm?
- { self_signed: config[:winrm_no_verify_cert] === true }
- elsif ssh?
- # Fall back to the old knife config key name for back compat.
- { verify_host_key: ssh_verify_host_key }
- else
- {}
- end
- end
-
- def ssh_opts
- opts = {}
- return opts if winrm?
-
- opts[:non_interactive] = true # Prevent password prompts from underlying net/ssh
- opts[:forward_agent] = (config[:ssh_forward_agent] === true)
- opts[:connection_timeout] = session_timeout
- opts
- end
-
- def ssh_identity_opts
- opts = {}
- return opts if winrm?
-
- identity_file = config[:ssh_identity_file]
- if identity_file
- opts[:key_files] = [identity_file]
- # We only set keys_only based on the explicit ssh_identity_file;
- # someone may use a gateway key and still expect password auth
- # on the target. Similarly, someone may have a default key specified
- # in knife config, but have provided a password on the CLI.
-
- # REVIEW NOTE: this is a new behavior. Originally, ssh_identity_file
- # could only be populated from CLI options, so there was no need to check
- # for this. We will also set keys_only to false only if there are keys
- # and no password.
- # If both are present, train(via net/ssh) will prefer keys, falling back to password.
- # Reference: https://github.com/chef/chef/blob/master/lib/chef/knife/ssh.rb#L272
- opts[:keys_only] = config.key?(:connection_password) == false
- else
- opts[:key_files] = []
- opts[:keys_only] = false
- end
-
- gateway_identity_file = config[:ssh_gateway] ? config[:ssh_gateway_identity] : nil
- unless gateway_identity_file.nil?
- opts[:key_files] << gateway_identity_file
- end
-
- opts
- end
-
- def gateway_opts
- opts = {}
- if config[:ssh_gateway]
- split = config[:ssh_gateway].split("@", 2)
- if split.length == 1
- gw_host = split[0]
- else
- gw_user = split[0]
- gw_host = split[1]
- end
- gw_host, gw_port = gw_host.split(":", 2)
- # TODO - validate convertible port in config validation?
- gw_port = Integer(gw_port) rescue nil
- opts[:bastion_host] = gw_host
- opts[:bastion_user] = gw_user
- opts[:bastion_port] = gw_port
- end
- opts
- end
-
- # use_sudo - tells bootstrap to use the sudo command to run bootstrap
- # use_sudo_password - tells bootstrap to use the sudo command to run bootstrap
- # and to use the password specified with --password
- # TODO: I'd like to make our sudo options sane:
- # --sudo (bool) - use sudo
- # --sudo-password PASSWORD (default: :password) - use this password for sudo
- # --sudo-options "opt,opt,opt" to pass into sudo
- # --sudo-command COMMAND sudo command other than sudo
- # REVIEW NOTE: knife bootstrap did not pull sudo values from Chef::Config,
- # should we change that for consistency?
- def sudo_opts
- return {} if winrm?
-
- opts = { sudo: false }
- if config[:use_sudo]
- opts[:sudo] = true
- if config[:use_sudo_password]
- opts[:sudo_password] = config[:connection_password]
- end
- if config[:preserve_home]
- opts[:sudo_options] = "-H"
- end
- end
- opts
- end
-
- def winrm_opts
- return {} unless winrm?
-
- opts = {
- winrm_transport: winrm_auth_method, # winrm gem and train calls auth method 'transport'
- winrm_basic_auth_only: config[:winrm_basic_auth_only] || false,
- ssl: config[:winrm_ssl] === true,
- ssl_peer_fingerprint: config[:winrm_ssl_peer_fingerprint],
- }
-
- if winrm_auth_method == "kerberos"
- opts[:kerberos_service] = config[:kerberos_service] if config[:kerberos_service]
- opts[:kerberos_realm] = config[:kerberos_realm] if config[:kerberos_service]
- end
-
- if config[:ca_trust_file]
- opts[:ca_trust_path] = config[:ca_trust_file]
- end
-
- opts[:operation_timeout] = session_timeout
-
- opts
- end
-
- # Config overrides to force password auth.
- def force_ssh_password_opts(password)
- {
- password: password,
- non_interactive: false,
- keys_only: false,
- key_files: [],
- auth_methods: %i{password keyboard_interactive},
- }
- end
-
- def force_winrm_password_opts(password)
- {
- password: password,
- }
- end
-
- # This is for deprecating config options. The fallback_key can be used
- # to pull an old knife config option out of the config file when the
- # cli value has been renamed. This is different from the deprecated
- # cli values, since these are for config options that have no corresponding
- # cli value.
- #
- # DO NOT USE - this whole API is considered deprecated
- #
- # @api deprecated
- #
- def config_value(key, fallback_key = nil, default = nil)
- Chef.deprecated(:knife_bootstrap_apis, "Use of config_value is deprecated. Knife plugin authors should access the config hash directly, which does correct merging of cli and config options.")
- if config.key?(key)
- # the first key is the primary key so we check the merged hash first
- config[key]
- elsif config.key?(fallback_key)
- # we get the old config option here (the deprecated cli option shouldn't exist)
- config[fallback_key]
- else
- default
- end
- end
-
- def upload_bootstrap(content)
- script_name = connection.windows? ? "bootstrap.bat" : "bootstrap.sh"
- remote_path = connection.normalize_path(File.join(connection.temp_dir, script_name))
- connection.upload_file_content!(content, remote_path)
- remote_path
- end
-
- # build the command string for bootstrapping
- # @return String
- def bootstrap_command(remote_path)
- if connection.windows?
- "cmd.exe /C #{remote_path}"
- else
- "sh #{remote_path}"
- end
- end
-
- private
-
- # To avoid cluttering the CLI options, some flags (such as port and user)
- # are shared between protocols. However, there is still a need to allow the operator
- # to specify defaults separately, since they may not be the same values for different
- # protocols.
-
- # These keys are available in Chef::Config, and are prefixed with the protocol name.
- # For example, :user CLI option will map to :winrm_user and :ssh_user Chef::Config keys,
- # based on the connection protocol in use.
-
- # @api private
- def config_for_protocol(option)
- if option == :port
- config[:connection_port] || config[knife_key_for_protocol(option)]
- else
- config[:connection_user] || config[knife_key_for_protocol(option)]
- end
- end
-
- # @api private
- def knife_key_for_protocol(option)
- "#{connection_protocol}_#{option}".to_sym
- end
-
- # True if policy_name and run_list are both given
- def policyfile_and_run_list_given?
- run_list_given? && policyfile_options_given?
- end
-
- def run_list_given?
- !config[:run_list].nil? && !config[:run_list].empty?
- end
-
- def policyfile_options_given?
- !!config[:policy_name]
- end
-
- # True if one of policy_name or policy_group was given, but not both
- def incomplete_policyfile_options?
- (!!config[:policy_name] ^ config[:policy_group])
- end
-
- # session_timeout option has a default that may not arrive, particularly if
- # we're being invoked from a plugin that doesn't merge_config.
- def session_timeout
- timeout = config[:session_timeout]
- return options[:session_timeout][:default] if timeout.nil?
-
- timeout.to_i
- end
- end
- end
-end
diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb
deleted file mode 100644
index 20759d6fdf..0000000000
--- a/lib/chef/knife/bootstrap/chef_vault_handler.rb
+++ /dev/null
@@ -1,162 +0,0 @@
-#
-# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright (c) 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.
-#
-class Chef
- class Knife
- class Bootstrap < Knife
- class ChefVaultHandler
-
- # @return [Hash] knife merged config, typically @config
- attr_accessor :config
-
- # @return [Chef::Knife::UI] ui object for output
- attr_accessor :ui
-
- # @return [Chef::ApiClient] vault client
- attr_reader :client
-
- # @param config [Hash] knife merged config, typically @config
- # @param ui [Chef::Knife::UI] ui object for output
- def initialize(config: {}, knife_config: nil, ui: nil)
- @config = config
- unless knife_config.nil?
- @config = knife_config
- Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
- end
- @ui = ui
- end
-
- # Updates the chef vault items for the newly created client.
- #
- # @param client [Chef::ApiClient] vault client
- def run(client)
- return unless doing_chef_vault?
-
- sanity_check
-
- @client = client
-
- update_bootstrap_vault_json!
- end
-
- # Iterate through all the vault items to update. Items may be either a String
- # or an Array of Strings:
- #
- # {
- # "vault1": "item",
- # "vault2": [ "item1", "item2", "item2" ]
- # }
- #
- def update_bootstrap_vault_json!
- vault_json.each do |vault, items|
- [ items ].flatten.each do |item|
- update_vault(vault, item)
- end
- end
- end
-
- # @return [Boolean] if we've got chef vault options to act on or not
- def doing_chef_vault?
- !!(bootstrap_vault_json || bootstrap_vault_file || bootstrap_vault_item)
- end
-
- private
-
- # warn if the user has given mutual conflicting options
- def sanity_check
- if bootstrap_vault_item && (bootstrap_vault_json || bootstrap_vault_file)
- ui.warn "--vault-item given with --vault-list or --vault-file, ignoring the latter"
- end
-
- if bootstrap_vault_json && bootstrap_vault_file
- ui.warn "--vault-list given with --vault-file, ignoring the latter"
- end
- end
-
- # @return [String] string with serialized JSON representing the chef vault items
- def bootstrap_vault_json
- config[:bootstrap_vault_json]
- end
-
- # @return [String] JSON text in a file representing the chef vault items
- def bootstrap_vault_file
- config[:bootstrap_vault_file]
- end
-
- # @return [Hash] Ruby object representing the chef vault items to create
- def bootstrap_vault_item
- config[:bootstrap_vault_item]
- end
-
- # Helper to return a ruby object representing all the data bags and items
- # to update via chef-vault.
- #
- # @return [Hash] deserialized ruby hash with all the vault items
- def vault_json
- @vault_json ||=
- begin
- if bootstrap_vault_item
- bootstrap_vault_item
- else
- json = bootstrap_vault_json || File.read(bootstrap_vault_file)
- Chef::JSONCompat.from_json(json)
- end
- end
- end
-
- # Update an individual vault item and save it
- #
- # @param vault [String] name of the chef-vault encrypted data bag
- # @param item [String] name of the chef-vault encrypted item
- def update_vault(vault, item)
- require_chef_vault!
- bootstrap_vault_item = load_chef_bootstrap_vault_item(vault, item)
- bootstrap_vault_item.clients(client)
- bootstrap_vault_item.save
- end
-
- # Hook to stub out ChefVault
- #
- # @param vault [String] name of the chef-vault encrypted data bag
- # @param item [String] name of the chef-vault encrypted item
- # @return [ChefVault::Item] ChefVault::Item object
- def load_chef_bootstrap_vault_item(vault, item)
- ChefVault::Item.load(vault, item)
- end
-
- public :load_chef_bootstrap_vault_item # for stubbing
-
- # Helper to very lazily require the chef-vault gem
- def require_chef_vault!
- @require_chef_vault ||=
- begin
- error_message = "Knife bootstrap requires version 2.6.0 or higher of the chef-vault gem to configure vault items"
- require "chef-vault"
- if Gem::Version.new(ChefVault::VERSION) < Gem::Version.new("2.6.0")
- raise error_message
- end
-
- true
- rescue LoadError
- raise error_message
- end
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
deleted file mode 100644
index d9c3d83d06..0000000000
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ /dev/null
@@ -1,212 +0,0 @@
-#
-# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright (c) 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 "../../node"
-require_relative "../../server_api"
-require_relative "../../api_client/registration"
-require_relative "../../api_client"
-require "tmpdir" unless defined?(Dir.mktmpdir)
-
-class Chef
- class Knife
- class Bootstrap < Knife
- class ClientBuilder
-
- # @return [Hash] knife merged config, typically @config
- attr_accessor :config
- # @return [Hash] chef config object
- attr_accessor :chef_config
- # @return [Chef::Knife::UI] ui object for output
- attr_accessor :ui
- # @return [Chef::ApiClient] client saved on run
- attr_reader :client
-
- # @param config [Hash] Hash of knife config settings
- # @param chef_config [Hash] Hash of chef config settings
- # @param ui [Chef::Knife::UI] UI object for output
- def initialize(config: {}, knife_config: nil, chef_config: {}, ui: nil)
- @config = config
- unless knife_config.nil?
- @config = knife_config
- Chef.deprecated(:knife_bootstrap_apis, "The knife_config option to the Bootstrap::ClientBuilder object is deprecated and has been renamed to just 'config'")
- end
- @chef_config = chef_config
- @ui = ui
- end
-
- # Main entry. Prompt the user to clean up any old client or node objects. Then create
- # the new client, then create the new node.
- def run
- sanity_check
-
- ui.info("Creating new client for #{node_name}")
-
- @client = create_client!
-
- ui.info("Creating new node for #{node_name}")
-
- create_node!
- end
-
- # Tempfile to use to write newly created client credentials to.
- #
- # This method is public so that the knife bootstrapper can read then and pass the value into
- # the handler for chef vault which needs the client cert we create here.
- #
- # We hang onto the tmpdir as an ivar as well so that it will not get GC'd and removed
- #
- # @return [String] path to the generated client.pem
- def client_path
- @client_path ||=
- begin
- @tmpdir = Dir.mktmpdir
- File.join(@tmpdir, "#{node_name}.pem")
- end
- end
-
- private
-
- # @return [String] node name from the config
- def node_name
- config[:chef_node_name]
- end
-
- # @return [String] environment from the config
- def environment
- config[:environment]
- end
-
- # @return [String] run_list from the config
- def run_list
- config[:run_list]
- end
-
- # @return [String] policy_name from the config
- def policy_name
- config[:policy_name]
- end
-
- # @return [String] policy_group from the config
- def policy_group
- config[:policy_group]
- end
-
- # @return [Hash,Array] Object representation of json first-boot attributes from the config
- def first_boot_attributes
- config[:first_boot_attributes]
- end
-
- # @return [String] chef server url from the Chef::Config
- def chef_server_url
- chef_config[:chef_server_url]
- end
-
- # Accesses the run_list and coerces it into an Array, changing nils into
- # the empty Array, and splitting strings representations of run_lists into
- # Arrays.
- #
- # @return [Array] run_list coerced into an array
- def normalized_run_list
- case run_list
- when nil
- []
- when String
- run_list.split(/\s*,\s*/)
- when Array
- run_list
- end
- end
-
- # Create the client object and save it to the Chef API
- def create_client!
- Chef::ApiClient::Registration.new(node_name, client_path, http_api: rest).run
- end
-
- # Create the node object (via the lazy accessor) and save it to the Chef API
- def create_node!
- node.save
- end
-
- # Create a new Chef::Node. Supports creating the node with its name, run_list, attributes
- # and environment. This injects a rest object into the Chef::Node which uses the client key
- # for authentication so that the client creates the node and therefore we get the acls setup
- # correctly.
- #
- # @return [Chef::Node] new chef node to create
- def node
- @node ||=
- begin
- node = Chef::Node.new(chef_server_rest: client_rest)
- node.name(node_name)
- node.run_list(normalized_run_list)
- node.normal_attrs = first_boot_attributes if first_boot_attributes
- node.environment(environment) if environment
- node.policy_name = policy_name if policy_name
- node.policy_group = policy_group if policy_group
- (config[:tags] || []).each do |tag|
- node.tags << tag
- end
- node
- end
- end
-
- # Check for the existence of a node and/or client already on the server. If the node
- # already exists, we must delete it in order to proceed so that we can create a new node
- # object with the permissions of the new client. There is a use case for creating a new
- # client and wiring it up to a precreated node object, but we do currently support that.
- #
- # We prompt the user about what to do and will fail hard if we do not get confirmation to
- # delete any prior node/client objects.
- def sanity_check
- if resource_exists?("nodes/#{node_name}")
- ui.confirm("Node #{node_name} exists, overwrite it")
- rest.delete("nodes/#{node_name}")
- end
- if resource_exists?("clients/#{node_name}")
- ui.confirm("Client #{node_name} exists, overwrite it")
- rest.delete("clients/#{node_name}")
- end
- end
-
- # Check if an relative path exists on the chef server
- #
- # @param relative_path [String] URI path relative to the chef organization
- # @return [Boolean] if the relative path exists or returns a 404
- def resource_exists?(relative_path)
- rest.get(relative_path)
- true
- rescue Net::HTTPClientException => e
- raise unless e.response.code == "404"
-
- false
- end
-
- # @return [Chef::ServerAPI] REST client using the client credentials
- def client_rest
- @client_rest ||= Chef::ServerAPI.new(chef_server_url, client_name: node_name, signing_key_filename: client_path)
- end
-
- # @return [Chef::ServerAPI] REST client using the cli user's knife credentials
- # this uses the users's credentials
- def rest
- @rest ||= Chef::ServerAPI.new(chef_server_url)
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/bootstrap/train_connector.rb b/lib/chef/knife/bootstrap/train_connector.rb
deleted file mode 100644
index a220ece5bc..0000000000
--- a/lib/chef/knife/bootstrap/train_connector.rb
+++ /dev/null
@@ -1,336 +0,0 @@
-# Copyright:: Copyright (c) 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 "train"
-require "tempfile" unless defined?(Tempfile)
-require "uri" unless defined?(URI)
-require "securerandom" unless defined?(SecureRandom)
-
-class Chef
- class Knife
- class Bootstrap < Knife
- class TrainConnector
- SSH_CONFIG_OVERRIDE_KEYS ||= %i{user port proxy}.freeze
-
- MKTEMP_WIN_COMMAND ||= <<~EOM.freeze
- $parent = [System.IO.Path]::GetTempPath();
- [string] $name = [System.Guid]::NewGuid();
- $tmp = New-Item -ItemType Directory -Path (Join-Path $parent $name);
- $tmp.FullName
- EOM
-
- DEFAULT_REMOTE_TEMP ||= "/tmp".freeze
-
- def initialize(host_url, default_protocol, opts)
- @host_url = host_url
- @default_protocol = default_protocol
- @opts_in = opts
- end
-
- def config
- @config ||= begin
- uri_opts = opts_from_uri(@host_url, @default_protocol)
- transport_config(@host_url, @opts_in.merge(uri_opts))
- end
- end
-
- def connection
- @connection ||= begin
- Train.validate_backend(config)
- train = Train.create(config[:backend], config)
- # Note that the train connection is not currently connected
- # to the remote host, but it's ready to go.
- train.connection
- end
- end
-
- #
- # Establish a connection to the configured host.
- #
- # @raise [TrainError]
- # @raise [TrainUserError]
- #
- # @return [TrueClass] true if the connection could be established.
- def connect!
- # Force connection to establish
- connection.wait_until_ready
- true
- end
-
- #
- # @return [String] the configured hostname
- def hostname
- config[:host]
- end
-
- # Answers the question, "is this connection configured for password auth?"
- # @return [Boolean] true if the connection is configured with password auth
- def password_auth?
- config.key? :password
- end
-
- # Answers the question, "Am I connected to a linux host?"
- #
- # @return [Boolean] true if the connected host is linux.
- def linux?
- connection.platform.linux?
- end
-
- # Answers the question, "Am I connected to a unix host?"
- #
- # @note this will always return true for a linux host
- # because train classifies linux as a unix
- #
- # @return [Boolean] true if the connected host is unix or linux
- def unix?
- connection.platform.unix?
- end
-
- #
- # Answers the question, "Am I connected to a Windows host?"
- #
- # @return [Boolean] true if the connected host is Windows
- def windows?
- connection.platform.windows?
- end
-
- #
- # Creates a temporary directory on the remote host if it
- # hasn't already. Caches directory location. For *nix,
- # it will ensure that the directory is owned by the logged-in user
- #
- # @return [String] the temporary path created on the remote host.
- def temp_dir
- @tmpdir ||= begin
- if windows?
- run_command!(MKTEMP_WIN_COMMAND).stdout.split.last
- else
- # Get a 6 chars string using secure random
- # eg. /tmp/chef_XXXXXX.
- # Use mkdir to create TEMP dir to get rid of mktemp
- dir = "#{DEFAULT_REMOTE_TEMP}/chef_#{SecureRandom.alphanumeric(6)}"
- run_command!("mkdir -p '#{dir}'")
- # Ensure that dir has the correct owner. We are possibly
- # running with sudo right now - so this directory would be owned by root.
- # File upload is performed over SCP as the current logged-in user,
- # so we'll set ownership to ensure that works.
- run_command!("chown #{config[:user]} '#{dir}'") if config[:sudo]
-
- dir
- end
- end
- end
-
- #
- # Uploads a file from "local_path" to "remote_path"
- #
- # @param local_path [String] The path to a file on the local file system
- # @param remote_path [String] The destination path on the remote file system.
- # @return NilClass
- def upload_file!(local_path, remote_path)
- connection.upload(local_path, remote_path)
- nil
- end
-
- #
- # Uploads the provided content into the file "remote_path" on the remote host.
- #
- # @param content [String] The content to upload into remote_path
- # @param remote_path [String] The destination path on the remote file system.
- # @return NilClass
- def upload_file_content!(content, remote_path)
- t = Tempfile.new("chef-content")
- t.binmode
- t << content
- t.close
- upload_file!(t.path, remote_path)
- nil
- ensure
- t.close
- t.unlink
- end
-
- #
- # Force-deletes the file at "path" from the remote host.
- #
- # @param path [String] The path of the file on the remote host
- def del_file!(path)
- if windows?
- run_command!("If (Test-Path \"#{path}\") { Remove-Item -Force -Path \"#{path}\" }")
- else
- run_command!("rm -f \"#{path}\"")
- end
- nil
- end
-
- #
- # normalizes path across OS's - always use forward slashes, which
- # Windows and *nix understand.
- #
- # @param path [String] The path to normalize
- #
- # @return [String] the normalized path
- def normalize_path(path)
- path.tr("\\", "/")
- end
-
- #
- # Runs a command on the remote host.
- #
- # @param command [String] The command to run.
- # @param data_handler [Proc] An optional block. When provided, inbound data will be
- # published via `data_handler.call(data)`. This can allow
- # callers to receive and render updates from remote command execution.
- #
- # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
- def run_command(command, &data_handler)
- connection.run_command(command, &data_handler)
- end
-
- #
- # Runs a command the remote host
- #
- # @param command [String] The command to run.
- # @param data_handler [Proc] An optional block. When provided, inbound data will be
- # published via `data_handler.call(data)`. This can allow
- # callers to receive and render updates from remote command execution.
- #
- # @raise Chef::Knife::Bootstrap::RemoteExecutionFailed if an error occurs (non-zero exit status)
- # @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status
- def run_command!(command, &data_handler)
- result = run_command(command, &data_handler)
- if result.exit_status != 0
- raise RemoteExecutionFailed.new(hostname, command, result)
- end
-
- result
- end
-
- private
-
- # For a given url and set of options, create a config
- # hash suitable for passing into train.
- def transport_config(host_url, opts_in)
- # These baseline opts are not protocol-specific
- opts = { target: host_url,
- www_form_encoded_password: true,
- transport_retries: 2,
- transport_retry_sleep: 1,
- backend: opts_in[:backend],
- logger: opts_in[:logger] }
-
- # Accepts options provided by caller if they're not already configured,
- # but note that they will be constrained to valid options for the backend protocol
- opts.merge!(opts_from_caller(opts, opts_in))
-
- # WinRM has some additional computed options
- opts.merge!(opts_inferred_from_winrm(opts, opts_in))
-
- # Now that everything is populated, fill in anything missing
- # that may be found in user ssh config
- opts.merge!(missing_opts_from_ssh_config(opts, opts_in))
-
- Train.target_config(opts)
- end
-
- # Some winrm options are inferred based on other options.
- # Return a hash of winrm options based on configuration already built.
- def opts_inferred_from_winrm(config, opts_in)
- return {} unless config[:backend] == "winrm"
-
- opts_out = {}
-
- if opts_in[:ssl]
- opts_out[:ssl] = true
- opts_out[:self_signed] = opts_in[:self_signed] || false
- end
-
- # See note here: https://github.com/mwrock/WinRM#example
- if %w{ssl plaintext}.include?(opts_in[:winrm_auth_method])
- opts_out[:winrm_disable_sspi] = true
- end
- opts_out
- end
-
- # Returns a hash containing valid options for the current
- # transport protocol that are not already present in config
- def opts_from_caller(config, opts_in)
- # Train.options gives us the supported config options for the
- # backend provider (ssh, winrm). We'll use that
- # to filter out options that don't belong
- # to the transport type we're using.
- valid_opts = Train.options(config[:backend])
- opts_in.select do |key, _v|
- valid_opts.key?(key) && !config.key?(key)
- end
- end
-
- # Extract any of username/password/host/port/transport
- # that are in the URI and return them as a config has
- def opts_from_uri(uri, default_protocol)
- # Train.unpack_target_from_uri only works for complete URIs in
- # form of proto://[user[:pass]@]host[:port]/
- # So we'll add the protocol prefix if it's not supplied.
- uri_to_check = if URI::DEFAULT_PARSER.make_regexp.match(uri)
- uri
- else
- "#{default_protocol}://#{uri}"
- end
-
- Train.unpack_target_from_uri(uri_to_check)
- end
-
- # This returns a hash that consists of settings
- # populated from SSH configuration that are not already present
- # in the configuration passed in.
- # This is necessary because train will default these values
- # itself - causing SSH config data to be ignored
- def missing_opts_from_ssh_config(config, opts_in)
- return {} unless config[:backend] == "ssh"
-
- host_cfg = ssh_config_for_host(config[:host])
- opts_out = {}
- opts_in.each do |key, _value|
- if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key)
- opts_out[key] = host_cfg[key]
- end
- end
- opts_out
- end
-
- # Having this as a method makes it easier to mock
- # SSH Config for testing.
- def ssh_config_for_host(host)
- require "net/ssh" unless defined?(Net::SSH)
- Net::SSH::Config.for(host)
- end
- end
-
- class RemoteExecutionFailed < StandardError
- attr_reader :exit_status, :command, :hostname, :stdout, :stderr
-
- def initialize(hostname, command, result)
- @hostname = hostname
- @exit_status = result.exit_status
- @stderr = result.stderr
- @stdout = result.stdout
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
deleted file mode 100644
index 38d25583b3..0000000000
--- a/lib/chef/knife/client_bulk_delete.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientBulkDelete < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- option :delete_validators,
- short: "-D",
- long: "--delete-validators",
- description: "Force deletion of clients if they're validators."
-
- banner "knife client bulk delete REGEX (options)"
-
- def run
- if name_args.length < 1
- ui.fatal("You must supply a regular expression to match the results against")
- exit 42
- end
- all_clients = Chef::ApiClientV1.list(true)
-
- matcher = /#{name_args[0]}/
- clients_to_delete = {}
- validators_to_delete = {}
- all_clients.each do |name, client|
- next unless name&.match?(matcher)
-
- if client.validator
- validators_to_delete[client.name] = client
- else
- clients_to_delete[client.name] = client
- end
- end
-
- if clients_to_delete.empty? && validators_to_delete.empty?
- ui.info "No clients match the expression /#{name_args[0]}/"
- exit 0
- end
-
- check_and_delete_validators(validators_to_delete)
- check_and_delete_clients(clients_to_delete)
- end
-
- def check_and_delete_validators(validators)
- unless validators.empty?
- unless config[:delete_validators]
- ui.msg("The following clients are validators and will not be deleted:")
- print_clients(validators)
- ui.msg("You must specify --delete-validators to delete the validator clients")
- else
- ui.msg("The following validators will be deleted:")
- print_clients(validators)
- if ui.confirm_without_exit("Are you sure you want to delete these validators")
- destroy_clients(validators)
- end
- end
- end
- end
-
- def check_and_delete_clients(clients)
- unless clients.empty?
- ui.msg("The following clients will be deleted:")
- print_clients(clients)
- ui.confirm("Are you sure you want to delete these clients")
- destroy_clients(clients)
- end
- end
-
- def destroy_clients(clients)
- clients.sort.each do |name, client|
- client.destroy
- ui.msg("Deleted client #{name}")
- end
- end
-
- def print_clients(clients)
- ui.msg("")
- ui.msg(ui.list(clients.keys.sort, :columns_down))
- ui.msg("")
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
deleted file mode 100644
index d6e0eab63b..0000000000
--- a/lib/chef/knife/client_create.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class ClientCreate < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- option :file,
- short: "-f FILE",
- long: "--file FILE",
- description: "Write the private key to a file if the #{ChefUtils::Dist::Server::PRODUCT} generated one."
-
- option :validator,
- long: "--validator",
- description: "Create the client as a validator.",
- boolean: true
-
- option :public_key,
- short: "-p FILE",
- long: "--public-key",
- description: "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)."
-
- option :prevent_keygen,
- short: "-k",
- long: "--prevent-keygen",
- description: "Prevent #{ChefUtils::Dist::Server::PRODUCT} from generating a default key pair for you. Cannot be passed with --public-key.",
- boolean: true
-
- banner "knife client create CLIENTNAME (options)"
-
- def client
- @client_field ||= Chef::ApiClientV1.new
- end
-
- def create_client(client)
- # should not be using save :( bad behavior
- Chef::ApiClientV1.from_hash(client).save
- end
-
- def run
- test_mandatory_field(@name_args[0], "client name")
- client.name @name_args[0]
-
- if config[:public_key] && config[:prevent_keygen]
- show_usage
- ui.fatal("You cannot pass --public-key and --prevent-keygen")
- exit 1
- end
-
- if !config[:prevent_keygen] && !config[:public_key]
- client.create_key(true)
- end
-
- if config[:validator]
- client.validator(true)
- end
-
- if config[:public_key]
- client.public_key File.read(File.expand_path(config[:public_key]))
- end
-
- output = edit_hash(client)
- final_client = create_client(output)
- ui.info("Created #{final_client}")
-
- # output private_key if one
- if final_client.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(final_client.private_key)
- end
- else
- puts final_client.private_key
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
deleted file mode 100644
index 3ecfa38242..0000000000
--- a/lib/chef/knife/client_delete.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientDelete < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- option :delete_validators,
- short: "-D",
- long: "--delete-validators",
- description: "Force deletion of client if it's a validator."
-
- banner "knife client delete [CLIENT [CLIENT]] (options)"
-
- def run
- if @name_args.length == 0
- show_usage
- ui.fatal("You must specify at least one client name")
- exit 1
- end
-
- @name_args.each do |client_name|
- delete_client(client_name)
- end
- end
-
- def delete_client(client_name)
- delete_object(Chef::ApiClientV1, client_name, "client") do
- object = Chef::ApiClientV1.load(client_name)
- if object.validator
- unless config[:delete_validators]
- ui.fatal("You must specify --delete-validators to delete the validator client #{client_name}")
- exit 2
- end
- end
- object.destroy
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb
deleted file mode 100644
index f89f5e38ec..0000000000
--- a/lib/chef/knife/client_edit.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientEdit < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- banner "knife client edit CLIENT (options)"
-
- def run
- @client_name = @name_args[0]
-
- if @client_name.nil?
- show_usage
- ui.fatal("You must specify a client name")
- exit 1
- end
-
- original_data = Chef::ApiClientV1.load(@client_name).to_h
- edited_client = edit_hash(original_data)
- if original_data != edited_client
- client = Chef::ApiClientV1.from_hash(edited_client)
- client.save
- ui.msg("Saved #{client}.")
- else
- ui.msg("Client unchanged, not saving.")
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
deleted file mode 100644
index b4fc46767b..0000000000
--- a/lib/chef/knife/client_list.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientList < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- banner "knife client list (options)"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- output(format_list_for_display(Chef::ApiClientV1.list))
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
deleted file mode 100644
index 6741895b23..0000000000
--- a/lib/chef/knife/client_reregister.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientReregister < Knife
-
- deps do
- require_relative "../api_client_v1"
- end
-
- banner "knife client reregister CLIENT (options)"
-
- option :file,
- short: "-f FILE",
- long: "--file FILE",
- description: "Write the key to a file."
-
- def run
- @client_name = @name_args[0]
-
- if @client_name.nil?
- show_usage
- ui.fatal("You must specify a client name")
- exit 1
- end
-
- client = Chef::ApiClientV1.reregister(@client_name)
- Chef::Log.trace("Updated client data: #{client.inspect}")
- key = client.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(key)
- end
- else
- ui.msg key
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
deleted file mode 100644
index 9170c73085..0000000000
--- a/lib/chef/knife/client_show.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class ClientShow < Knife
-
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../api_client_v1"
- end
-
- banner "knife client show CLIENT (options)"
-
- def run
- @client_name = @name_args[0]
-
- if @client_name.nil?
- show_usage
- ui.fatal("You must specify a client name")
- exit 1
- end
-
- client = Chef::ApiClientV1.load(@client_name)
- output(format_for_display(client))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/config_list.rb b/lib/chef/knife/config_list.rb
deleted file mode 100644
index c9f821e2a8..0000000000
--- a/lib/chef/knife/config_list.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-#
-# Copyright:: Copyright (c) 2018, Noah Kantrowitz
-# 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 "../knife"
-
-class Chef
- class Knife
- class ConfigList < Knife
- banner "knife config list (options)"
-
- TABLE_HEADER ||= [" Profile", "Client", "Key", "Server"].freeze
-
- deps do
- require_relative "../workstation_config_loader"
- require "tty-screen" unless defined?(TTY::Screen)
- require "tty-table" unless defined?(TTY::Table)
- end
-
- option :ignore_knife_rb,
- short: "-i",
- long: "--ignore-knife-rb",
- description: "Ignore the current config.rb/knife.rb configuration.",
- default: false
-
- def configure_chef
- apply_computed_config
- end
-
- def run
- credentials_data = self.class.config_loader.parse_credentials_file
- if credentials_data.nil? || credentials_data.empty?
- # Should this just show the ambient knife.rb config as "default" instead?
- ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty")
- exit 1
- end
-
- current_profile = self.class.config_loader.credentials_profile(config[:profile])
- profiles = credentials_data.keys.map do |profile|
- if config[:ignore_knife_rb]
- # Don't do any fancy loading nonsense, just the raw data.
- profile_data = credentials_data[profile]
- {
- profile: profile,
- active: profile == current_profile,
- client_name: profile_data["client_name"] || profile_data["node_name"],
- client_key: profile_data["client_key"],
- server_url: profile_data["chef_server_url"],
- }
- else
- # Fancy loading nonsense so we get what the actual config would be.
- # Note that this modifies the global config, after this, all bets are
- # off as to whats in the config.
- Chef::Config.reset
- wcl = Chef::WorkstationConfigLoader.new(nil, Chef::Log, profile: profile)
- wcl.load
- {
- profile: profile,
- active: profile == current_profile,
- client_name: Chef::Config[:node_name],
- client_key: Chef::Config[:client_key],
- server_url: Chef::Config[:chef_server_url],
- }
- end
- end
-
- # Try to reset the config.
- unless config[:ignore_knife_rb]
- Chef::Config.reset
- apply_computed_config
- end
-
- if ui.interchange?
- # Machine-readable output.
- ui.output(profiles)
- else
- # Table output.
- ui.output(render_table(profiles))
- end
- end
-
- private
-
- def render_table(profiles, padding: 1)
- rows = []
- # Render the data to a 2D array that will be used for the table.
- profiles.each do |profile|
- # Replace the home dir in the client key path with ~.
- profile[:client_key] = profile[:client_key].to_s.gsub(/^#{Regexp.escape(Dir.home)}/, "~") if profile[:client_key]
- profile[:profile] = "#{profile[:active] ? "*" : " "}#{profile[:profile]}"
- rows << profile.values_at(:profile, :client_name, :client_key, :server_url)
- end
-
- table = TTY::Table.new(header: TABLE_HEADER, rows: rows)
-
- # Rotate the table to vertical if the screen width is less than table width.
- if table.width > TTY::Screen.width
- table.orientation = :vertical
- table.rotate
- # Add a new line after each profile record.
- table.render do |renderer|
- renderer.border do
- separator ->(row) { (row + 1) % TABLE_HEADER.size == 0 }
- end
- # Remove the leading space added of the first column.
- renderer.filter = Proc.new do |val, row_index, col_index|
- if col_index == 1 || (row_index) % TABLE_HEADER.size == 0
- val.strip
- else
- val
- end
- end
- end
- else
- table.render do |renderer|
- renderer.border do
- mid "-"
- end
- renderer.padding = [0, padding, 0, 0] # pad right with 2 characters
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb
deleted file mode 100644
index 2a27fd5d88..0000000000
--- a/lib/chef/knife/configure.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class Configure < Knife
- attr_reader :chef_server, :new_client_name, :admin_client_name, :admin_client_key
- attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key
-
- deps do
- require_relative "../util/path_helper"
- require_relative "client_create"
- require_relative "user_create"
- require "ohai" unless defined?(Ohai::System)
- Chef::Knife::ClientCreate.load_deps
- Chef::Knife::UserCreate.load_deps
- end
-
- banner "knife configure (options)"
-
- option :repository,
- short: "-r REPO",
- long: "--repository REPO",
- description: "The path to the chef-repo."
-
- option :initial,
- short: "-i",
- long: "--initial",
- boolean: true,
- description: "Use to create a API client, typically an administrator client on a freshly-installed server."
-
- option :admin_client_name,
- long: "--admin-client-name NAME",
- description: "The name of the client, typically the name of the admin client."
-
- option :admin_client_key,
- long: "--admin-client-key PATH",
- description: "The path to the private key used by the client, typically a file named admin.pem."
-
- option :validation_client_name,
- long: "--validation-client-name NAME",
- description: "The name of the validation client, typically a client named chef-validator."
-
- option :validation_key,
- long: "--validation-key PATH",
- description: "The path to the validation key used by the client, typically a file named validation.pem."
-
- def configure_chef
- # We are just faking out the system so that you can do this without a key specified
- Chef::Config[:node_name] = "woot"
- super
- Chef::Config[:node_name] = nil
- end
-
- def run
- FileUtils.mkdir_p(chef_config_path)
-
- ask_user_for_config
-
- confirm("Overwrite #{config_file_path}") if ::File.exist?(config_file_path)
-
- ::File.open(config_file_path, "w") do |f|
- f.puts <<~EOH
- [default]
- client_name = '#{new_client_name}'
- client_key = '#{new_client_key}'
- chef_server_url = '#{chef_server}'
- EOH
- end
-
- if config[:initial]
- ui.msg("Creating initial API user...")
- Chef::Config[:chef_server_url] = chef_server
- Chef::Config[:node_name] = admin_client_name
- Chef::Config[:client_key] = admin_client_key
- user_create = Chef::Knife::UserCreate.new
- user_create.name_args = [ new_client_name ]
- user_create.config[:user_password] = config[:user_password] ||
- ui.ask("Please enter a password for the new user: ", echo: false)
- user_create.config[:admin] = true
- user_create.config[:file] = new_client_key
- user_create.config[:yes] = true
- user_create.config[:disable_editing] = true
- user_create.run
- else
- ui.msg("*****")
- ui.msg("")
- ui.msg("You must place your client key in:")
- ui.msg(" #{new_client_key}")
- ui.msg("Before running commands with Knife")
- ui.msg("")
- ui.msg("*****")
- end
-
- ui.msg("Knife configuration file written to #{config_file_path}")
- end
-
- def ask_user_for_config
- server_name = guess_servername
- @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", default: "https://#{server_name}/organizations/myorg")
- if config[:initial]
- @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", default: Etc.getlogin)
- @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", default: "admin")
- @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", default: "#{ChefUtils::Dist::Server::CONF_DIR}/admin.pem")
- @admin_client_key = File.expand_path(@admin_client_key)
- else
- @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", default: Etc.getlogin)
- end
-
- @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem")
- @new_client_key = File.expand_path(@new_client_key)
- end
-
- # @return [String] our best guess at what the servername should be using Ohai data and falling back to localhost
- def guess_servername
- o = Ohai::System.new
- o.all_plugins(%w{ os hostname fqdn })
- o[:fqdn] || o[:machinename] || o[:hostname] || "localhost"
- end
-
- # @return [String] the path to the user's .chef directory
- def chef_config_path
- @chef_config_path ||= Chef::Util::PathHelper.home(".chef")
- end
-
- # @return [String] the full path to the config file (credential file)
- def config_file_path
- @config_file_path ||= ::File.expand_path(::File.join(chef_config_path, "credentials"))
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb
deleted file mode 100644
index d6657ccb4f..0000000000
--- a/lib/chef/knife/cookbook_bulk_delete.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookBulkDelete < Knife
-
- deps do
- require_relative "cookbook_delete"
- require_relative "../cookbook_version"
- end
-
- option :purge, short: "-p", long: "--purge", boolean: true, description: "Permanently remove files from backing data store."
-
- banner "knife cookbook bulk delete REGEX (options)"
-
- def run
- unless regex_str = @name_args.first
- ui.fatal("You must supply a regular expression to match the results against")
- exit 42
- end
-
- regex = Regexp.new(regex_str)
-
- all_cookbooks = Chef::CookbookVersion.list
- cookbooks_names = all_cookbooks.keys.grep(regex)
- cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name]; hash }
- ui.msg "All versions of the following cookbooks will be deleted:"
- ui.msg ""
- ui.msg ui.list(cookbooks_to_delete.keys.sort, :columns_down)
- ui.msg ""
-
- unless config[:yes]
- ui.confirm("Do you really want to delete these cookbooks")
-
- if config[:purge]
- ui.msg("Files that are common to multiple cookbooks are shared, so purging the files may break other cookbooks.")
- ui.confirm("Are you sure you want to purge files instead of just deleting the cookbooks")
- end
- ui.msg ""
- end
-
- cookbooks_names.each do |cookbook_name|
- versions = rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map { |v| v["version"] }.flatten
- versions.each do |version|
- rest.delete("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}")
- ui.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]")
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_delete.rb b/lib/chef/knife/cookbook_delete.rb
deleted file mode 100644
index 04ecb95cf4..0000000000
--- a/lib/chef/knife/cookbook_delete.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookDelete < Knife
-
- attr_accessor :cookbook_name, :version
-
- deps do
- require_relative "../cookbook_version"
- end
-
- option :all, short: "-a", long: "--all", boolean: true, description: "Delete all versions of the cookbook."
-
- option :purge, short: "-p", long: "--purge", boolean: true, description: "Permanently remove files from backing data store."
-
- banner "knife cookbook delete COOKBOOK VERSION (options)"
-
- def run
- confirm("Files that are common to multiple cookbooks are shared, so purging the files may disable other cookbooks. Are you sure you want to purge files instead of just deleting the cookbook") if config[:purge]
- @cookbook_name, @version = name_args
- if @cookbook_name && @version
- delete_explicit_version
- elsif @cookbook_name && config[:all]
- delete_all_versions
- elsif @cookbook_name && @version.nil?
- delete_without_explicit_version
- elsif @cookbook_name.nil?
- show_usage
- ui.fatal("You must provide the name of the cookbook to delete.")
- exit(1)
- end
- end
-
- def delete_explicit_version
- delete_object(Chef::CookbookVersion, "#{@cookbook_name} version #{@version}", "cookbook") do
- delete_request("cookbooks/#{@cookbook_name}/#{@version}")
- end
- end
-
- def delete_all_versions
- confirm("Do you really want to delete all versions of #{@cookbook_name}")
- delete_all_without_confirmation
- end
-
- def delete_all_without_confirmation
- # look up the available versions again just in case the user
- # got to the list of versions to delete and selected 'all'
- # and also a specific version
- @available_versions = nil
- Array(available_versions).each do |version|
- delete_version_without_confirmation(version)
- end
- end
-
- def delete_without_explicit_version
- if available_versions.nil?
- # we already logged an error or 2 about it, so just bail
- exit(1)
- elsif available_versions.size == 1
- @version = available_versions.first
- delete_explicit_version
- else
- versions_to_delete = ask_which_versions_to_delete
- delete_versions_without_confirmation(versions_to_delete)
- end
- end
-
- def available_versions
- @available_versions ||= rest.get("cookbooks/#{@cookbook_name}").map do |name, url_and_version|
- url_and_version["versions"].map { |url_by_version| url_by_version["version"] }
- end.flatten
- rescue Net::HTTPClientException => e
- if /^404/.match?(e.to_s)
- ui.error("Cannot find a cookbook named #{@cookbook_name} to delete.")
- nil
- else
- raise
- end
- end
-
- def ask_which_versions_to_delete
- question = "Which version(s) do you want to delete?\n"
- valid_responses = {}
- available_versions.each_with_index do |version, index|
- valid_responses[(index + 1).to_s] = version
- question << "#{index + 1}. #{@cookbook_name} #{version}\n"
- end
- valid_responses[(available_versions.size + 1).to_s] = :all
- question << "#{available_versions.size + 1}. All versions\n\n"
- responses = ask_question(question).split(",").map(&:strip)
-
- if responses.empty?
- ui.error("No versions specified, exiting")
- exit(1)
- end
- versions = responses.map do |response|
- if version = valid_responses[response]
- version
- else
- ui.error("#{response} is not a valid choice, skipping it")
- end
- end
- versions.compact
- end
-
- def delete_version_without_confirmation(version)
- object = delete_request("cookbooks/#{@cookbook_name}/#{version}")
- output(format_for_display(object)) if config[:print_after]
- ui.info("Deleted cookbook[#{@cookbook_name}][#{version}]")
- end
-
- def delete_versions_without_confirmation(versions)
- versions.each do |version|
- if version == :all
- delete_all_without_confirmation
- break
- else
- delete_version_without_confirmation(version)
- end
- end
- end
-
- private
-
- def delete_request(path)
- path += "?purge=true" if config[:purge]
- rest.delete(path)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb
deleted file mode 100644
index a07b519511..0000000000
--- a/lib/chef/knife/cookbook_download.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookDownload < Knife
-
- attr_reader :version
- attr_accessor :cookbook_name
-
- deps do
- require_relative "../cookbook_version"
- end
-
- banner "knife cookbook download COOKBOOK [VERSION] (options)"
-
- option :latest,
- short: "-N",
- long: "--latest",
- description: "The version of the cookbook to download.",
- boolean: true
-
- option :download_directory,
- short: "-d DOWNLOAD_DIRECTORY",
- long: "--dir DOWNLOAD_DIRECTORY",
- description: "The directory to download the cookbook into.",
- default: Dir.pwd
-
- option :force,
- short: "-f",
- long: "--force",
- description: "Force download over the download directory if it exists."
-
- # TODO: tim/cw: 5-23-2010: need to implement knife-side
- # specificity for downloads - need to implement --platform and
- # --fqdn here
- def run
- @cookbook_name, @version = @name_args
-
- if @cookbook_name.nil?
- show_usage
- ui.fatal("You must specify a cookbook name")
- exit 1
- elsif @version.nil?
- @version = determine_version
- if @version.nil?
- ui.fatal("No such cookbook found")
- exit 1
- end
- end
-
- ui.info("Downloading #{@cookbook_name} cookbook version #{@version}")
-
- cookbook = Chef::CookbookVersion.load(@cookbook_name, @version)
- manifest = cookbook.cookbook_manifest
-
- basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}")
- if File.exist?(basedir)
- if config[:force]
- Chef::Log.trace("Deleting #{basedir}")
- FileUtils.rm_rf(basedir)
- else
- ui.fatal("Directory #{basedir} exists, use --force to overwrite")
- exit
- end
- end
-
- manifest.by_parent_directory.each do |segment, files|
- ui.info("Downloading #{segment}")
- files.each do |segment_file|
- dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR))
- Chef::Log.trace("Downloading #{segment_file["path"]} to #{dest}")
- FileUtils.mkdir_p(File.dirname(dest))
- tempfile = rest.streaming_request(segment_file["url"])
- FileUtils.mv(tempfile.path, dest)
- end
- end
- ui.info("Cookbook downloaded to #{basedir}")
- end
-
- def determine_version
- if available_versions.nil?
- nil
- elsif available_versions.size == 1
- @version = available_versions.first
- elsif config[:latest]
- @version = available_versions.last
- else
- ask_which_version
- end
- end
-
- def available_versions
- @available_versions ||= begin
- versions = Chef::CookbookVersion.available_versions(@cookbook_name)
- unless versions.nil?
- versions.map! { |version| Chef::Version.new(version) }
- versions.sort!
- end
- versions
- end
- @available_versions
- end
-
- def ask_which_version
- question = "Which version do you want to download?\n"
- valid_responses = {}
- available_versions.each_with_index do |version, index|
- valid_responses[(index + 1).to_s] = version
- question << "#{index + 1}. #{@cookbook_name} #{version}\n"
- end
- question += "\n"
- response = ask_question(question).strip
-
- unless @version = valid_responses[response]
- ui.error("'#{response}' is not a valid value.")
- exit(1)
- end
- @version
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_metadata.rb b/lib/chef/knife/cookbook_metadata.rb
deleted file mode 100644
index 8d8970b1c1..0000000000
--- a/lib/chef/knife/cookbook_metadata.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookMetadata < Knife
-
- deps do
- require_relative "../cookbook_loader"
- require_relative "../cookbook/metadata"
- end
-
- banner "knife cookbook metadata COOKBOOK (options)"
-
- option :cookbook_path,
- short: "-o PATH:PATH",
- long: "--cookbook-path PATH:PATH",
- description: "A colon-separated path to look for cookbooks in.",
- proc: lambda { |o| o.split(":") }
-
- option :all,
- short: "-a",
- long: "--all",
- description: "Generate metadata for all cookbooks, rather than just a single cookbook."
-
- def run
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- if config[:all]
- cl = Chef::CookbookLoader.new(config[:cookbook_path])
- cl.load_cookbooks
- cl.each_key do |cname|
- generate_metadata(cname.to_s)
- end
- else
- cookbook_name = @name_args[0]
- if cookbook_name.nil? || cookbook_name.empty?
- ui.error "You must specify the cookbook to generate metadata for, or use the --all option."
- exit 1
- end
- generate_metadata(cookbook_name)
- end
- end
-
- def generate_metadata(cookbook)
- Array(config[:cookbook_path]).reverse_each do |path|
- file = File.expand_path(File.join(path, cookbook, "metadata.rb"))
- if File.exist?(file)
- generate_metadata_from_file(cookbook, file)
- else
- validate_metadata_json(path, cookbook)
- end
- end
- end
-
- def generate_metadata_from_file(cookbook, file)
- ui.info("Generating metadata for #{cookbook} from #{file}")
- md = Chef::Cookbook::Metadata.new
- md.name(cookbook)
- md.from_file(file)
- json_file = File.join(File.dirname(file), "metadata.json")
- File.open(json_file, "w") do |f|
- f.write(Chef::JSONCompat.to_json_pretty(md))
- end
- Chef::Log.trace("Generated #{json_file}")
- rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
- ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
- ui.stderr.puts "in #{file}:"
- ui.stderr.puts
- ui.stderr.puts e.message
- exit 1
- end
-
- def validate_metadata_json(path, cookbook)
- json_file = File.join(path, cookbook, "metadata.json")
- if File.exist?(json_file)
- Chef::Cookbook::Metadata.validate_json(IO.read(json_file))
- end
- rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
- ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax."
- ui.stderr.puts "in #{json_file}:"
- ui.stderr.puts
- ui.stderr.puts e.message
- exit 1
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_metadata_from_file.rb b/lib/chef/knife/cookbook_metadata_from_file.rb
deleted file mode 100644
index d768213384..0000000000
--- a/lib/chef/knife/cookbook_metadata_from_file.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright (c) Chef Software Inc.
-# Copyright:: Copyright 2010-2016, Matthew Kent
-# 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 "../knife"
-
-class Chef
- class Knife
- class CookbookMetadataFromFile < Knife
-
- deps do
- require_relative "../cookbook/metadata"
- end
-
- banner "knife cookbook metadata from file FILE (options)"
-
- def run
- if @name_args.length < 1
- show_usage
- ui.fatal("You must specify the FILE.")
- exit(1)
- end
-
- file = @name_args[0]
- cookbook = File.basename(File.dirname(file))
-
- @metadata = Chef::Knife::CookbookMetadata.new
- @metadata.generate_metadata_from_file(cookbook, file)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb
deleted file mode 100644
index 0b97fba139..0000000000
--- a/lib/chef/knife/cookbook_show.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookShow < Knife
-
- deps do
- require_relative "../json_compat"
- require "uri" unless defined?(URI)
- require_relative "../cookbook_version"
- end
-
- banner "knife cookbook show COOKBOOK [VERSION] [PART] [FILENAME] (options)"
-
- option :fqdn,
- short: "-f FQDN",
- long: "--fqdn FQDN",
- description: "The FQDN of the host to see the file for."
-
- option :platform,
- short: "-p PLATFORM",
- long: "--platform PLATFORM",
- description: "The platform to see the file for."
-
- option :platform_version,
- short: "-V VERSION",
- long: "--platform-version VERSION",
- description: "The platform version to see the file for."
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- cookbook_name, cookbook_version, segment, filename = @name_args
-
- cookbook = Chef::CookbookVersion.load(cookbook_name, cookbook_version) unless cookbook_version.nil?
-
- case @name_args.length
- when 4 # We are showing a specific file
- node = {}
- node[:fqdn] = config[:fqdn] if config.key?(:fqdn)
- node[:platform] = config[:platform] if config.key?(:platform)
- node[:platform_version] = config[:platform_version] if config.key?(:platform_version)
-
- class << node
- def attribute?(name) # rubocop:disable Lint/NestedMethodDefinition
- key?(name)
- end
- end
-
- manifest_entry = cookbook.preferred_manifest_record(node, segment, filename)
- temp_file = rest.streaming_request(manifest_entry[:url])
-
- # the temp file is cleaned up elsewhere
- temp_file.open if temp_file.closed?
- pretty_print(temp_file.read)
-
- when 3 # We are showing a specific part of the cookbook
- if segment == "metadata"
- output(cookbook.metadata)
- else
- output(cookbook.files_for(segment))
- end
- when 2 # We are showing the whole cookbook
- output(cookbook.display)
- when 1 # We are showing the cookbook versions (all of them)
- env = config[:environment]
- api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}"
- output(format_cookbook_list_for_display(rest.get(api_endpoint)))
- when 0
- show_usage
- ui.fatal("You must specify a cookbook name")
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb
deleted file mode 100644
index 9f6f3c4cb2..0000000000
--- a/lib/chef/knife/cookbook_upload.rb
+++ /dev/null
@@ -1,292 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Walters (<cw@chef.io>)
-# Author:: Nuo Yan (<yan.nuo@gmail.com>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class CookbookUpload < Knife
- deps do
- require_relative "../mixin/file_class"
- include Chef::Mixin::FileClass
- require_relative "../exceptions"
- require_relative "../cookbook_loader"
- require_relative "../cookbook_uploader"
- end
-
- banner "knife cookbook upload [COOKBOOKS...] (options)"
-
- option :cookbook_path,
- short: "-o 'PATH:PATH'",
- long: "--cookbook-path 'PATH:PATH'",
- description: "A delimited path to search for cookbooks. On Unix the delimiter is ':', on Windows it is ';'.",
- proc: lambda { |o| o.split(File::PATH_SEPARATOR) }
-
- option :freeze,
- long: "--freeze",
- description: "Freeze this version of the cookbook so that it cannot be overwritten.",
- boolean: true
-
- option :all,
- short: "-a",
- long: "--all",
- description: "Upload all cookbooks, rather than just a single cookbook."
-
- option :force,
- long: "--force",
- boolean: true,
- description: "Update cookbook versions even if they have been frozen."
-
- option :concurrency,
- long: "--concurrency NUMBER_OF_THREADS",
- description: "How many concurrent threads will be used.",
- default: 10,
- proc: lambda { |o| o.to_i }
-
- option :environment,
- short: "-E",
- long: "--environment ENVIRONMENT",
- description: "Set ENVIRONMENT's version dependency match the version you're uploading.",
- default: nil
-
- option :depends,
- short: "-d",
- long: "--include-dependencies",
- description: "Also upload cookbook dependencies."
-
- def run
- # Sanity check before we load anything from the server
- if ! config[:all] && @name_args.empty?
- show_usage
- ui.fatal("You must specify the --all flag or at least one cookbook name")
- exit 1
- end
-
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- assert_environment_valid!
- version_constraints_to_update = {}
- upload_failures = 0
- upload_ok = 0
-
- # Get a list of cookbooks and their versions from the server
- # to check for the existence of a cookbook's dependencies.
- @server_side_cookbooks = Chef::CookbookVersion.list_all_versions
- justify_width = @server_side_cookbooks.map(&:size).max.to_i + 2
-
- cookbooks = []
- cookbooks_to_upload.each do |cookbook_name, cookbook|
- raise Chef::Exceptions::MetadataNotFound.new(cookbook.root_paths[0], cookbook_name) unless cookbook.has_metadata_file?
-
- if cookbook.metadata.name.nil?
- message = "Cookbook loaded at path [#{cookbook.root_paths[0]}] has invalid metadata: #{cookbook.metadata.errors.join("; ")}"
- raise Chef::Exceptions::MetadataNotValid, message
- end
-
- cookbooks << cookbook
- end
-
- if cookbooks.empty?
- cookbook_path = config[:cookbook_path].respond_to?(:join) ? config[:cookbook_path].join(", ") : config[:cookbook_path]
- ui.warn("Could not find any cookbooks in your cookbook path: '#{File.expand_path(cookbook_path)}'. Use --cookbook-path to specify the desired path.")
- else
- Chef::CookbookLoader.copy_to_tmp_dir_from_array(cookbooks) do |tmp_cl|
- tmp_cl.load_cookbooks
- tmp_cl.compile_metadata
- tmp_cl.freeze_versions if config[:freeze]
-
- cookbooks_for_upload = []
- tmp_cl.each do |cookbook_name, cookbook|
- cookbooks_for_upload << cookbook
- version_constraints_to_update[cookbook_name] = cookbook.version
- end
- if config[:all]
- if cookbooks_for_upload.any?
- begin
- upload(cookbooks_for_upload, justify_width)
- rescue Chef::Exceptions::CookbookFrozen
- ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
- ui.error("Uploading of some of the cookbooks must be failed. Remove cookbook whose version is frozen from your cookbooks repo OR use --force option.")
- upload_failures += 1
- rescue SystemExit => e
- raise exit e.status
- end
- ui.info("Uploaded all cookbooks.") if upload_failures == 0
- end
- else
- tmp_cl.each do |cookbook_name, cookbook|
-
- upload([cookbook], justify_width)
- upload_ok += 1
- rescue Exceptions::CookbookNotFoundInRepo => e
- upload_failures += 1
- ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
- Log.debug(e)
- upload_failures += 1
- rescue Exceptions::CookbookFrozen
- ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.")
- upload_failures += 1
- rescue SystemExit => e
- raise exit e.status
-
- end
-
- if upload_failures == 0
- ui.info "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"}."
- elsif upload_failures > 0 && upload_ok > 0
- ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok == 1 ? "" : "s"} ok but #{upload_failures} " +
- "cookbook#{upload_failures == 1 ? "" : "s"} upload failed."
- elsif upload_failures > 0 && upload_ok == 0
- ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures == 1 ? "" : "s"}."
- exit 1
- end
- end
- unless version_constraints_to_update.empty?
- update_version_constraints(version_constraints_to_update) if config[:environment]
- end
- end
- end
- end
-
- def cookbooks_to_upload
- @cookbooks_to_upload ||=
- if config[:all]
- cookbook_repo.load_cookbooks
- else
- upload_set = {}
- @name_args.each do |cookbook_name|
-
- unless upload_set.key?(cookbook_name)
- upload_set[cookbook_name] = cookbook_repo[cookbook_name]
- if config[:depends]
- upload_set[cookbook_name].metadata.dependencies.each_key { |dep| @name_args << dep }
- end
- end
- rescue Exceptions::CookbookNotFoundInRepo => e
- ui.error(e.message)
- Log.debug(e)
-
- end
- upload_set
- end
- end
-
- def cookbook_repo
- @cookbook_loader ||= begin
- Chef::Cookbook::FileVendor.fetch_from_disk(config[:cookbook_path])
- Chef::CookbookLoader.new(config[:cookbook_path])
- end
- end
-
- def update_version_constraints(new_version_constraints)
- new_version_constraints.each do |cookbook_name, version|
- environment.cookbook_versions[cookbook_name] = "= #{version}"
- end
- environment.save
- end
-
- def environment
- @environment ||= config[:environment] ? Environment.load(config[:environment]) : nil
- end
-
- private
-
- def assert_environment_valid!
- environment
- rescue Net::HTTPClientException => e
- if e.response.code.to_s == "404"
- ui.error "The environment #{config[:environment]} does not exist on the server, aborting."
- Log.debug(e)
- exit 1
- else
- raise
- end
- end
-
- def upload(cookbooks, justify_width)
- cookbooks.each do |cb|
- ui.info("Uploading #{cb.name.to_s.ljust(justify_width + 10)} [#{cb.version}]")
- check_for_broken_links!(cb)
- check_for_dependencies!(cb)
- end
- Chef::CookbookUploader.new(cookbooks, force: config[:force], concurrency: config[:concurrency]).upload_cookbooks
- rescue Chef::Exceptions::CookbookFrozen => e
- ui.error e
- raise
- end
-
- def check_for_broken_links!(cookbook)
- # MUST!! dup the cookbook version object--it memoizes its
- # manifest object, but the manifest becomes invalid when you
- # regenerate the metadata
- broken_files = cookbook.dup.manifest_records_by_path.select do |path, info|
- !/[0-9a-f]{32,}/.match?(info["checksum"])
- end
- unless broken_files.empty?
- broken_filenames = Array(broken_files).map { |path, info| path }
- ui.error "The cookbook #{cookbook.name} has one or more broken files"
- ui.error "This is probably caused by broken symlinks in the cookbook directory"
- ui.error "The broken file(s) are: #{broken_filenames.join(" ")}"
- exit 1
- end
- end
-
- def check_for_dependencies!(cookbook)
- # for all dependencies, check if the version is on the server, or
- # the version is in the cookbooks being uploaded. If not, exit and warn the user.
- missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version|
- check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
- end
-
- unless missing_dependencies.empty?
- missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'" }
- ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently"
- ui.error "being uploaded and cannot be found on the server."
- ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(", ")}"
- exit 1
- end
- end
-
- def check_server_side_cookbooks(cookbook_name, version)
- if @server_side_cookbooks[cookbook_name].nil?
- false
- else
- versions = @server_side_cookbooks[cookbook_name]["versions"].collect { |versions| versions["version"] }
- Log.debug "Versions of cookbook '#{cookbook_name}' returned by the server: #{versions.join(", ")}"
- @server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash|
- if Chef::VersionConstraint.new(version).include?(versions_hash["version"])
- Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash["version"]}' on the server"
- return true
- end
- end
- false
- end
- end
-
- def check_uploading_cookbooks(cookbook_name, version)
- if (! cookbooks_to_upload[cookbook_name].nil?) && Chef::VersionConstraint.new(version).include?(cookbooks_to_upload[cookbook_name].version)
- Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to a local cookbook."
- return true
- end
- false
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
deleted file mode 100644
index 9aa81da82f..0000000000
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ /dev/null
@@ -1,264 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../../run_list"
-require_relative "../../util/path_helper"
-require "pathname" unless defined?(Pathname)
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- module Core
- # Instances of BootstrapContext are the context objects (i.e., +self+) for
- # bootstrap templates. For backwards compatibility, they +must+ set the
- # following instance variables:
- # * @config - a hash of knife's config values
- # * @run_list - the run list for the node to bootstrap
- #
- class BootstrapContext
-
- attr_accessor :client_pem
- attr_accessor :config
- attr_accessor :chef_config
-
- def initialize(config, run_list, chef_config, secret = nil)
- @config = config
- @run_list = run_list
- @chef_config = chef_config
- @secret = secret
- end
-
- def bootstrap_environment
- config[:environment]
- end
-
- def validation_key
- if chef_config[:validation_key] &&
- File.exist?(File.expand_path(chef_config[:validation_key]))
- IO.read(File.expand_path(chef_config[:validation_key]))
- else
- false
- end
- end
-
- def client_d
- @client_d ||= client_d_content
- end
-
- def encrypted_data_bag_secret
- @secret
- end
-
- # Contains commands and content, see trusted_certs_content
- # @todo Rename to trusted_certs_script
- def trusted_certs
- @trusted_certs ||= trusted_certs_content
- end
-
- def get_log_location
- if !(chef_config[:config_log_location].class == IO ) && (chef_config[:config_log_location].nil? || chef_config[:config_log_location].to_s.empty?)
- "STDOUT"
- elsif chef_config[:config_log_location].equal?(:win_evt)
- raise "The value :win_evt is not supported for config_log_location on Linux Platforms \n"
- elsif chef_config[:config_log_location].equal?(:syslog)
- ":syslog"
- elsif chef_config[:config_log_location].equal?(STDOUT)
- "STDOUT"
- elsif chef_config[:config_log_location].equal?(STDERR)
- "STDERR"
- elsif chef_config[:config_log_location]
- %Q{"#{chef_config[:config_log_location]}"}
- else
- "STDOUT"
- end
- end
-
- def config_content
- client_rb = <<~CONFIG
- chef_server_url "#{chef_config[:chef_server_url]}"
- validation_client_name "#{chef_config[:validation_client_name]}"
- CONFIG
-
- unless chef_config[:chef_license].nil?
- client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
- end
-
- unless chef_config[:config_log_level].nil? || chef_config[:config_log_level].empty?
- client_rb << %Q{log_level :#{chef_config[:config_log_level]}\n}
- end
-
- client_rb << "log_location #{get_log_location}\n"
-
- if config[:chef_node_name]
- client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
- else
- client_rb << "# Using default node name (fqdn)\n"
- end
-
- # We configure :verify_api_cert only when it's overridden on the CLI
- # or when specified in the knife config.
- if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
- value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
- client_rb << %Q{verify_api_cert #{value}\n}
- end
-
- # We configure :ssl_verify_mode only when it's overridden on the CLI
- # or when specified in the knife config.
- if config[:node_ssl_verify_mode] || config.key?(:ssl_verify_mode)
- value = case config[:node_ssl_verify_mode]
- when "peer"
- :verify_peer
- when "none"
- :verify_none
- when nil
- config[:ssl_verify_mode]
- else
- nil
- end
-
- if value
- client_rb << %Q{ssl_verify_mode :#{value}\n}
- end
- end
-
- if config[:ssl_verify_mode]
- client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
- end
-
- if config[:bootstrap_proxy]
- client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
- client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
- end
-
- if config[:bootstrap_proxy_user]
- client_rb << %Q{http_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
- client_rb << %Q{https_proxy_user "#{config[:bootstrap_proxy_user]}"\n}
- end
-
- if config[:bootstrap_proxy_pass]
- client_rb << %Q{http_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
- client_rb << %Q{https_proxy_pass "#{config[:bootstrap_proxy_pass]}"\n}
- end
-
- if config[:bootstrap_no_proxy]
- client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
- end
-
- if encrypted_data_bag_secret
- client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n}
- end
-
- unless trusted_certs.empty?
- client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n}
- end
-
- if chef_config[:fips]
- client_rb << "fips true\n"
- end
-
- unless chef_config[:file_cache_path].nil?
- client_rb << "file_cache_path \"#{chef_config[:file_cache_path]}\"\n"
- end
-
- unless chef_config[:file_backup_path].nil?
- client_rb << "file_backup_path \"#{chef_config[:file_backup_path]}\"\n"
- end
-
- client_rb
- end
-
- def start_chef
- # If the user doesn't have a client path configure, let bash use the PATH for what it was designed for
- client_path = chef_config[:chef_client_path] || ChefUtils::Dist::Infra::CLIENT
- s = "#{client_path} -j /etc/chef/first-boot.json"
- if config[:verbosity] && config[:verbosity] >= 3
- s << " -l trace"
- elsif config[:verbosity] && config[:verbosity] >= 2
- s << " -l debug"
- end
- s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
- s << " --no-color" unless config[:color]
- s
- end
-
- #
- # Returns the version of Chef to install (as recognized by the Omnitruck API)
- #
- # @return [String] download version string
- def version_to_install
- return config[:bootstrap_version] if config[:bootstrap_version]
-
- if config[:channel] == "stable"
- Chef::VERSION.split(".").first
- else
- "latest"
- end
- end
-
- def first_boot
- (config[:first_boot_attributes] = Mash.new(config[:first_boot_attributes]) || Mash.new).tap do |attributes|
- if config[:policy_name] && config[:policy_group]
- attributes[:policy_name] = config[:policy_name]
- attributes[:policy_group] = config[:policy_group]
- else
- attributes[:run_list] = @run_list
- end
- attributes.delete(:run_list) if attributes[:policy_name] && !attributes[:policy_name].empty?
- attributes.merge!(tags: config[:tags]) if config[:tags] && !config[:tags].empty?
- end
- end
-
- private
-
- # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
- # This string should contain both the commands necessary to both create the files, as well as their content
- def trusted_certs_content
- content = ""
- if chef_config[:trusted_certs_dir]
- Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
- content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" +
- IO.read(File.expand_path(cert)) + "\nEOP\n"
- end
- end
- content
- end
-
- def client_d_content
- content = ""
- if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
- root = Pathname(chef_config[:client_d_dir])
- root.find do |f|
- relative = f.relative_path_from(root)
- if f != root
- file_on_node = "/etc/chef/client.d/#{relative}"
- if f.directory?
- content << "mkdir #{file_on_node}\n"
- else
- content << "cat > #{file_on_node} <<'EOP'\n" +
- f.read.gsub("'", "'\\\\''") + "\nEOP\n"
- end
- end
- end
- end
- content
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/cookbook_scm_repo.rb b/lib/chef/knife/core/cookbook_scm_repo.rb
deleted file mode 100644
index ba194a8a6d..0000000000
--- a/lib/chef/knife/core/cookbook_scm_repo.rb
+++ /dev/null
@@ -1,159 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../../mixin/shell_out"
-
-class Chef
- class Knife
- class CookbookSCMRepo
-
- DIRTY_REPO = /^\s+M/.freeze
-
- include Chef::Mixin::ShellOut
-
- attr_reader :repo_path
- attr_reader :default_branch
- attr_reader :use_current_branch
- attr_reader :ui
-
- def initialize(repo_path, ui, opts = {})
- @repo_path = repo_path
- @ui = ui
- @default_branch = "master"
- @use_current_branch = false
- apply_opts(opts)
- end
-
- def sanity_check
- unless ::File.directory?(repo_path)
- ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory")
- exit 1
- end
- unless git_repo?(repo_path)
- ui.error "The cookbook repo #{repo_path} is not a git repository."
- ui.info("Use `git init` to initialize a git repo")
- exit 1
- end
- if use_current_branch
- @default_branch = get_current_branch
- end
- unless branch_exists?(default_branch)
- ui.error "The default branch '#{default_branch}' does not exist"
- ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
- exit 1
- end
- cmd = git("status --porcelain")
- if DIRTY_REPO.match?(cmd.stdout)
- ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
- ui.msg cmd.stdout
- ui.info "Commit or stash your changes before importing cookbooks"
- exit 1
- end
- # TODO: any untracked files in the cookbook directory will get nuked later
- # make this an error condition also.
- true
- end
-
- def reset_to_default_state
- ui.info("Checking out the #{default_branch} branch.")
- git("checkout #{default_branch}")
- end
-
- def prepare_to_import(cookbook_name)
- branch = "chef-vendor-#{cookbook_name}"
- if branch_exists?(branch)
- ui.info("Pristine copy branch (#{branch}) exists, switching to it.")
- git("checkout #{branch}")
- else
- ui.info("Creating pristine copy branch #{branch}")
- git("checkout -b #{branch}")
- end
- end
-
- def finalize_updates_to(cookbook_name, version)
- if update_count = updated?(cookbook_name)
- ui.info "#{update_count} files updated, committing changes"
- git("add #{cookbook_name}")
- git("commit -m \"Import #{cookbook_name} version #{version}\" -- #{cookbook_name}")
- ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}")
- git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}")
- true
- else
- ui.info("No changes made to #{cookbook_name}")
- false
- end
- end
-
- def merge_updates_from(cookbook_name, version)
- branch = "chef-vendor-#{cookbook_name}"
- Dir.chdir(repo_path) do
- if system("git merge #{branch}")
- ui.info("Cookbook #{cookbook_name} version #{version} successfully installed")
- else
- ui.error("You have merge conflicts - please resolve manually")
- ui.info("Merge status (cd #{repo_path}; git status):")
- system("git status")
- exit 3
- end
- end
- end
-
- def updated?(cookbook_name)
- update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count
- update_count == 0 ? nil : update_count
- end
-
- def branch_exists?(branch_name)
- git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ }
- end
-
- def get_current_branch
- ref = git("symbolic-ref HEAD").stdout
- ref.chomp.split("/")[2]
- end
-
- private
-
- def git_repo?(directory)
- if File.directory?(File.join(directory, ".git"))
- true
- elsif File.dirname(directory) == directory
- false
- else
- git_repo?(File.dirname(directory))
- end
- end
-
- def apply_opts(opts)
- opts.each do |option, value|
- case option.to_s
- when "default_branch"
- @default_branch = value
- when "use_current_branch"
- @use_current_branch = value
- end
- end
- end
-
- def git(command)
- shell_out!("git #{command}", cwd: repo_path)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/core/gem_glob_loader.rb b/lib/chef/knife/core/gem_glob_loader.rb
deleted file mode 100644
index d058379e71..0000000000
--- a/lib/chef/knife/core/gem_glob_loader.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../../version"
-require_relative "../../util/path_helper"
-class Chef
- class Knife
- class SubcommandLoader
- class GemGlobLoader < Chef::Knife::SubcommandLoader
- MATCHES_CHEF_GEM ||= %r{/chef-\d+\.\d+\.\d+}.freeze
- MATCHES_THIS_CHEF_GEM ||= %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
-
- def subcommand_files
- @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
- end
-
- # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
- # If rubygems is not installed, falls back to globbing the knife directory.
- # The Hash is of the form {"relative/path" => "/absolute/path"}
- #--
- # Note: the "right" way to load the plugins is to require the relative path, i.e.,
- # require 'chef/knife/command'
- # but we're getting frustrated by bugs at every turn, and it's slow besides. So
- # subcommand loader has been modified to load the plugins by using Kernel.load
- # with the absolute path.
- def gem_and_builtin_subcommands
- require "rubygems" unless defined?(Gem)
- find_subcommands_via_rubygems
- rescue LoadError
- find_subcommands_via_dirglob
- end
-
- def find_subcommands_via_dirglob
- # The "require paths" of the core knife subcommands bundled with chef
- files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../knife", __dir__)), "*.rb")]
- subcommand_files = {}
- files.each do |knife_file|
- rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1]
- subcommand_files[rel_path] = knife_file
- end
- subcommand_files
- end
-
- def find_subcommands_via_rubygems
- files = find_files_latest_gems "chef/knife/*.rb"
- subcommand_files = {}
- files.each do |file|
- rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
-
- # When not installed as a gem (ChefDK/appbundler in particular), AND
- # a different version of Chef is installed via gems, `files` will
- # include some files from the 'other' Chef install. If this contains
- # a knife command that doesn't exist in this version of Chef, we will
- # get a LoadError later when we try to require it.
- next if from_different_chef_version?(file)
-
- subcommand_files[rel_path] = file
- end
-
- subcommand_files.merge(find_subcommands_via_dirglob)
- end
-
- private
-
- def find_files_latest_gems(glob, check_load_path = true)
- files = []
-
- if check_load_path
- files = $LOAD_PATH.map do |load_path|
- Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob_dir(load_path)}#{Gem.suffix_pattern}"]
- end.flatten.select { |file| File.file? file.untaint }
- end
-
- gem_files = latest_gem_specs.map do |spec|
- # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
- if spec.respond_to? :matches_for_glob
- spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
- else
- check_spec_for_glob(spec, glob)
- end
- end.flatten
-
- files.concat gem_files
- files.uniq! if check_load_path
-
- files
- end
-
- def latest_gem_specs
- @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
- Gem::Specification.latest_specs(true) # find prerelease gems
- else
- Gem.source_index.latest_specs(true)
- end
- end
-
- def check_spec_for_glob(spec, glob)
- dirs = if spec.require_paths.size > 1
- "{#{spec.require_paths.join(",")}}"
- else
- spec.require_paths.first
- end
-
- glob = File.join(Chef::Util::PathHelper.escape_glob_dir(spec.full_gem_path, dirs), glob)
-
- Dir[glob].map(&:untaint)
- end
-
- def from_different_chef_version?(path)
- matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
- end
-
- def matches_any_chef_gem?(path)
- path =~ MATCHES_CHEF_GEM
- end
-
- def matches_this_chef_gem?(path)
- path =~ MATCHES_THIS_CHEF_GEM
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
deleted file mode 100644
index c1d71f3edf..0000000000
--- a/lib/chef/knife/core/hashed_command_loader.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../../version"
-class Chef
- class Knife
- class SubcommandLoader
- #
- # Load a subcommand from a pre-computed path
- # for the given command.
- #
- class HashedCommandLoader < Chef::Knife::SubcommandLoader
- KEY = "_autogenerated_command_paths".freeze
-
- attr_accessor :manifest
-
- def initialize(chef_config_dir, plugin_manifest)
- super(chef_config_dir)
- @manifest = plugin_manifest
- end
-
- def guess_category(args)
- category_words = positional_arguments(args)
- category_words.map! { |w| w.split("-") }.flatten!
- find_longest_key(manifest[KEY]["plugins_by_category"], category_words, " ")
- end
-
- def list_commands(pref_category = nil)
- if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category)
- commands = { pref_category => manifest[KEY]["plugins_by_category"][pref_category] }
- else
- commands = manifest[KEY]["plugins_by_category"]
- end
- # If any of the specified plugins in the manifest don't have a valid path we will
- # eventually get an error and the user will need to rehash - instead, lets just
- # print out 1 error here telling them to rehash
- errors = {}
- commands.collect { |k, v| v }.flatten.each do |command|
- paths = manifest[KEY]["plugins_paths"][command]
- if paths && paths.is_a?(Array)
- # It is only an error if all the paths don't exist
- if paths.all? { |sc| !File.exist?(sc) }
- errors[command] = paths
- end
- end
- end
- if errors.empty?
- commands
- else
- Chef::Log.error "There are plugin files specified in the knife cache that cannot be found. Please run knife rehash to update the subcommands cache. If you see this error after rehashing delete the cache at #{Chef::Knife::SubcommandLoader.plugin_manifest_path}"
- Chef::Log.error "Missing files:\n\t#{errors.values.flatten.join("\n\t")}"
- {}
- end
- end
-
- def subcommand_files
- manifest[KEY]["plugins_paths"].values.flatten
- end
-
- def load_command(args)
- paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)]
- if paths.nil? || paths.empty? || (! paths.is_a? Array)
- false
- else
- paths.each do |sc|
- if File.exist?(sc)
- Kernel.load sc
- else
- return false
- end
- end
- true
- end
- end
-
- def subcommand_for_args(args)
- if manifest[KEY]["plugins_paths"].key?(args)
- args
- else
- find_longest_key(manifest[KEY]["plugins_paths"], args, "_")
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/node_editor.rb b/lib/chef/knife/core/node_editor.rb
deleted file mode 100644
index 2f9b326d16..0000000000
--- a/lib/chef/knife/core/node_editor.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright (c) 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 "../../json_compat"
-require_relative "../../node"
-
-class Chef
- class Knife
- class NodeEditor
- attr_reader :node, :ui, :config
- private :node, :ui, :config
-
- # @param node [Chef::Node]
- # @param ui [Chef::Knife::UI]
- # @param config [Hash]
- def initialize(node, ui, config)
- @node, @ui, @config = node, ui, config
- end
-
- # Opens the node data (as JSON) in the user's editor and returns a new
- # {Chef::Node} reflecting the user's changes.
- #
- # @return [Chef::Node]
- def edit_node
- abort "You specified the --disable_editing option, nothing to edit" if config[:disable_editing]
- assert_editor_set!
-
- updated_node_data = ui.edit_hash(view)
- apply_updates(updated_node_data)
- @updated_node
- end
-
- # Returns an array of the names of properties that have been changed or
- # +false+ if none were changed.
- #
- # @return [Array<String>] if any properties have been changed.
- # @return [false] if no properties have been changed.
- def updated?
- return false if @updated_node.nil?
-
- pristine_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(node))
- updated_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@updated_node))
-
- updated_properties = %w{
- name
- chef_environment
- automatic
- default
- normal
- override
- policy_name
- policy_group
- run_list
- }.reject do |key|
- pristine_copy[key] == updated_copy[key]
- end
-
- updated_properties.any? && updated_properties
- end
-
- # @api private
- def view
- result = {
- "name" => node.name,
- "chef_environment" => node.chef_environment,
- "normal" => node.normal_attrs,
- "policy_name" => node.policy_name,
- "policy_group" => node.policy_group,
- "run_list" => node.run_list,
- }
-
- if config[:all_attributes]
- result["default"] = node.default_attrs
- result["override"] = node.override_attrs
- result["automatic"] = node.automatic_attrs
- end
-
- result
- end
-
- # @api private
- def apply_updates(updated_data)
- if node.name && node.name != updated_data["name"]
- ui.warn "Changing the name of a node results in a new node being created, #{node.name} will not be modified or removed."
- ui.confirm "Proceed with creation of new node"
- end
-
- data = updated_data.dup
-
- unless config[:all_attributes]
- data["automatic"] = node.automatic_attrs
- data["default"] = node.default_attrs
- data["override"] = node.override_attrs
- end
-
- @updated_node = Node.from_hash(data)
- end
-
- private
-
- def abort(message)
- ui.error(message)
- exit 1
- end
-
- def assert_editor_set!
- unless config[:editor]
- abort "You must set your EDITOR environment variable or configure your editor via knife.rb"
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb
deleted file mode 100644
index 6690bc1075..0000000000
--- a/lib/chef/knife/core/node_presenter.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "text_formatter"
-require_relative "generic_presenter"
-
-class Chef
- class Knife
- module Core
-
- # This module may be included into a knife subcommand class to automatically
- # add configuration options used by the NodePresenter
- module NodeFormattingOptions
- # @private
- # Would prefer to do this in a rational way, but can't be done b/c of
- # Mixlib::CLI's design :(
- def self.included(includer)
- includer.class_eval do
- option :medium_output,
- short: "-m",
- long: "--medium",
- boolean: true,
- default: false,
- description: "Include normal attributes in the output"
-
- option :long_output,
- short: "-l",
- long: "--long",
- boolean: true,
- default: false,
- description: "Include all attributes in the output"
- end
- end
- end
-
- # A customized presenter for Chef::Node objects. Supports variable-length
- # output formats for displaying node data
- class NodePresenter < GenericPresenter
-
- def format(data)
- if parse_format_option == :json
- summarize_json(data)
- else
- super
- end
- end
-
- def summarize_json(data)
- if data.is_a?(Chef::Node)
- node = data
- result = {}
-
- result["name"] = node.name
- if node.policy_name.nil? && node.policy_group.nil?
- result["chef_environment"] = node.chef_environment
- else
- result["policy_name"] = node.policy_name
- result["policy_group"] = node.policy_group
- end
- result["run_list"] = node.run_list
- result["normal"] = node.normal_attrs
-
- if config[:long_output]
- result["default"] = node.default_attrs
- result["override"] = node.override_attrs
- result["automatic"] = node.automatic_attrs
- end
-
- Chef::JSONCompat.to_json_pretty(result)
- else
- Chef::JSONCompat.to_json_pretty(data)
- end
- end
-
- # Converts a Chef::Node object to a string suitable for output to a
- # terminal. If config[:medium_output] or config[:long_output] are set
- # the volume of output is adjusted accordingly. Uses colors if enabled
- # in the ui object.
- def summarize(data)
- if data.is_a?(Chef::Node)
- node = data
- # special case clouds with their split horizon thing.
- ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
-
- summarized = <<~SUMMARY
- #{ui.color("Node Name:", :bold)} #{ui.color(node.name, :bold)}
- SUMMARY
- show_policy = !(node.policy_name.nil? && node.policy_group.nil?)
- if show_policy
- summarized << <<~POLICY
- #{key("Policy Name:")} #{node.policy_name}
- #{key("Policy Group:")} #{node.policy_group}
- POLICY
- else
- summarized << <<~ENV
- #{key("Environment:")} #{node.chef_environment}
- ENV
- end
- summarized << <<~SUMMARY
- #{key("FQDN:")} #{node[:fqdn]}
- #{key("IP:")} #{ip}
- #{key("Run List:")} #{node.run_list}
- SUMMARY
- unless show_policy
- summarized << <<~ROLES
- #{key("Roles:")} #{Array(node[:roles]).join(", ")}
- ROLES
- end
- summarized << <<~SUMMARY
- #{key("Recipes:")} #{Array(node[:recipes]).join(", ")}
- #{key("Platform:")} #{node[:platform]} #{node[:platform_version]}
- #{key("Tags:")} #{node.tags.join(", ")}
- SUMMARY
- if config[:medium_output] || config[:long_output]
- summarized += <<~MORE
- #{key("Attributes:")}
- #{text_format(node.normal_attrs)}
- MORE
- end
- if config[:long_output]
- summarized += <<~MOST
- #{key("Default Attributes:")}
- #{text_format(node.default_attrs)}
- #{key("Override Attributes:")}
- #{text_format(node.override_attrs)}
- #{key("Automatic Attributes (Ohai Data):")}
- #{text_format(node.automatic_attrs)}
- MOST
- end
- summarized
- else
- super
- end
- end
-
- def key(key_text)
- ui.color(key_text, :cyan)
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
deleted file mode 100644
index 5421fc46ce..0000000000
--- a/lib/chef/knife/core/object_loader.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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.
-#
-
-autoload :FFI_Yajl, "ffi_yajl"
-require_relative "../../util/path_helper"
-require_relative "../../data_bag_item"
-
-class Chef
- class Knife
- module Core
- class ObjectLoader
-
- attr_reader :ui
- attr_reader :klass
-
- class ObjectType
- FILE = 1
- FOLDER = 2
- end
-
- def initialize(klass, ui)
- @klass = klass
- @ui = ui
- end
-
- def load_from(repo_location, *components)
- unless object_file = find_file(repo_location, *components)
- ui.error "Could not find or open file '#{components.last}' in current directory or in '#{repo_location}/#{components.join("/")}'"
- exit 1
- end
- object_from_file(object_file)
- end
-
- # When someone makes this awesome, please update the above error message.
- def find_file(repo_location, *components)
- if file_exists_and_is_readable?(File.expand_path( components.last ))
- File.expand_path( components.last )
- else
- relative_path = File.join(Dir.pwd, repo_location, *components)
- if file_exists_and_is_readable?(relative_path)
- relative_path
- else
- nil
- end
- end
- end
-
- # Find all objects in the given location
- # If the object type is File it will look for all *.{json,rb}
- # files, otherwise it will lookup for folders only (useful for
- # data_bags)
- #
- # @param [String] path - base look up location
- #
- # @return [Array<String>] basenames of the found objects
- #
- # @api public
- def find_all_objects(path)
- path = File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path(path)), "*")
- path << ".{json,rb}"
- objects = Dir.glob(path)
- objects.map { |o| File.basename(o) }
- end
-
- def find_all_object_dirs(path)
- path = File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path(path)), "*")
- objects = Dir.glob(path)
- objects.delete_if { |o| !File.directory?(o) }
- objects.map { |o| File.basename(o) }
- end
-
- def object_from_file(filename)
- case filename
- when /\.(js|json)$/
- r = FFI_Yajl::Parser.parse(IO.read(filename))
-
- # Chef::DataBagItem doesn't work well with the json_create method
- if @klass == Chef::DataBagItem
- r
- else
- @klass.from_hash(r)
- end
- when /\.rb$/
- r = klass.new
- r.from_file(filename)
- r
- else
- ui.fatal("File must end in .js, .json, or .rb")
- exit 30
- end
- end
-
- def file_exists_and_is_readable?(file)
- File.exist?(file) && File.readable?(file)
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb
deleted file mode 100644
index 9a4ea76508..0000000000
--- a/lib/chef/knife/core/status_presenter.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-#
-# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
-# Copyright:: Copyright (c) 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 "text_formatter"
-require_relative "generic_presenter"
-
-class Chef
- class Knife
- module Core
-
- # This module may be included into a knife subcommand class to automatically
- # add configuration options used by the StatusPresenter
- module StatusFormattingOptions
- # @private
- # Would prefer to do this in a rational way, but can't be done b/c of
- # Mixlib::CLI's design :(
- def self.included(includer)
- includer.class_eval do
- option :medium_output,
- short: "-m",
- long: "--medium",
- boolean: true,
- default: false,
- description: "Include normal attributes in the output"
-
- option :long_output,
- short: "-l",
- long: "--long",
- boolean: true,
- default: false,
- description: "Include all attributes in the output"
- end
- end
- end
-
- # A customized presenter for Chef::Node objects. Supports variable-length
- # output formats for displaying node data
- class StatusPresenter < GenericPresenter
-
- def format(data)
- if parse_format_option == :json
- summarize_json(data)
- else
- super
- end
- end
-
- def summarize_json(list)
- result_list = []
- list.each do |node|
- result = {}
-
- result["name"] = node["name"] || node.name
- result["chef_environment"] = node["chef_environment"]
- ip = (node["cloud"] && node["cloud"]["public_ipv4_addrs"].first) || node["ipaddress"]
- fqdn = (node["cloud"] && node["cloud"]["public_hostname"]) || node["fqdn"]
- result["ip"] = ip if ip
- result["fqdn"] = fqdn if fqdn
- result["run_list"] = node.run_list if config["run_list"]
- result["ohai_time"] = node["ohai_time"]
- result["platform"] = node["platform"] if node["platform"]
- result["platform_version"] = node["platform_version"] if node["platform_version"]
-
- if config[:long_output]
- result["default"] = node.default_attrs
- result["override"] = node.override_attrs
- result["automatic"] = node.automatic_attrs
- end
- result_list << result
- end
-
- Chef::JSONCompat.to_json_pretty(result_list)
- end
-
- # Converts a Chef::Node object to a string suitable for output to a
- # terminal. If config[:medium_output] or config[:long_output] are set
- # the volume of output is adjusted accordingly. Uses colors if enabled
- # in the ui object.
- def summarize(list)
- summarized = ""
- list.each do |data|
- node = data
- # special case clouds with their split horizon thing.
- ip = (node[:cloud] && node[:cloud][:public_ipv4_addrs] && node[:cloud][:public_ipv4_addrs].first) || node[:ipaddress]
- fqdn = (node[:cloud] && node[:cloud][:public_hostname]) || node[:fqdn]
- name = node["name"] || node.name
-
- if config[:run_list]
- if config[:long_output]
- run_list = node.run_list.map { |rl| "#{rl.type}[#{rl.name}]" }
- else
- run_list = node["run_list"]
- end
- end
-
- line_parts = []
-
- if node["ohai_time"]
- hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
- hours_text = "#{hours} hour#{hours == 1 ? " " : "s"}"
- minutes_text = "#{minutes} minute#{minutes == 1 ? " " : "s"}"
- seconds_text = "#{seconds} second#{seconds == 1 ? " " : "s"}"
- if hours > 24
- color = :red
- text = hours_text
- elsif hours >= 1
- color = :yellow
- text = hours_text
- elsif minutes >= 1
- color = :green
- text = minutes_text
- else
- color = :green
- text = seconds_text
- end
- line_parts << @ui.color(text, color) + " ago" << name
- else
- line_parts << "Node #{name} has not yet converged"
- end
-
- line_parts << fqdn if fqdn
- line_parts << ip if ip
- line_parts << run_list.to_s if run_list
-
- if node["platform"]
- platform = node["platform"].dup
- if node["platform_version"]
- platform << " #{node["platform_version"]}"
- end
- line_parts << platform
- end
-
- summarized = summarized + line_parts.join(", ") + ".\n"
- end
- summarized
- end
-
- def key(key_text)
- ui.color(key_text, :cyan)
- end
-
- # @private
- # @todo this is duplicated from StatusHelper in the Webui. dedup.
- def time_difference_in_hms(unix_time)
- now = Time.now.to_i
- difference = now - unix_time.to_i
- hours = (difference / 3600).to_i
- difference = difference % 3600
- minutes = (difference / 60).to_i
- seconds = (difference % 60)
- [hours, minutes, seconds]
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
deleted file mode 100644
index 26d7e0277c..0000000000
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ /dev/null
@@ -1,203 +0,0 @@
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../../version"
-require_relative "../../util/path_helper"
-require_relative "gem_glob_loader"
-require_relative "hashed_command_loader"
-
-class Chef
- class Knife
- #
- # Public Methods of a Subcommand Loader
- #
- # load_commands - loads all available subcommands
- # load_command(args) - loads subcommands for the given args
- # list_commands(args) - lists all available subcommands,
- # optionally filtering by category
- # subcommand_files - returns an array of all subcommand files
- # that could be loaded
- # command_class_from(args) - returns the subcommand class for the
- # user-requested command
- #
- class SubcommandLoader
- attr_reader :chef_config_dir
-
- # A small factory method. Eventually, this is the only place
- # where SubcommandLoader should know about its subclasses, but
- # to maintain backwards compatibility many of the instance
- # methods in this base class contain default implementations
- # of the functions sub classes should otherwise provide
- # or directly instantiate the appropriate subclass
- def self.for_config(chef_config_dir)
- if autogenerated_manifest?
- Chef::Log.trace("Using autogenerated hashed command manifest #{plugin_manifest_path}")
- Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
- else
- Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
- end
- end
-
- # There are certain situations where we want to shortcut the loader selection
- # in self.for_config and force using the GemGlobLoader
- def self.gem_glob_loader(chef_config_dir)
- Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
- end
-
- def self.plugin_manifest?
- plugin_manifest_path && File.exist?(plugin_manifest_path)
- end
-
- def self.autogenerated_manifest?
- plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
- end
-
- def self.plugin_manifest
- Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
- end
-
- def self.plugin_manifest_path
- Chef::Util::PathHelper.home(".chef", "plugin_manifest.json")
- end
-
- def self.generate_hash
- output = if plugin_manifest?
- plugin_manifest
- else
- { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {} }
- end
- output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_paths"] = Chef::Knife.subcommand_files
- output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_by_category"] = Chef::Knife.subcommands_by_category
- output
- end
-
- def self.write_hash(data)
- plugin_manifest_dir = File.expand_path("..", plugin_manifest_path)
- FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
- File.open(plugin_manifest_path, "w") do |f|
- f.write(Chef::JSONCompat.to_json_pretty(data))
- end
- end
-
- def initialize(chef_config_dir)
- @chef_config_dir = chef_config_dir
- end
-
- # Load all the sub-commands
- def load_commands
- return true if @loaded
-
- subcommand_files.each { |subcommand| Kernel.load subcommand }
- @loaded = true
- end
-
- def force_load
- @loaded = false
- load_commands
- end
-
- def load_command(_command_args)
- load_commands
- end
-
- def list_commands(pref_cat = nil)
- load_commands
- if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
- { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
- else
- Chef::Knife.subcommands_by_category
- end
- end
-
- def command_class_from(args)
- cmd_words = positional_arguments(args)
- load_command(cmd_words)
- result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
- cmd_words, "_")]
- result || Chef::Knife.subcommands[args.first.tr("-", "_")]
- end
-
- def guess_category(args)
- category_words = positional_arguments(args)
- category_words.map! { |w| w.split("-") }.flatten!
- find_longest_key(Chef::Knife.subcommands_by_category,
- category_words, " ")
- end
-
- #
- # This is shared between the custom_manifest_loader and the gem_glob_loader
- #
- def find_subcommands_via_dirglob
- # The "require paths" of the core knife subcommands bundled with chef
- files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../knife", __dir__)), "*.rb")]
- subcommand_files = {}
- files.each do |knife_file|
- rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1]
- subcommand_files[rel_path] = knife_file
- end
- subcommand_files
- end
-
- #
- # Utility function for finding an element in a hash given an array
- # of words and a separator. We find the the longest key in the
- # hash composed of the given words joined by the separator.
- #
- def find_longest_key(hash, words, sep = "_")
- words = words.dup
- match = nil
- until match || words.empty?
- candidate = words.join(sep).tr("-", "_")
- if hash.key?(candidate)
- match = candidate
- else
- words.pop
- end
- end
- match
- end
-
- #
- # The positional arguments from the argument list provided by the
- # users. Used to search for subcommands and categories.
- #
- # @return [Array<String>]
- #
- def positional_arguments(args)
- args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
- end
-
- # Returns an Array of paths to knife commands located in
- # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
- def site_subcommands
- user_specific_files = []
-
- if chef_config_dir
- user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob_dir(chef_config_dir)))
- end
-
- # finally search ~/.chef/plugins/knife/*.rb
- Chef::Util::PathHelper.home(".chef", "plugins", "knife") do |p|
- user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(p), "*.rb"))
- end
-
- user_specific_files
- end
- end
- end
-end
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
deleted file mode 100644
index aa84537064..0000000000
--- a/lib/chef/knife/core/ui.rb
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Christopher Brown (<cb@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "forwardable" unless defined?(Forwardable)
-require_relative "../../platform/query_helpers"
-require_relative "generic_presenter"
-require "tempfile" unless defined?(Tempfile)
-
-class Chef
- class Knife
-
- # The User Interaction class used by knife.
- class UI
-
- extend Forwardable
-
- attr_reader :stdout
- attr_reader :stderr
- attr_reader :stdin
- attr_reader :config
-
- attr_reader :presenter
-
- def_delegator :@presenter, :format_list_for_display
- def_delegator :@presenter, :format_for_display
- def_delegator :@presenter, :format_cookbook_list_for_display
-
- def initialize(stdout, stderr, stdin, config)
- @stdout, @stderr, @stdin, @config = stdout, stderr, stdin, config
- @presenter = Chef::Knife::Core::GenericPresenter.new(self, config)
- end
-
- # Creates a new +presenter_class+ object and uses it to format structured
- # data for display. By default, a Chef::Knife::Core::GenericPresenter
- # object is used.
- def use_presenter(presenter_class)
- @presenter = presenter_class.new(self, config)
- end
-
- def highline
- @highline ||= begin
- require "highline"
- HighLine.new
- end
- end
-
- # Creates a new object of class TTY::Prompt
- # with interrupt as exit so that it can be terminated with status code.
- def prompt
- @prompt ||= begin
- require "tty-prompt"
- TTY::Prompt.new(interrupt: :exit)
- end
- end
-
- # pastel.decorate is a lightweight replacement for highline.color
- def pastel
- @pastel ||= begin
- require "pastel" unless defined?(Pastel)
- Pastel.new
- end
- end
-
- # Prints a message to stdout. Aliased as +info+ for compatibility with
- # the logger API.
- #
- # @param message [String] the text string
- def msg(message)
- stdout.puts message
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
-
- exit 0
- end
-
- # Prints a msg to stderr. Used for info, warn, error, and fatal.
- #
- # @param message [String] the text string
- def log(message)
- lines = message.split("\n")
- first_line = lines.shift
- stderr.puts first_line
- # If the message is multiple lines,
- # indent subsequent lines to align with the
- # log type prefix ("ERROR: ", etc)
- unless lines.empty?
- prefix, = first_line.split(":", 2)
- return if prefix.nil?
-
- prefix_len = prefix.length
- prefix_len -= 9 if color? # prefix includes 9 bytes of color escape sequences
- prefix_len += 2 # include room to align to the ": " following PREFIX
- padding = " " * prefix_len
- lines.each do |line|
- stderr.puts "#{padding}#{line}"
- end
- end
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
-
- exit 0
- end
-
- alias :info :log
- alias :err :log
-
- # Print a Debug
- #
- # @param message [String] the text string
- def debug(message)
- log("#{color("DEBUG:", :blue, :bold)} #{message}")
- end
-
- # Print a warning message
- #
- # @param message [String] the text string
- def warn(message)
- log("#{color("WARNING:", :yellow, :bold)} #{message}")
- end
-
- # Print an error message
- #
- # @param message [String] the text string
- def error(message)
- log("#{color("ERROR:", :red, :bold)} #{message}")
- end
-
- # Print a message describing a fatal error.
- #
- # @param message [String] the text string
- def fatal(message)
- log("#{color("FATAL:", :red, :bold)} #{message}")
- end
-
- # Print a message describing a fatal error and exit 1
- #
- # @param message [String] the text string
- def fatal!(message)
- fatal(message)
- exit 1
- end
-
- def color(string, *colors)
- if color?
- pastel.decorate(string, *colors)
- else
- string
- end
- end
-
- # Should colored output be used? For output to a terminal, this is
- # determined by the value of `config[:color]`. When output is not to a
- # terminal, colored output is never used
- def color?
- Chef::Config[:color] && stdout.tty?
- end
-
- def ask(*args, **options, &block)
- prompt.ask(*args, **options, &block)
- end
-
- def list(*args)
- highline.list(*args)
- end
-
- # Formats +data+ using the configured presenter and outputs the result
- # via +msg+. Formatting can be customized by configuring a different
- # presenter. See +use_presenter+
- def output(data)
- msg @presenter.format(data)
- end
-
- # Determines if the output format is a data interchange format, i.e.,
- # JSON or YAML
- def interchange?
- @presenter.interchange?
- end
-
- def ask_question(question, opts = {})
- question += "[#{opts[:default]}] " if opts[:default]
-
- if opts[:default] && config[:defaults]
- opts[:default]
- else
- stdout.print question
- a = stdin.readline.strip
-
- if opts[:default]
- a.empty? ? opts[:default] : a
- else
- a
- end
- end
- end
-
- def pretty_print(data)
- stdout.puts data
- rescue Errno::EPIPE => e
- raise e if @config[:verbosity] >= 2
-
- exit 0
- end
-
- # Hash -> Hash
- # Works the same as edit_data but
- # returns a hash rather than a JSON string/Fully inflated object
- def edit_hash(hash)
- raw = edit_data(hash, false)
- Chef::JSONCompat.parse(raw)
- end
-
- def edit_data(data, parse_output = true, object_class: nil)
- output = Chef::JSONCompat.to_json_pretty(data)
- unless config[:disable_editing]
- Tempfile.open([ "knife-edit-", ".json" ]) do |tf|
- tf.sync = true
- tf.puts output
- tf.close
- raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details." unless system("#{config[:editor]} #{tf.path}")
-
- output = IO.read(tf.path)
- end
- end
-
- if parse_output
- if object_class.nil?
- raise ArgumentError, "Please pass in the object class to hydrate or use #edit_hash"
- else
- object_class.from_hash(Chef::JSONCompat.parse(output))
- end
- else
- output
- end
- end
-
- def edit_object(klass, name)
- object = klass.load(name)
-
- output = edit_data(object, object_class: klass)
-
- # Only make the save if the user changed the object.
- #
- # Output JSON for the original (object) and edited (output), then parse
- # them without reconstituting the objects into real classes
- # (create_additions=false). Then, compare the resulting simple objects,
- # which will be Array/Hash/String/etc.
- #
- # We wouldn't have to do these shenanigans if all the editable objects
- # implemented to_hash, or if to_json against a hash returned a string
- # with stable key order.
- object_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(object))
- output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output))
- if object_parsed_again != output_parsed_again
- output.save
- msg("Saved #{output}")
- else
- msg("Object unchanged, not saving")
- end
- output(format_for_display(object)) if config[:print_after]
- end
-
- def confirmation_instructions(default_choice)
- case default_choice
- when true
- "? (Y/n) "
- when false
- "? (y/N) "
- else
- "? (Y/N) "
- end
- end
-
- # See confirm method for argument information
- def confirm_without_exit(question, append_instructions = true, default_choice = nil)
- return true if config[:yes]
-
- stdout.print question
- stdout.print confirmation_instructions(default_choice) if append_instructions
-
- answer = stdin.readline
- answer.chomp!
-
- case answer
- when "Y", "y"
- true
- when "N", "n"
- msg("You said no, so I'm done here.")
- false
- when ""
- unless default_choice.nil?
- default_choice
- else
- msg("I have no idea what to do with '#{answer}'")
- msg("Just say Y or N, please.")
- confirm_without_exit(question, append_instructions, default_choice)
- end
- else
- msg("I have no idea what to do with '#{answer}'")
- msg("Just say Y or N, please.")
- confirm_without_exit(question, append_instructions, default_choice)
- end
- end
-
- #
- # Not the ideal signature for a function but we need to stick with this
- # for now until we get a chance to break our API in Chef 12.
- #
- # question => Question to print before asking for confirmation
- # append_instructions => Should print '? (Y/N)' as instructions
- # default_choice => Set to true for 'Y', and false for 'N' as default answer
- #
- def confirm(question, append_instructions = true, default_choice = nil)
- unless confirm_without_exit(question, append_instructions, default_choice)
- exit 3
- end
- true
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/core/windows_bootstrap_context.rb b/lib/chef/knife/core/windows_bootstrap_context.rb
deleted file mode 100644
index 4b40d5bfb9..0000000000
--- a/lib/chef/knife/core/windows_bootstrap_context.rb
+++ /dev/null
@@ -1,406 +0,0 @@
-#
-# Author:: Seth Chisamore (<schisamo@chef.io>)
-# Copyright:: Copyright (c) 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 "bootstrap_context"
-require_relative "../../util/path_helper"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- module Core
- # Instances of BootstrapContext are the context objects (i.e., +self+) for
- # bootstrap templates. For backwards compatibility, they +must+ set the
- # following instance variables:
- # * @config - a hash of knife's config values
- # * @run_list - the run list for the node to bootstrap
- #
- class WindowsBootstrapContext < BootstrapContext
- attr_accessor :config
- attr_accessor :chef_config
- attr_accessor :secret
-
- def initialize(config, run_list, chef_config, secret = nil)
- @config = config
- @run_list = run_list
- @chef_config = chef_config
- @secret = secret
- super(config, run_list, chef_config, secret)
- end
-
- def validation_key
- if File.exist?(File.expand_path(chef_config[:validation_key]))
- IO.read(File.expand_path(chef_config[:validation_key]))
- else
- false
- end
- end
-
- def encrypted_data_bag_secret
- escape_and_echo(@secret)
- end
-
- def trusted_certs_script
- @trusted_certs_script ||= trusted_certs_content
- end
-
- def config_content
- # The windows: true / windows: false in the block that follows is more than a bit weird. The way to read this is that we need
- # the e.g. var_chef_dir to be rendered for the windows value ("C:\chef"), but then we are rendering into a file to be read by
- # ruby, so we don't actually care about forward-vs-backslashes and by rendering into unix we avoid having to deal with the
- # double-backwhacking of everything. So we expect to see:
- #
- # file_cache_path "C:/chef"
- #
- # Which is mildly odd, but should be entirely correct as far as ruby cares.
- #
- client_rb = <<~CONFIG
- chef_server_url "#{chef_config[:chef_server_url]}"
- validation_client_name "#{chef_config[:validation_client_name]}"
- file_cache_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\cache"
- file_backup_path "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.var_chef_dir(windows: true))}\\\\backup"
- cache_options ({:path => "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\cache\\\\checksums", :skip_expires => true})
- CONFIG
-
- unless chef_config[:chef_license].nil?
- client_rb << "chef_license \"#{chef_config[:chef_license]}\"\n"
- end
-
- if config[:chef_node_name]
- client_rb << %Q{node_name "#{config[:chef_node_name]}"\n}
- else
- client_rb << "# Using default node name (fqdn)\n"
- end
-
- if config[:config_log_level]
- client_rb << %Q{log_level :#{config[:config_log_level]}\n}
- else
- client_rb << "log_level :auto\n"
- end
-
- client_rb << "log_location #{get_log_location}"
-
- # We configure :verify_api_cert only when it's overridden on the CLI
- # or when specified in the knife config.
- if !config[:node_verify_api_cert].nil? || config.key?(:verify_api_cert)
- value = config[:node_verify_api_cert].nil? ? config[:verify_api_cert] : config[:node_verify_api_cert]
- client_rb << %Q{verify_api_cert #{value}\n}
- end
-
- # We configure :ssl_verify_mode only when it's overridden on the CLI
- # or when specified in the knife config.
- if config[:node_ssl_verify_mode] || config.key?(:ssl_verify_mode)
- value = case config[:node_ssl_verify_mode]
- when "peer"
- :verify_peer
- when "none"
- :verify_none
- when nil
- config[:ssl_verify_mode]
- else
- nil
- end
-
- if value
- client_rb << %Q{ssl_verify_mode :#{value}\n}
- end
- end
-
- if config[:ssl_verify_mode]
- client_rb << %Q{ssl_verify_mode :#{config[:ssl_verify_mode]}\n}
- end
-
- if config[:bootstrap_proxy]
- client_rb << "\n"
- client_rb << %Q{http_proxy "#{config[:bootstrap_proxy]}"\n}
- client_rb << %Q{https_proxy "#{config[:bootstrap_proxy]}"\n}
- client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n} if config[:bootstrap_no_proxy]
- end
-
- if config[:bootstrap_no_proxy]
- client_rb << %Q{no_proxy "#{config[:bootstrap_no_proxy]}"\n}
- end
-
- if secret
- client_rb << %Q{encrypted_data_bag_secret "#{ChefConfig::PathHelper.escapepath(ChefConfig::Config.etc_chef_dir(windows: true))}\\\\encrypted_data_bag_secret"\n}
- end
-
- unless trusted_certs_script.empty?
- client_rb << %Q{trusted_certs_dir "#{ChefConfig::Config.etc_chef_dir(windows: true)}/trusted_certs"\n}
- end
-
- if chef_config[:fips]
- client_rb << "fips true\n"
- end
-
- escape_and_echo(client_rb)
- end
-
- def get_log_location
- if chef_config[:config_log_location].equal?(:win_evt)
- %Q{:#{chef_config[:config_log_location]}\n}
- elsif chef_config[:config_log_location].equal?(:syslog)
- raise "syslog is not supported for log_location on Windows OS\n"
- elsif chef_config[:config_log_location].equal?(STDOUT)
- "STDOUT\n"
- elsif chef_config[:config_log_location].equal?(STDERR)
- "STDERR\n"
- elsif chef_config[:config_log_location].nil? || chef_config[:config_log_location].empty?
- "STDOUT\n"
- elsif chef_config[:config_log_location]
- %Q{"#{chef_config[:config_log_location]}"\n}
- else
- "STDOUT\n"
- end
- end
-
- def start_chef
- c_opscode_dir = ChefConfig::PathHelper.cleanpath(ChefConfig::Config.c_opscode_dir, windows: true)
- client_rb = clean_etc_chef_file("client.rb")
- first_boot = clean_etc_chef_file("first-boot.json")
-
- bootstrap_environment_option = bootstrap_environment.nil? ? "" : " -E #{bootstrap_environment}"
-
- start_chef = "SET \"PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;#{c_opscode_dir}\\bin;#{c_opscode_dir}\\embedded\\bin\;%PATH%\"\n"
- start_chef << "#{ChefUtils::Dist::Infra::CLIENT} -c #{client_rb} -j #{first_boot}#{bootstrap_environment_option}\n"
- end
-
- def win_wget
- # I tried my best to figure out how to properly url decode and switch / to \
- # but this is VBScript - so I don't really care that badly.
- win_wget = <<~WGET
- url = WScript.Arguments.Named("url")
- path = WScript.Arguments.Named("path")
- proxy = null
- '* Vaguely attempt to handle file:// scheme urls by url unescaping and switching all
- '* / into \. Also assume that file:/// is a local absolute path and that file://<foo>
- '* is possibly a network file path.
- If InStr(url, "file://") = 1 Then
- url = Unescape(url)
- If InStr(url, "file:///") = 1 Then
- sourcePath = Mid(url, Len("file:///") + 1)
- Else
- sourcePath = Mid(url, Len("file:") + 1)
- End If
- sourcePath = Replace(sourcePath, "/", "\\")
-
- Set objFSO = CreateObject("Scripting.FileSystemObject")
- If objFSO.Fileexists(path) Then objFSO.DeleteFile path
- objFSO.CopyFile sourcePath, path, true
- Set objFSO = Nothing
-
- Else
- Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP")
- Set wshShell = CreateObject( "WScript.Shell" )
- Set objUserVariables = wshShell.Environment("USER")
-
- rem http proxy is optional
- rem attempt to read from HTTP_PROXY env var first
- On Error Resume Next
-
- If NOT (objUserVariables("HTTP_PROXY") = "") Then
- proxy = objUserVariables("HTTP_PROXY")
-
- rem fall back to named arg
- ElseIf NOT (WScript.Arguments.Named("proxy") = "") Then
- proxy = WScript.Arguments.Named("proxy")
- End If
-
- If NOT isNull(proxy) Then
- rem setProxy method is only available on ServerXMLHTTP 6.0+
- Set objXMLHTTP = CreateObject("MSXML2.ServerXMLHTTP.6.0")
- objXMLHTTP.setProxy 2, proxy
- End If
-
- On Error Goto 0
-
- objXMLHTTP.open "GET", url, false
- objXMLHTTP.send()
- If objXMLHTTP.Status = 200 Then
- Set objADOStream = CreateObject("ADODB.Stream")
- objADOStream.Open
- objADOStream.Type = 1
- objADOStream.Write objXMLHTTP.ResponseBody
- objADOStream.Position = 0
- Set objFSO = Createobject("Scripting.FileSystemObject")
- If objFSO.Fileexists(path) Then objFSO.DeleteFile path
- Set objFSO = Nothing
- objADOStream.SaveToFile path
- objADOStream.Close
- Set objADOStream = Nothing
- End If
- Set objXMLHTTP = Nothing
- End If
- WGET
- escape_and_echo(win_wget)
- end
-
- def win_wget_ps
- win_wget_ps = <<~WGET_PS
- param(
- [String] $remoteUrl,
- [String] $localPath
- )
-
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
-
- $ProxyUrl = $env:http_proxy;
- $webClient = new-object System.Net.WebClient;
-
- if ($ProxyUrl -ne '') {
- $WebProxy = New-Object System.Net.WebProxy($ProxyUrl,$true)
- $WebClient.Proxy = $WebProxy
- }
-
- $webClient.DownloadFile($remoteUrl, $localPath);
- WGET_PS
-
- escape_and_echo(win_wget_ps)
- end
-
- def install_chef
- # The normal install command uses regular double quotes in
- # the install command, so request such a string from install_command
- install_command('"') + "\n" + fallback_install_task_command
- end
-
- def clean_etc_chef_file(path)
- ChefConfig::PathHelper.cleanpath(etc_chef_file(path), windows: true)
- end
-
- def etc_chef_file(path)
- "#{bootstrap_directory}/#{path}"
- end
-
- def bootstrap_directory
- ChefConfig::Config.etc_chef_dir(windows: true)
- end
-
- def local_download_path
- "%TEMP%\\#{ChefUtils::Dist::Infra::CLIENT}-latest.msi"
- end
-
- # Build a URL to query www.chef.io that will redirect to the correct
- # Chef Infra msi download.
- def msi_url(machine_os = nil, machine_arch = nil, download_context = nil)
- if config[:msi_url].nil? || config[:msi_url].empty?
- url = "https://www.chef.io/chef/download?p=windows"
- url += "&pv=#{machine_os}" unless machine_os.nil?
- url += "&m=#{machine_arch}" unless machine_arch.nil?
- url += "&DownloadContext=#{download_context}" unless download_context.nil?
- url += "&channel=#{config[:channel]}"
- url += "&v=#{version_to_install}"
- else
- config[:msi_url]
- end
- end
-
- def first_boot
- escape_and_echo(super.to_json)
- end
-
- # escape WIN BATCH special chars
- # and prefixes each line with an
- # echo
- def escape_and_echo(file_contents)
- file_contents.gsub(/^(.*)$/, 'echo.\1').gsub(/([(<|>)^])/, '^\1')
- end
-
- private
-
- def install_command(executor_quote)
- "msiexec /qn /log #{executor_quote}%CHEF_CLIENT_MSI_LOG_PATH%#{executor_quote} /i #{executor_quote}%LOCAL_DESTINATION_MSI_PATH%#{executor_quote}"
- end
-
- # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
- # This string should contain both the commands necessary to both create the files, as well as their content
- def trusted_certs_content
- content = ""
- if chef_config[:trusted_certs_dir]
- Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
- content << "> #{bootstrap_directory}/trusted_certs/#{File.basename(cert)} (\n" +
- escape_and_echo(IO.read(File.expand_path(cert))) + "\n)\n"
- end
- end
- content
- end
-
- def client_d_content
- content = ""
- if chef_config[:client_d_dir] && File.exist?(chef_config[:client_d_dir])
- root = Pathname(chef_config[:client_d_dir])
- root.find do |f|
- relative = f.relative_path_from(root)
- if f != root
- file_on_node = "#{bootstrap_directory}/client.d/#{relative}".tr("/", "\\")
- if f.directory?
- content << "mkdir #{file_on_node}\n"
- else
- content << "> #{file_on_node} (\n" +
- escape_and_echo(IO.read(File.expand_path(f))) + "\n)\n"
- end
- end
- end
- end
- content
- end
-
- def fallback_install_task_command
- # This command will be executed by schtasks.exe in the batch
- # code below. To handle tasks that contain arguments that
- # need to be double quoted, schtasks allows the use of single
- # quotes that will later be converted to double quotes
- command = install_command("'")
- <<~EOH
- @set MSIERRORCODE=!ERRORLEVEL!
- @if ERRORLEVEL 1 (
- @echo WARNING: Failed to install #{ChefUtils::Dist::Infra::PRODUCT} MSI package in remote context with status code !MSIERRORCODE!.
- @echo WARNING: This may be due to a defect in operating system update KB2918614: http://support.microsoft.com/kb/2918614
- @set OLDLOGLOCATION="%CHEF_CLIENT_MSI_LOG_PATH%-fail.log"
- @move "%CHEF_CLIENT_MSI_LOG_PATH%" "!OLDLOGLOCATION!" > NUL
- @echo WARNING: Saving installation log of failure at !OLDLOGLOCATION!
- @echo WARNING: Retrying installation with local context...
- @schtasks /create /f /sc once /st 00:00:00 /tn chefclientbootstraptask /ru SYSTEM /rl HIGHEST /tr \"cmd /c #{command} & sleep 2 & waitfor /s %computername% /si chefclientinstalldone\"
-
- @if ERRORLEVEL 1 (
- @echo ERROR: Failed to create #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL! > "&2"
- ) else (
- @echo Successfully created scheduled task to install #{ChefUtils::Dist::Infra::PRODUCT}.
- @schtasks /run /tn chefclientbootstraptask
- @if ERRORLEVEL 1 (
- @echo ERROR: Failed to execute #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task with status code !ERRORLEVEL!. > "&2"
- ) else (
- @echo Successfully started #{ChefUtils::Dist::Infra::PRODUCT} installation scheduled task.
- @echo Waiting for installation to complete -- this may take a few minutes...
- waitfor chefclientinstalldone /t 600
- if ERRORLEVEL 1 (
- @echo ERROR: Timed out waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install
- ) else (
- @echo Finished waiting for #{ChefUtils::Dist::Infra::PRODUCT} package to install.
- )
- @schtasks /delete /f /tn chefclientbootstraptask > NUL
- )
- )
- ) else (
- @echo Successfully installed #{ChefUtils::Dist::Infra::PRODUCT} package.
- )
- EOH
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
deleted file mode 100644
index 11448c69b7..0000000000
--- a/lib/chef/knife/data_bag_create.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "data_bag_secret_options"
-
-class Chef
- class Knife
- class DataBagCreate < Knife
- include DataBagSecretOptions
-
- deps do
- require_relative "../data_bag"
- require_relative "../encrypted_data_bag_item"
- end
-
- banner "knife data bag create BAG [ITEM] (options)"
- category "data bag"
-
- def run
- @data_bag_name, @data_bag_item_name = @name_args
-
- if @data_bag_name.nil?
- show_usage
- ui.fatal("You must specify a data bag name")
- exit 1
- end
-
- begin
- Chef::DataBag.validate_name!(@data_bag_name)
- rescue Chef::Exceptions::InvalidDataBagName => e
- ui.fatal(e.message)
- exit(1)
- end
-
- # Verify if the data bag exists
- begin
- rest.get("data/#{@data_bag_name}")
- ui.info("Data bag #{@data_bag_name} already exists")
- rescue Net::HTTPClientException => e
- raise unless /^404/.match?(e.to_s)
-
- # if it doesn't exists, try to create it
- rest.post("data", { "name" => @data_bag_name })
- ui.info("Created data_bag[#{@data_bag_name}]")
- end
-
- # if an item is specified, create it, as well
- if @data_bag_item_name
- create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output|
- item = Chef::DataBagItem.from_hash(
- if encryption_secret_provided?
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
- else
- output
- end
- )
- item.data_bag(@data_bag_name)
- rest.post("data/#{@data_bag_name}", item)
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb
deleted file mode 100644
index ab38244e91..0000000000
--- a/lib/chef/knife/data_bag_delete.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class DataBagDelete < Knife
-
- deps do
- require_relative "../data_bag"
- end
-
- banner "knife data bag delete BAG [ITEM] (options)"
- category "data bag"
-
- def run
- if @name_args.length == 2
- delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do
- rest.delete("data/#{@name_args[0]}/#{@name_args[1]}")
- end
- elsif @name_args.length == 1
- delete_object(Chef::DataBag, @name_args[0], "data_bag") do
- rest.delete("data/#{@name_args[0]}")
- end
- else
- show_usage
- ui.fatal("You must specify at least a data bag name")
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
deleted file mode 100644
index 1935f2149e..0000000000
--- a/lib/chef/knife/data_bag_edit.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "data_bag_secret_options"
-
-class Chef
- class Knife
- class DataBagEdit < Knife
- include DataBagSecretOptions
-
- deps do
- require_relative "../data_bag_item"
- require_relative "../encrypted_data_bag_item"
- end
-
- banner "knife data bag edit BAG ITEM (options)"
- category "data bag"
-
- def load_item(bag, item_name)
- item = Chef::DataBagItem.load(bag, item_name)
- if encrypted?(item.raw_data)
- if encryption_secret_provided_ignore_encrypt_flag?
- [Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true]
- else
- ui.fatal("You cannot edit an encrypted data bag without providing the secret.")
- exit(1)
- end
- else
- [item.raw_data, false]
- end
- end
-
- def run
- if @name_args.length != 2
- stdout.puts "You must supply the data bag and an item to edit"
- stdout.puts opt_parser
- exit 1
- end
-
- item, was_encrypted = load_item(@name_args[0], @name_args[1])
- edited_item = edit_hash(item)
-
- if was_encrypted || encryption_secret_provided?
- ui.info("Encrypting data bag using provided secret.")
- item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret)
- else
- ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.")
- item_to_save = edited_item
- end
-
- rest.put("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
- stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
- ui.output(edited_item) if config[:print_after]
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
deleted file mode 100644
index 5f48b0a934..0000000000
--- a/lib/chef/knife/data_bag_from_file.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "data_bag_secret_options"
-
-class Chef
- class Knife
- class DataBagFromFile < Knife
- include DataBagSecretOptions
-
- deps do
- require_relative "../util/path_helper"
- require_relative "../data_bag"
- require_relative "../data_bag_item"
- require_relative "core/object_loader"
- require_relative "../encrypted_data_bag_item"
- end
-
- banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
- category "data bag"
-
- option :all,
- short: "-a",
- long: "--all",
- description: "Upload all data bags or all items for specified data bags."
-
- def loader
- @loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui)
- end
-
- def run
- if config[:all] == true
- load_all_data_bags(@name_args)
- else
- if @name_args.size < 2
- ui.msg(opt_parser)
- exit(1)
- end
- @data_bag = @name_args.shift
- load_data_bag_items(@data_bag, @name_args)
- end
- end
-
- private
-
- def data_bags_path
- @data_bag_path ||= "data_bags"
- end
-
- def find_all_data_bags
- loader.find_all_object_dirs("./#{data_bags_path}")
- end
-
- def find_all_data_bag_items(data_bag)
- loader.find_all_objects("./#{data_bags_path}/#{data_bag}")
- end
-
- def load_all_data_bags(args)
- data_bags = args.empty? ? find_all_data_bags : [args.shift]
- data_bags.each do |data_bag|
- load_data_bag_items(data_bag)
- end
- end
-
- def load_data_bag_items(data_bag, items = nil)
- items ||= find_all_data_bag_items(data_bag)
- item_paths = normalize_item_paths(items)
- item_paths.each do |item_path|
- item = loader.load_from((data_bags_path).to_s, data_bag, item_path)
- item = if encryption_secret_provided?
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret)
- else
- item
- end
- dbag = Chef::DataBagItem.new
- dbag.data_bag(data_bag)
- dbag.raw_data = item
- dbag.save
- ui.info("Updated data_bag_item[#{dbag.data_bag}::#{dbag.id}]")
- end
- end
-
- def normalize_item_paths(args)
- paths = []
- args.each do |path|
- if File.directory?(path)
- paths.concat(Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path), "*.json")))
- else
- paths << path
- end
- end
- paths
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_list.rb b/lib/chef/knife/data_bag_list.rb
deleted file mode 100644
index 801bf588b4..0000000000
--- a/lib/chef/knife/data_bag_list.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class DataBagList < Knife
-
- deps do
- require_relative "../data_bag"
- end
-
- banner "knife data bag list (options)"
- category "data bag"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- output(format_list_for_display(Chef::DataBag.list))
- end
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb
deleted file mode 100644
index 8f9f96502f..0000000000
--- a/lib/chef/knife/data_bag_secret_options.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright (c) 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 "mixlib/cli" unless defined?(Mixlib::CLI)
-require_relative "../config"
-require_relative "../encrypted_data_bag_item/check_encrypted"
-
-class Chef
- class Knife
- module DataBagSecretOptions
- include Mixlib::CLI
- include Chef::EncryptedDataBagItem::CheckEncrypted
-
- # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do
- # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file`
- # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate
- # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret`
- # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they
- # are provided.
-
- def self.included(base)
- base.option :cl_secret,
- long: "--secret SECRET",
- description: "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'."
-
- base.option :cl_secret_file,
- long: "--secret-file SECRET_FILE",
- description: "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'."
-
- base.option :encrypt,
- long: "--encrypt",
- description: "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it.",
- boolean: true,
- default: false
- end
-
- def encryption_secret_provided?
- base_encryption_secret_provided?
- end
-
- def encryption_secret_provided_ignore_encrypt_flag?
- base_encryption_secret_provided?(false)
- end
-
- def read_secret
- # Moving the non 'compile-time' requires into here to speed up knife command loading
- # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
- require_relative "../encrypted_data_bag_item"
-
- if config[:cl_secret]
- config[:cl_secret]
- elsif config[:cl_secret_file]
- Chef::EncryptedDataBagItem.load_secret(config[:cl_secret_file])
- elsif secret = config[:secret]
- secret
- else
- secret_file = config[:secret_file]
- Chef::EncryptedDataBagItem.load_secret(secret_file)
- end
- end
-
- def validate_secrets
- if config[:cl_secret] && config[:cl_secret_file]
- ui.fatal("Please specify only one of --secret, --secret-file")
- exit(1)
- end
-
- if config[:secret] && config[:secret_file]
- ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
- exit(1)
- end
- end
-
- private
-
- ##
- # Determine if the user has specified an appropriate secret for encrypting data bag items.
- # @return boolean
- def base_encryption_secret_provided?(need_encrypt_flag = true)
- validate_secrets
-
- return true if config[:cl_secret] || config[:cl_secret_file]
-
- if need_encrypt_flag
- if config[:encrypt]
- unless config[:secret] || config[:secret_file]
- ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
- exit(1)
- end
- return true
- end
- return false
- elsif config[:secret] || config[:secret_file]
- # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
- return true
- end
- false
- end
-
- def knife_config
- Chef.deprecated(:knife_bootstrap_apis, "The `knife_config` bootstrap helper has been deprecated, use the properly merged `config` helper instead")
- Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
deleted file mode 100644
index cb7b56c333..0000000000
--- a/lib/chef/knife/data_bag_show.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "data_bag_secret_options"
-
-class Chef
- class Knife
- class DataBagShow < Knife
- include DataBagSecretOptions
-
- deps do
- require_relative "../data_bag"
- require_relative "../encrypted_data_bag_item"
- end
-
- banner "knife data bag show BAG [ITEM] (options)"
- category "data bag"
-
- def run
- display = case @name_args.length
- when 2 # Bag and Item names provided
- secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
- raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
- encrypted = encrypted?(raw_data)
-
- if encrypted && secret
- # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
- ui.info("Encrypted data bag detected, decrypting with provided secret.")
- raw = Chef::EncryptedDataBagItem.load(@name_args[0],
- @name_args[1],
- secret)
- format_for_display(raw.to_h)
- elsif encrypted && !secret
- ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
- format_for_display(raw_data)
- else
- ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") if secret
- format_for_display(raw_data)
- end
-
- when 1 # Only Bag name provided
- format_list_for_display(Chef::DataBag.load(@name_args[0]))
- else
- stdout.puts opt_parser
- exit(1)
- end
- output(display)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/delete.rb b/lib/chef/knife/delete.rb
deleted file mode 100644
index 3e5c545017..0000000000
--- a/lib/chef/knife/delete.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Delete < Chef::ChefFS::Knife
- banner "knife delete [PATTERN1 ... PATTERNn]"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- end
-
- option :recurse,
- short: "-r",
- long: "--[no-]recurse",
- boolean: true,
- default: false,
- description: "Delete directories recursively."
-
- option :both,
- long: "--both",
- boolean: true,
- default: false,
- description: "Delete both the local and remote copies."
-
- option :local,
- long: "--local",
- boolean: true,
- default: false,
- description: "Delete the local copy (leave the remote copy)."
-
- def run
- if name_args.length == 0
- show_usage
- ui.fatal("You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"")
- exit 1
- end
-
- # Get the matches (recursively)
- error = false
- if config[:local]
- pattern_args.each do |pattern|
- Chef::ChefFS::FileSystem.list(local_fs, pattern).each do |result|
- if delete_result(result)
- error = true
- end
- end
- end
- elsif config[:both]
- pattern_args.each do |pattern|
- Chef::ChefFS::FileSystem.list_pairs(pattern, chef_fs, local_fs).each do |chef_result, local_result|
- if delete_result(chef_result, local_result)
- error = true
- end
- end
- end
- else # Remote only
- pattern_args.each do |pattern|
- Chef::ChefFS::FileSystem.list(chef_fs, pattern).each do |result|
- if delete_result(result)
- error = true
- end
- end
- end
- end
-
- if error
- exit 1
- end
- end
-
- def format_path_with_root(entry)
- root = entry.root == chef_fs ? " (remote)" : " (local)"
- "#{format_path(entry)}#{root}"
- end
-
- def delete_result(*results)
- deleted_any = false
- found_any = false
- error = false
- results.each do |result|
-
- result.delete(config[:recurse])
- deleted_any = true
- found_any = true
- rescue Chef::ChefFS::FileSystem::NotFoundError
- # This is not an error unless *all* of them were not found
- rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e
- ui.error "#{format_path_with_root(e.entry)} must be deleted recursively! Pass -r to knife delete."
- found_any = true
- error = true
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path_with_root(e.entry)} #{e.reason}."
- found_any = true
- error = true
-
- end
- if deleted_any
- output("Deleted #{format_path(results[0])}")
- elsif !found_any
- ui.error "#{format_path(results[0])}: No such file or directory"
- error = true
- end
- error
- end
- end
- end
-end
diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb
deleted file mode 100644
index f620b53bfa..0000000000
--- a/lib/chef/knife/deps.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Deps < Chef::ChefFS::Knife
- banner "knife deps PATTERN1 [PATTERNn]"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- require_relative "../run_list"
- end
-
- option :recurse,
- long: "--[no-]recurse",
- boolean: true,
- description: "List dependencies recursively (default: true). Only works with --tree."
-
- option :tree,
- long: "--tree",
- boolean: true,
- description: "Show dependencies in a visual tree. May show duplicates."
-
- option :remote,
- long: "--remote",
- boolean: true,
- description: "List dependencies on the server instead of the local filesystem."
-
- attr_accessor :exit_code
-
- def run
- if config[:recurse] == false && !config[:tree]
- ui.error "--no-recurse requires --tree"
- exit(1)
- end
- config[:recurse] = true if config[:recurse].nil?
-
- @root = config[:remote] ? chef_fs : local_fs
- dependencies = {}
- pattern_args.each do |pattern|
- Chef::ChefFS::FileSystem.list(@root, pattern).each do |entry|
- if config[:tree]
- print_dependencies_tree(entry, dependencies)
- else
- print_flattened_dependencies(entry, dependencies)
- end
- end
- end
- exit exit_code if exit_code
- end
-
- def print_flattened_dependencies(entry, dependencies)
- unless dependencies[entry.path]
- dependencies[entry.path] = get_dependencies(entry)
- dependencies[entry.path].each do |child|
- child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
- print_flattened_dependencies(child_entry, dependencies)
- end
- output format_path(entry)
- end
- end
-
- def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0)
- dependencies[entry.path] = get_dependencies(entry) unless dependencies[entry.path]
- output "#{" " * depth}#{format_path(entry)}"
- if !printed[entry.path] && (config[:recurse] || depth == 0)
- printed[entry.path] = true
- dependencies[entry.path].each do |child|
- child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
- print_dependencies_tree(child_entry, dependencies, printed, depth + 1)
- end
- end
- end
-
- def get_dependencies(entry)
- if entry.parent && entry.parent.path == "/cookbooks"
- entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
-
- elsif entry.parent && entry.parent.path == "/nodes"
- node = Chef::JSONCompat.parse(entry.read)
- result = []
- if node["chef_environment"] && node["chef_environment"] != "_default"
- result << "/environments/#{node["chef_environment"]}.json"
- end
- if node["run_list"]
- result += dependencies_from_runlist(node["run_list"])
- end
- result
-
- elsif entry.parent && entry.parent.path == "/roles"
- role = Chef::JSONCompat.parse(entry.read)
- result = []
- if role["run_list"]
- dependencies_from_runlist(role["run_list"]).each do |dependency|
- result << dependency unless result.include?(dependency)
- end
- end
- if role["env_run_lists"]
- role["env_run_lists"].each_pair do |env, run_list|
- dependencies_from_runlist(run_list).each do |dependency|
- result << dependency unless result.include?(dependency)
- end
- end
- end
- result
-
- elsif !entry.exists?
- raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)
-
- else
- []
- end
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- self.exit_code = 2
- []
- end
-
- def dependencies_from_runlist(run_list)
- chef_run_list = Chef::RunList.new
- chef_run_list.reset!(run_list)
- chef_run_list.map do |run_list_item|
- case run_list_item.type
- when :role
- "/roles/#{run_list_item.name}.json"
- when :recipe
- if run_list_item.name =~ /(.+)::[^:]*/
- "/cookbooks/#{$1}"
- else
- "/cookbooks/#{run_list_item.name}"
- end
- else
- raise "Unknown run list item type #{run_list_item.type}"
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/diff.rb b/lib/chef/knife/diff.rb
deleted file mode 100644
index 3e9336aacc..0000000000
--- a/lib/chef/knife/diff.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Diff < Chef::ChefFS::Knife
- banner "knife diff PATTERNS"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/command_line"
- end
-
- option :recurse,
- long: "--[no-]recurse",
- boolean: true,
- default: true,
- description: "List directories recursively."
-
- option :name_only,
- long: "--name-only",
- boolean: true,
- description: "Only show names of modified files."
-
- option :name_status,
- long: "--name-status",
- boolean: true,
- description: "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
-
- option :diff_filter,
- long: "--diff-filter=[(A|D|M|T)...[*]]",
- description: "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
-
- option :cookbook_version,
- long: "--cookbook-version VERSION",
- description: "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)."
-
- def run
- if config[:name_only]
- output_mode = :name_only
- end
- if config[:name_status]
- output_mode = :name_status
- end
- patterns = pattern_args_from(name_args.length > 0 ? name_args : [ "" ])
-
- # Get the matches (recursively)
- error = false
- begin
- patterns.each do |pattern|
- found_error = Chef::ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) }, config[:diff_filter], ui ) do |diff|
- stdout.print diff
- end
- error = true if found_error
- end
- rescue Chef::ChefFS::FileSystem::OperationFailedError => e
- ui.error "Failed on #{format_path(e.entry)} in #{e.operation}: #{e.message}"
- error = true
- end
-
- if error
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/download.rb b/lib/chef/knife/download.rb
deleted file mode 100644
index ab8c92a1c0..0000000000
--- a/lib/chef/knife/download.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Download < Chef::ChefFS::Knife
- banner "knife download PATTERNS"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/command_line"
- end
-
- option :recurse,
- long: "--[no-]recurse",
- boolean: true,
- default: true,
- description: "List directories recursively."
-
- option :purge,
- long: "--[no-]purge",
- boolean: true,
- default: false,
- description: "Delete matching local files and directories that do not exist remotely."
-
- option :force,
- long: "--[no-]force",
- boolean: true,
- default: false,
- description: "Force download of files even if they match (quicker and harmless, but doesn't print out what it changed)."
-
- option :dry_run,
- long: "--dry-run",
- short: "-n",
- boolean: true,
- default: false,
- description: "Don't take action, only print what would happen."
-
- option :diff,
- long: "--[no-]diff",
- boolean: true,
- default: true,
- description: "Turn off to avoid downloading existing files; only new (and possibly deleted) files with --no-diff."
-
- option :cookbook_version,
- long: "--cookbook-version VERSION",
- description: "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)."
-
- def run
- if name_args.length == 0
- show_usage
- ui.fatal("You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"")
- exit 1
- end
-
- error = false
- pattern_args.each do |pattern|
- if Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
- error = true
- end
- end
- if error
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/edit.rb b/lib/chef/knife/edit.rb
deleted file mode 100644
index caca201566..0000000000
--- a/lib/chef/knife/edit.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Edit < Chef::ChefFS::Knife
- banner "knife edit [PATTERN1 ... PATTERNn]"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- require_relative "../chef_fs/file_system/exceptions"
- end
-
- option :local,
- long: "--local",
- boolean: true,
- description: "Show local files instead of remote."
-
- def run
- # Get the matches (recursively)
- error = false
- pattern_args.each do |pattern|
- Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
- if result.dir?
- ui.error "#{format_path(result)}: is a directory" if pattern.exact_path
- error = true
- else
- begin
- new_value = edit_text(result.read, File.extname(result.name))
- if new_value
- result.write(new_value)
- output "Updated #{format_path(result)}"
- else
- output "#{format_path(result)} unchanged"
- end
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path(e.entry)}: #{e.reason}."
- error = true
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- error = true
- end
- end
- end
- end
- if error
- exit 1
- end
- end
-
- def edit_text(text, extension)
- unless config[:disable_editing]
- Tempfile.open([ "knife-edit-", extension ]) do |file|
- # Write the text to a temporary file
- file.write(text)
- file.close
-
- # Let the user edit the temporary file
- unless system("#{config[:editor]} #{file.path}")
- raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_setup/ for details."
- end
-
- result_text = IO.read(file.path)
-
- return result_text if result_text != text
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_compare.rb b/lib/chef/knife/environment_compare.rb
deleted file mode 100644
index 22abee59c8..0000000000
--- a/lib/chef/knife/environment_compare.rb
+++ /dev/null
@@ -1,128 +0,0 @@
-#
-# Author:: Sander Botman (<sbotman@schubergphilis.com>)
-# Copyright:: Copyright 2013-2016, Sander Botman.
-# 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentCompare < Knife
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment compare [ENVIRONMENT..] (options)"
-
- option :all,
- short: "-a",
- long: "--all",
- description: "Show all cookbooks.",
- boolean: true
-
- option :mismatch,
- short: "-m",
- long: "--mismatch",
- description: "Only show mismatching versions.",
- boolean: true
-
- def run
- # Get the commandline environments or all if none are provided.
- environments = environment_list
-
- # Get a list of all cookbooks that have constraints and their environment.
- constraints = constraint_list(environments)
-
- # Get the total list of cookbooks that have constraints
- cookbooks = cookbook_list(constraints)
-
- # If we cannot find any cookbooks, we can stop here.
- if cookbooks.nil? || cookbooks.empty?
- ui.error "Cannot find any environment cookbook constraints"
- exit 1
- end
-
- # Get all cookbooks so we can compare them all
- cookbooks = rest.get("/cookbooks?num_versions=1") if config[:all]
-
- # display matrix view of in the requested format.
- if config[:format] == "summary"
- matrix = matrix_output(cookbooks, constraints)
- ui.output(matrix)
- else
- ui.output(constraints)
- end
- end
-
- private
-
- def environment_list
- environments = []
- unless @name_args.nil? || @name_args.empty?
- @name_args.each { |name| environments << name }
- else
- environments = Chef::Environment.list
- end
- end
-
- def constraint_list(environments)
- constraints = {}
- environments.each do |env, url| # rubocop:disable Style/HashEachMethods
- # Because you cannot modify the default environment I filter it out here.
- unless env == "_default"
- envdata = Chef::Environment.load(env)
- ver = envdata.cookbook_versions
- constraints[env] = ver
- end
- end
- constraints
- end
-
- def cookbook_list(constraints)
- result = {}
- constraints.each_value { |cb| result.merge!(cb) }
- result
- end
-
- def matrix_output(cookbooks, constraints)
- rows = [ "" ]
- environments = []
- constraints.each_key { |e| environments << e.to_s }
- columns = environments.count + 1
- environments.each { |env| rows << ui.color(env, :bold) }
- cookbooks.each_key do |c|
- total = []
- environments.each { |n| total << constraints[n][c] }
- if total.uniq.count == 1
- next if config[:mismatch]
-
- color = :white
- else
- color = :yellow
- end
- rows << ui.color(c, :bold)
- environments.each do |e|
- tag = constraints[e][c] || "latest"
- rows << ui.color(tag, color)
- end
- end
- ui.list(rows, :uneven_columns_across, columns)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/environment_create.rb b/lib/chef/knife/environment_create.rb
deleted file mode 100644
index a724f72d4f..0000000000
--- a/lib/chef/knife/environment_create.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentCreate < Knife
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment create ENVIRONMENT (options)"
-
- option :description,
- short: "-d DESCRIPTION",
- long: "--description DESCRIPTION",
- description: "The environment description."
-
- def run
- env_name = @name_args[0]
-
- if env_name.nil?
- show_usage
- ui.fatal("You must specify an environment name")
- exit 1
- end
-
- env = Chef::Environment.new
- env.name(env_name)
- env.description(config[:description]) if config[:description]
- create_object(env, object_class: Chef::Environment)
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_delete.rb b/lib/chef/knife/environment_delete.rb
deleted file mode 100644
index ec1b7cb8d8..0000000000
--- a/lib/chef/knife/environment_delete.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentDelete < Knife
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment delete ENVIRONMENT (options)"
-
- def run
- env_name = @name_args[0]
-
- if env_name.nil?
- show_usage
- ui.fatal("You must specify an environment name")
- exit 1
- end
-
- delete_object(Chef::Environment, env_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_edit.rb b/lib/chef/knife/environment_edit.rb
deleted file mode 100644
index 7c6105a6c0..0000000000
--- a/lib/chef/knife/environment_edit.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentEdit < Knife
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment edit ENVIRONMENT (options)"
-
- def run
- env_name = @name_args[0]
-
- if env_name.nil?
- show_usage
- ui.fatal("You must specify an environment name")
- exit 1
- end
-
- edit_object(Chef::Environment, env_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_from_file.rb b/lib/chef/knife/environment_from_file.rb
deleted file mode 100644
index a5011a3abf..0000000000
--- a/lib/chef/knife/environment_from_file.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentFromFile < Knife
-
- deps do
- require_relative "../environment"
- require_relative "core/object_loader"
- end
-
- banner "knife environment from file FILE [FILE..] (options)"
-
- option :all,
- short: "-a",
- long: "--all",
- description: "Upload all environments."
-
- def loader
- @loader ||= Knife::Core::ObjectLoader.new(Chef::Environment, ui)
- end
-
- def environments_path
- @environments_path ||= "environments"
- end
-
- def find_all_environments
- loader.find_all_objects("./#{environments_path}/")
- end
-
- def load_all_environments
- environments = find_all_environments
- if environments.empty?
- ui.fatal("Unable to find any environment files in '#{environments_path}'")
- exit(1)
- end
- environments.each do |env|
- load_environment(env)
- end
- end
-
- def load_environment(env)
- updated = loader.load_from("environments", env)
- updated.save
- output(format_for_display(updated)) if config[:print_after]
- ui.info("Updated Environment #{updated.name}")
- end
-
- def run
- if config[:all] == true
- load_all_environments
- else
- if @name_args[0].nil?
- show_usage
- ui.fatal("You must specify a file to load")
- exit 1
- end
-
- @name_args.each do |arg|
- load_environment(arg)
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_list.rb b/lib/chef/knife/environment_list.rb
deleted file mode 100644
index 7bcdeb6084..0000000000
--- a/lib/chef/knife/environment_list.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentList < Knife
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment list (options)"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- output(format_list_for_display(Chef::Environment.list))
- end
- end
- end
-end
diff --git a/lib/chef/knife/environment_show.rb b/lib/chef/knife/environment_show.rb
deleted file mode 100644
index e336b2d392..0000000000
--- a/lib/chef/knife/environment_show.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class EnvironmentShow < Knife
-
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../environment"
- end
-
- banner "knife environment show ENVIRONMENT (options)"
-
- def run
- env_name = @name_args[0]
-
- if env_name.nil?
- show_usage
- ui.fatal("You must specify an environment name")
- exit 1
- end
-
- env = Chef::Environment.load(env_name)
- output(format_for_display(env))
- end
- end
- end
-end
diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb
deleted file mode 100644
index d3ce2cee24..0000000000
--- a/lib/chef/knife/exec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#--
-# Author:: Daniel DeLeo (<dan@chef.io)
-# Copyright:: Copyright (c) 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 "../knife"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef::Knife::Exec < Chef::Knife
-
- banner "knife exec [SCRIPT] (options)"
-
- deps do
- require_relative "../util/path_helper"
- end
-
- option :exec,
- short: "-E CODE",
- long: "--exec CODE",
- description: "A string of #{ChefUtils::Dist::Infra::PRODUCT} code to execute."
-
- option :script_path,
- short: "-p PATH:PATH",
- long: "--script-path PATH:PATH",
- description: "A colon-separated path to look for scripts in.",
- proc: lambda { |o| o.split(":") }
-
- deps do
- require_relative "../shell/ext"
- end
-
- def run
- config[:script_path] = Array(config[:script_path] || Chef::Config[:script_path])
-
- # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
- config[:script_path] << File.join(Chef::Knife.chef_config_dir, "scripts") if Chef::Knife.chef_config_dir
- Chef::Util::PathHelper.home(".chef", "scripts") { |p| config[:script_path] << p }
-
- scripts = Array(name_args)
- context = Object.new
- Shell::Extensions.extend_context_object(context)
- if config[:exec]
- context.instance_eval(config[:exec], "-E Argument", 0)
- elsif !scripts.empty?
- scripts.each do |script|
- file = find_script(script)
- context.instance_eval(IO.read(file), file, 0)
- end
- else
- puts "An interactive shell is opened"
- puts
- puts "Type your script and do:"
- puts
- puts "1. To run the script, use 'Ctrl D'"
- puts "2. To exit, use 'Ctrl/Shift C'"
- puts
- puts "Type here a script..."
- script = STDIN.read
- context.instance_eval(script, "STDIN", 0)
- end
- end
-
- def find_script(x)
- # Try to find a script. First try expanding the path given.
- script = File.expand_path(x)
- return script if File.exist?(script)
-
- # Failing that, try searching the script path. If we can't find
- # anything, fail gracefully.
- Chef::Log.trace("Searching script_path: #{config[:script_path].inspect}")
-
- config[:script_path].each do |path|
- path = File.expand_path(path)
- test = File.join(path, x)
- Chef::Log.trace("Testing: #{test}")
- if File.exist?(test)
- script = test
- Chef::Log.trace("Found: #{test}")
- return script
- end
- end
- ui.error("\"#{x}\" not found in current directory or script_path, giving up.")
- exit(1)
- end
-
-end
diff --git a/lib/chef/knife/key_create.rb b/lib/chef/knife/key_create.rb
deleted file mode 100644
index 6129cab683..0000000000
--- a/lib/chef/knife/key_create.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../key"
-require_relative "../json_compat"
-require_relative "../exceptions"
-
-class Chef
- class Knife
- # Service class for UserKeyCreate and ClientKeyCreate,
- # Implements common functionality of knife [user | org client] key create.
- #
- # @author Tyler Cloke
- #
- # @attr_accessor [Hash] cli input, see UserKeyCreate and ClientKeyCreate for what could populate it
- class KeyCreate
-
- attr_accessor :config
-
- def initialize(actor, actor_field_name, ui, config)
- @actor = actor
- @actor_field_name = actor_field_name
- @ui = ui
- @config = config
- end
-
- def public_key_or_key_name_error_msg
- <<~EOS
- You must pass either --public-key or --key-name, or both.
- If you only pass --public-key, a key name will be generated from the fingerprint of your key.
- If you only pass --key-name, a key pair will be generated by the server.
- EOS
- end
-
- def edit_data(key)
- @ui.edit_data(key)
- end
-
- def edit_hash(key)
- @ui.edit_hash(key)
- end
-
- def display_info(input)
- @ui.info(input)
- end
-
- def display_private_key(private_key)
- @ui.msg(private_key)
- end
-
- def output_private_key_to_file(private_key)
- File.open(@config[:file], "w") do |f|
- f.print(private_key)
- end
- end
-
- def create_key_from_hash(output)
- Chef::Key.from_hash(output).create
- end
-
- def run
- key = Chef::Key.new(@actor, @actor_field_name)
- if !@config[:public_key] && !@config[:key_name]
- raise Chef::Exceptions::KeyCommandInputError, public_key_or_key_name_error_msg
- elsif !@config[:public_key]
- key.create_key(true)
- end
-
- if @config[:public_key]
- key.public_key(File.read(File.expand_path(@config[:public_key])))
- end
-
- if @config[:key_name]
- key.name(@config[:key_name])
- end
-
- if @config[:expiration_date]
- key.expiration_date(@config[:expiration_date])
- else
- key.expiration_date("infinity")
- end
-
- output = edit_hash(key)
- key = create_key_from_hash(output)
-
- display_info("Created key: #{key.name}")
- if key.private_key
- if @config[:file]
- output_private_key_to_file(key.private_key)
- else
- display_private_key(key.private_key)
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/key_delete.rb b/lib/chef/knife/key_delete.rb
deleted file mode 100644
index 10f1235924..0000000000
--- a/lib/chef/knife/key_delete.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../key"
-
-class Chef
- class Knife
- # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys.
- # Implements common functionality of knife [user | org client] key delete.
- #
- # @author Tyler Cloke
- #
- # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it
- class KeyDelete
- def initialize(name, actor, actor_field_name, ui)
- @name = name
- @actor = actor
- @actor_field_name = actor_field_name
- @ui = ui
- end
-
- def confirm!
- @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}")
- end
-
- def print_destroyed
- @ui.info("Deleted key named #{@name} for the #{@actor_field_name} named #{@actor}")
- end
-
- def run
- key = Chef::Key.new(@actor, @actor_field_name)
- key.name(@name)
- confirm!
- key.destroy
- print_destroyed
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/key_edit.rb b/lib/chef/knife/key_edit.rb
deleted file mode 100644
index 3f8918f1a9..0000000000
--- a/lib/chef/knife/key_edit.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../key"
-require_relative "../json_compat"
-require_relative "../exceptions"
-
-class Chef
- class Knife
- # Service class for UserKeyEdit and ClientKeyEdit,
- # Implements common functionality of knife [user | org client] key edit.
- #
- # @author Tyler Cloke
- #
- # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it
- class KeyEdit
-
- attr_accessor :config
-
- def initialize(original_name, actor, actor_field_name, ui, config)
- @original_name = original_name
- @actor = actor
- @actor_field_name = actor_field_name
- @ui = ui
- @config = config
- end
-
- def public_key_and_create_key_error_msg
- <<~EOS
- You passed both --public-key and --create-key. Only pass one, or the other, or neither.
- Do not pass either if you do not want to change the public_key field of your key.
- Pass --public-key if you want to update the public_key field of your key from a specific public key.
- Pass --create-key if you want the server to generate a new key and use that to update the public_key field of your key.
- EOS
- end
-
- def edit_data(key)
- @ui.edit_data(key)
- end
-
- def edit_hash(key)
- @ui.edit_hash(key)
- end
-
- def display_info(input)
- @ui.info(input)
- end
-
- def display_private_key(private_key)
- @ui.msg(private_key)
- end
-
- def output_private_key_to_file(private_key)
- File.open(@config[:file], "w") do |f|
- f.print(private_key)
- end
- end
-
- def update_key_from_hash(output)
- Chef::Key.from_hash(output).update(@original_name)
- end
-
- def run
- key = Chef::Key.new(@actor, @actor_field_name)
- if @config[:public_key] && @config[:create_key]
- raise Chef::Exceptions::KeyCommandInputError, public_key_and_create_key_error_msg
- end
-
- if @config[:create_key]
- key.create_key(true)
- end
-
- if @config[:public_key]
- key.public_key(File.read(File.expand_path(@config[:public_key])))
- end
-
- if @config[:key_name]
- key.name(@config[:key_name])
- else
- key.name(@original_name)
- end
-
- if @config[:expiration_date]
- key.expiration_date(@config[:expiration_date])
- end
-
- output = edit_hash(key)
- key = update_key_from_hash(output)
-
- to_display = "Updated key: #{key.name}"
- to_display << " (formally #{@original_name})" if key.name != @original_name
- display_info(to_display)
- if key.private_key
- if @config[:file]
- output_private_key_to_file(key.private_key)
- else
- display_private_key(key.private_key)
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb
deleted file mode 100644
index 076b39d251..0000000000
--- a/lib/chef/knife/key_list.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../key"
-require_relative "../json_compat"
-require_relative "../exceptions"
-
-class Chef
- class Knife
- # Service class for UserKeyList and ClientKeyList, used to list keys.
- # Implements common functionality of knife [user | org client] key list.
- #
- # @author Tyler Cloke
- #
- # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it
- class KeyList
-
- attr_accessor :config
-
- def initialize(actor, list_method, ui, config)
- @actor = actor
- @list_method = list_method
- @ui = ui
- @config = config
- end
-
- def expired_and_non_expired_msg
- <<~EOS
- You cannot pass both --only-expired and --only-non-expired.
- Please pass one or none.
- EOS
- end
-
- def display_info(string)
- @ui.output(string)
- end
-
- def colorize(string)
- @ui.color(string, :cyan)
- end
-
- def run
- if @config[:only_expired] && @config[:only_non_expired]
- raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg
- end
-
- # call proper list function
- keys = Chef::Key.send(@list_method, @actor)
- if @config[:with_details]
- max_length = 0
- keys.each do |key|
- key["name"] = key["name"] + ":"
- max_length = key["name"].length if key["name"].length > max_length
- end
- keys.each do |key|
- next if !key["expired"] && @config[:only_expired]
- next if key["expired"] && @config[:only_non_expired]
-
- display = "#{colorize(key["name"].ljust(max_length))} #{key["uri"]}"
- display = "#{display} (expired)" if key["expired"]
- display_info(display)
- end
- else
- keys.each do |key|
- next if !key["expired"] && @config[:only_expired]
- next if key["expired"] && @config[:only_non_expired]
-
- display_info(key["name"])
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/key_show.rb b/lib/chef/knife/key_show.rb
deleted file mode 100644
index 8b3d980004..0000000000
--- a/lib/chef/knife/key_show.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../key"
-require_relative "../json_compat"
-require_relative "../exceptions"
-
-class Chef
- class Knife
- # Service class for UserKeyShow and ClientKeyShow, used to show keys.
- # Implements common functionality of knife [user | org client] key show.
- #
- # @author Tyler Cloke
- #
- # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it
- class KeyShow
-
- attr_accessor :config
-
- def initialize(name, actor, load_method, ui)
- @name = name
- @actor = actor
- @load_method = load_method
- @ui = ui
- end
-
- def display_output(key)
- @ui.output(@ui.format_for_display(key))
- end
-
- def run
- key = Chef::Key.send(@load_method, @actor, @name)
- key.public_key(key.public_key.strip)
- display_output(key)
- end
- end
- end
-end
diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb
deleted file mode 100644
index 1cc398e01a..0000000000
--- a/lib/chef/knife/list.rb
+++ /dev/null
@@ -1,177 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class List < Chef::ChefFS::Knife
- banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn] (options)"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- require "tty-screen"
- end
-
- option :recursive,
- short: "-R",
- boolean: true,
- description: "List directories recursively."
-
- option :bare_directories,
- short: "-d",
- boolean: true,
- description: "When directories match the pattern, do not show the directories' children."
-
- option :local,
- long: "--local",
- boolean: true,
- description: "List local directory instead of remote."
-
- option :flat,
- short: "-f",
- long: "--flat",
- boolean: true,
- description: "Show a list of filenames rather than the prettified ls-like output normally produced."
-
- option :one_column,
- short: "-1",
- boolean: true,
- description: "Show only one column of results."
-
- option :trailing_slashes,
- short: "-p",
- boolean: true,
- description: "Show trailing slashes after directories."
-
- attr_accessor :exit_code
-
- def run
- patterns = name_args.length == 0 ? [""] : name_args
-
- # Get the top-level matches
- all_results = parallelize(pattern_args_from(patterns)) do |pattern|
- pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a
-
- if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path
- ui.error "#{format_path(pattern_results.first)}: No such file or directory"
- self.exit_code = 1
- end
- pattern_results
- end.flatten(1).to_a
-
- # Process directories
- if !config[:bare_directories]
- dir_results = parallelize(all_results.select(&:dir?)) do |result|
- add_dir_result(result)
- end.flatten(1)
-
- else
- dir_results = []
- end
-
- # Process all other results
- results = all_results.select { |result| result.exists? && (!result.dir? || config[:bare_directories]) }.to_a
-
- # Flatten out directory results if necessary
- if config[:flat]
- dir_results.each do |result, children| # rubocop:disable Style/HashEachMethods
- results += children
- end
- dir_results = []
- end
-
- # Sort by path for happy output
- results = results.sort_by(&:path)
- dir_results = dir_results.sort_by { |result| result[0].path }
-
- # Print!
- if results.length == 0 && dir_results.length == 1
- results = dir_results[0][1]
- dir_results = []
- end
-
- print_result_paths results
- printed_something = results.length > 0
- dir_results.each do |result, children|
- if printed_something
- output ""
- else
- printed_something = true
- end
- output "#{format_path(result)}:"
- print_results(children.map { |result| maybe_add_slash(result.display_name, result.dir?) }.sort, "")
- end
-
- exit exit_code if exit_code
- end
-
- def add_dir_result(result)
- begin
- children = result.children.sort_by(&:name)
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- return []
- end
-
- result = [ [ result, children ] ]
- if config[:recursive]
- child_dirs = children.select(&:dir?)
- result += parallelize(child_dirs) { |child| add_dir_result(child) }.flatten(1).to_a
- end
- result
- end
-
- def print_result_paths(results, indent = "")
- print_results(results.map { |result| maybe_add_slash(format_path(result), result.dir?) }, indent)
- end
-
- def print_results(results, indent)
- return if results.length == 0
-
- print_space = results.map(&:length).max + 2
- if config[:one_column] || !stdout.isatty
- columns = 0
- else
- columns = TTY::Screen.columns
- end
- current_line = ""
- results.each do |result|
- if current_line.length > 0 && current_line.length + print_space > columns
- output current_line.rstrip
- current_line = ""
- end
- if current_line.length == 0
- current_line << indent
- end
- current_line << result
- current_line << (" " * (print_space - result.length))
- end
- output current_line.rstrip if current_line.length > 0
- end
-
- def maybe_add_slash(path, is_dir)
- if config[:trailing_slashes] && is_dir
- "#{path}/"
- else
- path
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/node_bulk_delete.rb b/lib/chef/knife/node_bulk_delete.rb
deleted file mode 100644
index 874509b730..0000000000
--- a/lib/chef/knife/node_bulk_delete.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeBulkDelete < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node bulk delete REGEX (options)"
-
- def run
- if name_args.length < 1
- ui.fatal("You must supply a regular expression to match the results against")
- exit 42
- end
-
- nodes_to_delete = {}
- matcher = /#{name_args[0]}/
-
- all_nodes.each do |name, node|
- next unless name&.match?(matcher)
-
- nodes_to_delete[name] = node
- end
-
- if nodes_to_delete.empty?
- ui.msg "No nodes match the expression /#{name_args[0]}/"
- exit 0
- end
-
- ui.msg("The following nodes will be deleted:")
- ui.msg("")
- ui.msg(ui.list(nodes_to_delete.keys.sort, :columns_down))
- ui.msg("")
- ui.confirm("Are you sure you want to delete these nodes")
-
- nodes_to_delete.sort.each do |name, node|
- node.destroy
- ui.msg("Deleted node #{name}")
- end
- end
-
- def all_nodes
- node_uris_by_name = Chef::Node.list
-
- node_uris_by_name.keys.inject({}) do |nodes_by_name, name|
- nodes_by_name[name] = Chef::Node.new.tap { |n| n.name(name) }
- nodes_by_name
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_create.rb b/lib/chef/knife/node_create.rb
deleted file mode 100644
index c0db667b25..0000000000
--- a/lib/chef/knife/node_create.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeCreate < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node create NODE (options)"
-
- def run
- @node_name = @name_args[0]
-
- if @node_name.nil?
- show_usage
- ui.fatal("You must specify a node name")
- exit 1
- end
-
- node = Chef::Node.new
- node.name(@node_name)
- create_object(node, object_class: Chef::Node)
- end
- end
- end
-end
diff --git a/lib/chef/knife/node_delete.rb b/lib/chef/knife/node_delete.rb
deleted file mode 100644
index 7c0c6f0a21..0000000000
--- a/lib/chef/knife/node_delete.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeDelete < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node delete [NODE [NODE]] (options)"
-
- def run
- if @name_args.length == 0
- show_usage
- ui.fatal("You must specify at least one node name")
- exit 1
- end
-
- @name_args.each do |node_name|
- delete_object(Chef::Node, node_name)
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_edit.rb b/lib/chef/knife/node_edit.rb
deleted file mode 100644
index a2585391ea..0000000000
--- a/lib/chef/knife/node_edit.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
-
- class NodeEdit < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- require_relative "core/node_editor"
- end
-
- banner "knife node edit NODE (options)"
-
- option :all_attributes,
- short: "-a",
- long: "--all",
- boolean: true,
- description: "Display all attributes when editing."
-
- def run
- if node_name.nil?
- show_usage
- ui.fatal("You must specify a node name")
- exit 1
- end
-
- updated_node = node_editor.edit_node
- if updated_values = node_editor.updated?
- ui.info "Saving updated #{updated_values.join(", ")} on node #{node.name}"
- updated_node.save
- else
- ui.info "Node not updated, skipping node save"
- end
- end
-
- def node_name
- @node_name ||= @name_args[0]
- end
-
- def node_editor
- @node_editor ||= Knife::NodeEditor.new(node, ui, config)
- end
-
- def node
- @node ||= Chef::Node.load(node_name)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_environment_set.rb b/lib/chef/knife/node_environment_set.rb
deleted file mode 100644
index 644b6138b6..0000000000
--- a/lib/chef/knife/node_environment_set.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Author:: Jimmy McCrory (<jimmy.mccrory@gmail.com>)
-# Copyright:: Copyright 2014-2016, Jimmy McCrory
-# 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 "../knife"
-
-class Chef
- class Knife
- class NodeEnvironmentSet < Knife
-
- deps do
- require_relative "../node"
- end
-
- banner "knife node environment set NODE ENVIRONMENT"
-
- def run
- if @name_args.size < 2
- ui.fatal "You must specify a node name and an environment."
- show_usage
- exit 1
- else
- @node_name = @name_args[0]
- @environment = @name_args[1]
- end
-
- node = Chef::Node.load(@node_name)
-
- node.chef_environment = @environment
-
- node.save
-
- config[:environment] = @environment
- output(format_for_display(node))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_from_file.rb b/lib/chef/knife/node_from_file.rb
deleted file mode 100644
index 86d602ae7c..0000000000
--- a/lib/chef/knife/node_from_file.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeFromFile < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- require_relative "core/object_loader"
- end
-
- banner "knife node from file FILE (options)"
-
- def loader
- @loader ||= Knife::Core::ObjectLoader.new(Chef::Node, ui)
- end
-
- def run
- @name_args.each do |arg|
- updated = loader.load_from("nodes", arg)
-
- updated.save
-
- output(format_for_display(updated)) if config[:print_after]
-
- ui.info("Updated Node #{updated.name}")
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_list.rb b/lib/chef/knife/node_list.rb
deleted file mode 100644
index a8b57aedc5..0000000000
--- a/lib/chef/knife/node_list.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeList < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node list (options)"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- env = Chef::Config[:environment]
- output(format_list_for_display( env ? Chef::Node.list_by_environment(env) : Chef::Node.list ))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_policy_set.rb b/lib/chef/knife/node_policy_set.rb
deleted file mode 100644
index d34ebd9478..0000000000
--- a/lib/chef/knife/node_policy_set.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-#
-# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodePolicySet < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node policy set NODE POLICY_GROUP POLICY_NAME (options)"
-
- def run
- validate_node!
- validate_options!
- node = Chef::Node.load(@name_args[0])
- set_policy(node)
- if node.save
- ui.info "Successfully set the policy on node #{node.name}"
- else
- ui.info "Error in updating node #{node.name}"
- end
- end
-
- private
-
- # Set policy name and group to node
- def set_policy(node)
- policy_group, policy_name = @name_args[1..]
- node.policy_name = policy_name
- node.policy_group = policy_group
- end
-
- # Validate policy name and policy group
- def validate_options!
- if incomplete_policyfile_options?
- ui.error("Policy group and name must be specified together")
- exit 1
- end
- true
- end
-
- # Validate node pass in CLI
- def validate_node!
- if @name_args[0].nil?
- ui.error("You must specify a node name")
- show_usage
- exit 1
- end
- end
-
- # True if one of policy_name or policy_group was given, but not both
- def incomplete_policyfile_options?
- policy_group, policy_name = @name_args[1..]
- (policy_group.nil? || policy_name.nil? || @name_args[1..-1].size > 2)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_run_list_add.rb b/lib/chef/knife/node_run_list_add.rb
deleted file mode 100644
index 40476371eb..0000000000
--- a/lib/chef/knife/node_run_list_add.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeRunListAdd < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node run_list add [NODE] [ENTRY [ENTRY]] (options)"
-
- option :after,
- short: "-a ITEM",
- long: "--after ITEM",
- description: "Place the ENTRY in the run list after ITEM."
-
- option :before,
- short: "-b ITEM",
- long: "--before ITEM",
- description: "Place the ENTRY in the run list before ITEM."
-
- def run
- node = Chef::Node.load(@name_args[0])
- if @name_args.size > 2
- # Check for nested lists and create a single plain one
- entries = @name_args[1..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[1].split(",").map(&:strip)
- end
-
- if config[:after] && config[:before]
- ui.fatal("You cannot specify both --before and --after!")
- exit 1
- end
-
- if config[:after]
- add_to_run_list_after(node, entries, config[:after])
- elsif config[:before]
- add_to_run_list_before(node, entries, config[:before])
- else
- add_to_run_list_after(node, entries)
- end
-
- node.save
-
- config[:run_list] = true
-
- output(format_for_display(node))
- end
-
- private
-
- def add_to_run_list_after(node, entries, after = nil)
- if after
- nlist = []
- node.run_list.each do |entry|
- nlist << entry
- if entry == after
- entries.each { |e| nlist << e }
- end
- end
- node.run_list.reset!(nlist)
- else
- entries.each { |e| node.run_list << e }
- end
- end
-
- def add_to_run_list_before(node, entries, before)
- nlist = []
- node.run_list.each do |entry|
- if entry == before
- entries.each { |e| nlist << e }
- end
- nlist << entry
- end
- node.run_list.reset!(nlist)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb
deleted file mode 100644
index 484e575475..0000000000
--- a/lib/chef/knife/node_run_list_remove.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class NodeRunListRemove < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node run_list remove [NODE] [ENTRY [ENTRY]] (options)"
-
- def run
- node = Chef::Node.load(@name_args[0])
-
- if @name_args.size > 2
- # Check for nested lists and create a single plain one
- entries = @name_args[1..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[1].split(",").map(&:strip)
- end
-
- # iterate over the list of things to remove,
- # warning if one of them was not found
- entries.each do |e|
- if node.run_list.find { |rli| e == rli.to_s }
- node.run_list.remove(e)
- else
- ui.warn "#{e} is not in the run list"
- unless /^(recipe|role)\[/.match?(e)
- ui.warn "(did you forget recipe[] or role[] around it?)"
- end
- end
- end
-
- node.save
-
- config[:run_list] = true
-
- output(format_for_display(node))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_run_list_set.rb b/lib/chef/knife/node_run_list_set.rb
deleted file mode 100644
index f356b39d95..0000000000
--- a/lib/chef/knife/node_run_list_set.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "../knife"
-
-class Chef
- class Knife
- class NodeRunListSet < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node run_list set NODE ENTRIES (options)"
-
- def run
- if @name_args.size < 2
- ui.fatal "You must supply both a node name and a run list."
- show_usage
- exit 1
- elsif @name_args.size > 2
- # Check for nested lists and create a single plain one
- entries = @name_args[1..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[1].split(",").map(&:strip)
- end
- node = Chef::Node.load(@name_args[0])
-
- set_run_list(node, entries)
-
- node.save
-
- config[:run_list] = true
-
- output(format_for_display(node))
- end
-
- # Clears out any existing run_list_items and sets them to the
- # specified entries
- def set_run_list(node, entries)
- node.run_list.run_list_items.clear
- entries.each { |e| node.run_list << e }
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/node_show.rb b/lib/chef/knife/node_show.rb
deleted file mode 100644
index 8ef06d8938..0000000000
--- a/lib/chef/knife/node_show.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "core/node_presenter"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class NodeShow < Knife
-
- include Knife::Core::NodeFormattingOptions
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife node show NODE (options)"
-
- option :run_list,
- short: "-r",
- long: "--run-list",
- description: "Show only the run list."
-
- option :environment,
- short: "-E",
- long: "--environment",
- description: "Show only the #{ChefUtils::Dist::Infra::PRODUCT} environment."
-
- def run
- ui.use_presenter Knife::Core::NodePresenter
- @node_name = @name_args[0]
-
- if @node_name.nil?
- show_usage
- ui.fatal("You must specify a node name")
- exit 1
- end
-
- node = Chef::Node.load(@node_name)
- output(format_for_display(node))
- end
- end
- end
-end
diff --git a/lib/chef/knife/raw.rb b/lib/chef/knife/raw.rb
deleted file mode 100644
index 5adb36ea70..0000000000
--- a/lib/chef/knife/raw.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-#
-# 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 "../knife"
-
-class Chef
- class Knife
- class Raw < Chef::Knife
- banner "knife raw REQUEST_PATH (options)"
-
- deps do
- require_relative "../json_compat"
- require_relative "../config"
- require_relative "../http"
- require_relative "../http/authenticator"
- require_relative "../http/cookie_manager"
- require_relative "../http/decompressor"
- require_relative "../http/json_output"
- end
-
- option :method,
- long: "--method METHOD",
- short: "-m METHOD",
- default: "GET",
- description: "Request method (GET, POST, PUT or DELETE). Default: GET."
-
- option :pretty,
- long: "--[no-]pretty",
- boolean: true,
- default: true,
- description: "Pretty-print JSON output. Default: true."
-
- option :input,
- long: "--input FILE",
- short: "-i FILE",
- description: "Name of file to use for PUT or POST."
-
- option :proxy_auth,
- long: "--proxy-auth",
- boolean: true,
- default: false,
- description: "Use webui proxy authentication. Client key must be the webui key."
-
- # We need a custom HTTP client class here because we don't want to even
- # try to decode the body, in case we get back corrupted JSON or whatnot.
- class RawInputServerAPI < Chef::HTTP
- def initialize(options = {})
- # If making a change here, also update Chef::ServerAPI.
- options[:client_name] ||= Chef::Config[:node_name]
- options[:raw_key] ||= Chef::Config[:client_key_contents]
- options[:signing_key_filename] ||= Chef::Config[:client_key] unless options[:raw_key]
- options[:ssh_agent_signing] ||= Chef::Config[:ssh_agent_signing]
- super(Chef::Config[:chef_server_url], options)
- end
- use Chef::HTTP::JSONOutput
- use Chef::HTTP::CookieManager
- use Chef::HTTP::Decompressor
- use Chef::HTTP::Authenticator
- use Chef::HTTP::RemoteRequestID
- end
-
- def run
- if name_args.length == 0
- show_usage
- ui.fatal("You must provide the path you want to hit on the server")
- exit(1)
- elsif name_args.length > 1
- show_usage
- ui.fatal("You must specify only a single path")
- exit(1)
- end
-
- path = name_args[0]
- data = false
- if config[:input]
- data = IO.read(config[:input])
- end
- begin
- method = config[:method].to_sym
-
- headers = { "Content-Type" => "application/json" }
-
- if config[:proxy_auth]
- headers["x-ops-request-source"] = "web"
- end
-
- if config[:pretty]
- chef_rest = RawInputServerAPI.new
- result = chef_rest.request(method, name_args[0], headers, data)
- unless result.is_a?(String)
- result = Chef::JSONCompat.to_json_pretty(result)
- end
- else
- chef_rest = RawInputServerAPI.new(raw_output: true)
- result = chef_rest.request(method, name_args[0], headers, data)
- end
- output result
- rescue Timeout::Error => e
- ui.error "Server timeout"
- exit 1
- rescue Net::HTTPClientException => e
- ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\""
- ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ""
- exit 1
- end
- end
-
- end # class Raw
- end
-end
diff --git a/lib/chef/knife/role_bulk_delete.rb b/lib/chef/knife/role_bulk_delete.rb
deleted file mode 100644
index f57ac79619..0000000000
--- a/lib/chef/knife/role_bulk_delete.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleBulkDelete < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role bulk delete REGEX (options)"
-
- def run
- if @name_args.length < 1
- ui.error("You must supply a regular expression to match the results against")
- exit 1
- end
-
- all_roles = Chef::Role.list(true)
-
- matcher = /#{@name_args[0]}/
- roles_to_delete = {}
- all_roles.each do |name, role|
- next unless name&.match?(matcher)
-
- roles_to_delete[role.name] = role
- end
-
- if roles_to_delete.empty?
- ui.info "No roles match the expression /#{@name_args[0]}/"
- exit 0
- end
-
- ui.msg("The following roles will be deleted:")
- ui.msg("")
- ui.msg(ui.list(roles_to_delete.keys.sort, :columns_down))
- ui.msg("")
- ui.confirm("Are you sure you want to delete these roles")
-
- roles_to_delete.sort.each do |name, role|
- role.destroy
- ui.msg("Deleted role #{name}")
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/role_create.rb b/lib/chef/knife/role_create.rb
deleted file mode 100644
index 295445554d..0000000000
--- a/lib/chef/knife/role_create.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleCreate < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role create ROLE (options)"
-
- option :description,
- short: "-d DESC",
- long: "--description DESC",
- description: "The role description."
-
- def run
- @role_name = @name_args[0]
-
- if @role_name.nil?
- show_usage
- ui.fatal("You must specify a role name")
- exit 1
- end
-
- role = Chef::Role.new
- role.name(@role_name)
- role.description(config[:description]) if config[:description]
- create_object(role, object_class: Chef::Role)
- end
- end
- end
-end
diff --git a/lib/chef/knife/role_delete.rb b/lib/chef/knife/role_delete.rb
deleted file mode 100644
index c46e265c5e..0000000000
--- a/lib/chef/knife/role_delete.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleDelete < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role delete ROLE (options)"
-
- def run
- @role_name = @name_args[0]
-
- if @role_name.nil?
- show_usage
- ui.fatal("You must specify a role name")
- exit 1
- end
-
- delete_object(Chef::Role, @role_name)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_edit.rb b/lib/chef/knife/role_edit.rb
deleted file mode 100644
index 1925336646..0000000000
--- a/lib/chef/knife/role_edit.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleEdit < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role edit ROLE (options)"
-
- def run
- @role_name = @name_args[0]
-
- if @role_name.nil?
- show_usage
- ui.fatal("You must specify a role name")
- exit 1
- end
-
- ui.edit_object(Chef::Role, @role_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/role_env_run_list_add.rb b/lib/chef/knife/role_env_run_list_add.rb
deleted file mode 100644
index b5753b46fc..0000000000
--- a/lib/chef/knife/role_env_run_list_add.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleEnvRunListAdd < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY [ENTRY]] (options)"
-
- option :after,
- short: "-a ITEM",
- long: "--after ITEM",
- description: "Place the ENTRY in the run list after ITEM."
-
- def add_to_env_run_list(role, environment, entries, after = nil)
- if after
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- role.run_list_for(environment).each do |entry|
- nlist << entry
- if entry == after
- entries.each { |e| nlist << e }
- end
- end
- role.env_run_lists_add(environment => nlist)
- else
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- role.run_list_for(environment).each do |entry|
- nlist << entry
- end
- entries.each { |e| nlist << e }
- role.env_run_lists_add(environment => nlist)
- end
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = @name_args[1]
-
- if @name_args.size > 2
- # Check for nested lists and create a single plain one
- entries = @name_args[2..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[2].split(",").map(&:strip)
- end
-
- add_to_env_run_list(role, environment, entries, config[:after])
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_env_run_list_clear.rb b/lib/chef/knife/role_env_run_list_clear.rb
deleted file mode 100644
index dda523e809..0000000000
--- a/lib/chef/knife/role_env_run_list_clear.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "../knife"
-
-class Chef
- class Knife
- class RoleEnvRunListClear < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role env_run_list clear [ROLE] [ENVIRONMENT] (options)"
- def clear_env_run_list(role, environment)
- nlist = []
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- if @name_args.size > 2
- ui.fatal "You must not supply an environment run list."
- show_usage
- exit 1
- end
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = @name_args[1]
-
- clear_env_run_list(role, environment)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_env_run_list_remove.rb b/lib/chef/knife/role_env_run_list_remove.rb
deleted file mode 100644
index 57363610ce..0000000000
--- a/lib/chef/knife/role_env_run_list_remove.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleEnvRunListRemove < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
-
- def remove_from_env_run_list(role, environment, item_to_remove)
- nlist = []
- role.run_list_for(environment).each do |entry|
- nlist << entry unless entry == item_to_remove
- # unless entry == @name_args[2]
- # nlist << entry
- # end
- end
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = @name_args[1]
- item_to_remove = @name_args[2]
-
- remove_from_env_run_list(role, environment, item_to_remove)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_env_run_list_replace.rb b/lib/chef/knife/role_env_run_list_replace.rb
deleted file mode 100644
index e76680661e..0000000000
--- a/lib/chef/knife/role_env_run_list_replace.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleEnvRunListReplace < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] (options)"
-
- def replace_in_env_run_list(role, environment, old_entry, new_entry)
- nlist = []
- role.run_list_for(environment).each do |entry|
- if entry == old_entry
- nlist << new_entry
- else
- nlist << entry
- end
- end
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = @name_args[1]
- old_entry = @name_args[2]
- new_entry = @name_args[3]
-
- replace_in_env_run_list(role, environment, old_entry, new_entry)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_env_run_list_set.rb b/lib/chef/knife/role_env_run_list_set.rb
deleted file mode 100644
index 0f1ce62a5d..0000000000
--- a/lib/chef/knife/role_env_run_list_set.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "../knife"
-
-class Chef
- class Knife
- class RoleEnvRunListSet < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES] (options)"
-
- # Clears out any existing env_run_list_items and sets them to the
- # specified entries
- def set_env_run_list(role, environment, entries)
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- entries.each { |e| nlist << e }
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = @name_args[1]
- if @name_args.size < 2
- ui.fatal "You must supply both a role name and an environment run list."
- show_usage
- exit 1
- elsif @name_args.size > 2
- # Check for nested lists and create a single plain one
- entries = @name_args[2..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[2].split(",").map(&:strip)
- end
-
- set_env_run_list(role, environment, entries )
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_from_file.rb b/lib/chef/knife/role_from_file.rb
deleted file mode 100644
index 16e38eeb63..0000000000
--- a/lib/chef/knife/role_from_file.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleFromFile < Knife
-
- deps do
- require_relative "../role"
- require_relative "core/object_loader"
- require_relative "../json_compat"
- end
-
- banner "knife role from file FILE [FILE..] (options)"
-
- def loader
- @loader ||= Knife::Core::ObjectLoader.new(Chef::Role, ui)
- end
-
- def run
- @name_args.each do |arg|
- updated = loader.load_from("roles", arg)
-
- updated.save
-
- output(format_for_display(updated)) if config[:print_after]
-
- ui.info("Updated Role #{updated.name}")
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_list.rb b/lib/chef/knife/role_list.rb
deleted file mode 100644
index d6aad053c1..0000000000
--- a/lib/chef/knife/role_list.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleList < Knife
-
- deps do
- require_relative "../node"
- require_relative "../json_compat"
- end
-
- banner "knife role list (options)"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- output(format_list_for_display(Chef::Role.list))
- end
- end
- end
-end
diff --git a/lib/chef/knife/role_run_list_add.rb b/lib/chef/knife/role_run_list_add.rb
deleted file mode 100644
index 76633ff5f6..0000000000
--- a/lib/chef/knife/role_run_list_add.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleRunListAdd < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role run_list add [ROLE] [ENTRY [ENTRY]] (options)"
-
- option :after,
- short: "-a ITEM",
- long: "--after ITEM",
- description: "Place the ENTRY in the run list after ITEM."
-
- def add_to_env_run_list(role, environment, entries, after = nil)
- if after
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- role.run_list_for(environment).each do |entry|
- nlist << entry
- if entry == after
- entries.each { |e| nlist << e }
- end
- end
- role.env_run_lists_add(environment => nlist)
- else
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- role.run_list_for(environment).each do |entry|
- nlist << entry
- end
- entries.each { |e| nlist << e }
- role.env_run_lists_add(environment => nlist)
- end
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = "_default"
-
- if @name_args.size > 1
- # Check for nested lists and create a single plain one
- entries = @name_args[1..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[1].split(",").map(&:strip)
- end
-
- add_to_env_run_list(role, environment, entries, config[:after])
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_run_list_clear.rb b/lib/chef/knife/role_run_list_clear.rb
deleted file mode 100644
index b7106233f0..0000000000
--- a/lib/chef/knife/role_run_list_clear.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "../knife"
-
-class Chef
- class Knife
- class RoleRunListClear < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role run_list clear [ROLE] (options)"
- def clear_env_run_list(role, environment)
- nlist = []
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- if @name_args.size > 2
- ui.fatal "You must not supply an environment run list."
- show_usage
- exit 1
- end
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = "_default"
-
- clear_env_run_list(role, environment)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_run_list_remove.rb b/lib/chef/knife/role_run_list_remove.rb
deleted file mode 100644
index 884f3bc28d..0000000000
--- a/lib/chef/knife/role_run_list_remove.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleRunListRemove < Knife
-
- deps do
- require_relative "../role"
- end
-
- banner "knife role run_list remove [ROLE] [ENTRY] (options)"
-
- def remove_from_env_run_list(role, environment, item_to_remove)
- nlist = []
- role.run_list_for(environment).each do |entry|
- nlist << entry unless entry == item_to_remove
- # unless entry == @name_args[2]
- # nlist << entry
- # end
- end
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = "_default"
- item_to_remove = @name_args[1]
-
- remove_from_env_run_list(role, environment, item_to_remove)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_run_list_replace.rb b/lib/chef/knife/role_run_list_replace.rb
deleted file mode 100644
index 16f789fbef..0000000000
--- a/lib/chef/knife/role_run_list_replace.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleRunListReplace < Knife
-
- deps do
- require_relative "../role"
- require_relative "../json_compat"
- end
-
- banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] (options)"
-
- def replace_in_env_run_list(role, environment, old_entry, new_entry)
- nlist = []
- role.run_list_for(environment).each do |entry|
- if entry == old_entry
- nlist << new_entry
- else
- nlist << entry
- end
- end
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = "_default"
- old_entry = @name_args[1]
- new_entry = @name_args[2]
-
- replace_in_env_run_list(role, environment, old_entry, new_entry)
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_run_list_set.rb b/lib/chef/knife/role_run_list_set.rb
deleted file mode 100644
index ad1a5e2923..0000000000
--- a/lib/chef/knife/role_run_list_set.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Author:: William Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "../knife"
-
-class Chef
- class Knife
- class RoleRunListSet < Knife
-
- deps do
- require_relative "../role"
- end
-
- banner "knife role run_list set [ROLE] [ENTRIES] (options)"
-
- # Clears out any existing env_run_list_items and sets them to the
- # specified entries
- def set_env_run_list(role, environment, entries)
- nlist = []
- unless role.env_run_lists.key?(environment)
- role.env_run_lists_add(environment => nlist)
- end
- entries.each { |e| nlist << e }
- role.env_run_lists_add(environment => nlist)
- end
-
- def run
- role = Chef::Role.load(@name_args[0])
- role.name(@name_args[0])
- environment = "_default"
- if @name_args.size < 1
- ui.fatal "You must supply both a role name and an environment run list."
- show_usage
- exit 1
- elsif @name_args.size > 1
- # Check for nested lists and create a single plain one
- entries = @name_args[1..].map do |entry|
- entry.split(",").map(&:strip)
- end.flatten
- else
- # Convert to array and remove the extra spaces
- entries = @name_args[1].split(",").map(&:strip)
- end
-
- set_env_run_list(role, environment, entries )
- role.save
- config[:env_run_list] = true
- output(format_for_display(role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/role_show.rb b/lib/chef/knife/role_show.rb
deleted file mode 100644
index ee90352e50..0000000000
--- a/lib/chef/knife/role_show.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class RoleShow < Knife
-
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../role"
- end
-
- banner "knife role show ROLE (options)"
-
- def run
- @role_name = @name_args[0]
-
- if @role_name.nil?
- show_usage
- ui.fatal("You must specify a role name.")
- exit 1
- end
-
- role = Chef::Role.load(@role_name)
- output(format_for_display(config[:environment] ? role.environment(config[:environment]) : role))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
deleted file mode 100644
index 2feb8e6729..0000000000
--- a/lib/chef/knife/search.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require_relative "core/node_presenter"
-
-class Chef
- class Knife
- class Search < Knife
-
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../node"
- require_relative "../environment"
- require_relative "../api_client"
- require_relative "../search/query"
- end
-
- include Knife::Core::NodeFormattingOptions
-
- banner "knife search INDEX QUERY (options)"
-
- option :start,
- short: "-b ROW",
- long: "--start ROW",
- description: "The row to start returning results at.",
- default: 0,
- proc: lambda { |i| i.to_i }
-
- option :rows,
- short: "-R INT",
- long: "--rows INT",
- description: "The number of rows to return.",
- default: nil,
- proc: lambda { |i| i.to_i }
-
- option :run_list,
- short: "-r",
- long: "--run-list",
- description: "Show only the run list."
-
- option :id_only,
- short: "-i",
- long: "--id-only",
- description: "Show only the ID of matching objects."
-
- option :query,
- short: "-q QUERY",
- long: "--query QUERY",
- description: "The search query; useful to protect queries starting with -."
-
- option :filter_result,
- short: "-f FILTER",
- long: "--filter-result FILTER",
- description: "Only return specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\"."
-
- def run
- read_cli_args
-
- if @type == "node"
- ui.use_presenter Knife::Core::NodePresenter
- end
-
- q = Chef::Search::Query.new
-
- result_items = []
- result_count = 0
-
- search_args = {}
- search_args[:fuzz] = true
- search_args[:start] = config[:start] if config[:start]
- search_args[:rows] = config[:rows] if config[:rows]
- if config[:filter_result]
- search_args[:filter_result] = create_result_filter(config[:filter_result])
- elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?)
- search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute])
- elsif config[:id_only]
- search_args[:filter_result] = create_result_filter_from_attributes([])
- end
-
- begin
- q.search(@type, @query, search_args) do |item|
- formatted_item = {}
- if config[:id_only]
- formatted_item = format_for_display({ "id" => item["__display_name"] })
- elsif item.is_a?(Hash)
- # doing a little magic here to set the correct name
- formatted_item[item["__display_name"]] = item.reject { |k| k == "__display_name" }
- else
- formatted_item = format_for_display(item)
- end
- result_items << formatted_item
- result_count += 1
- end
- rescue Net::HTTPClientException => e
- msg = Chef::JSONCompat.from_json(e.response.body)["error"].first
- ui.error("knife search failed: #{msg}")
- exit 99
- end
-
- if ui.interchange?
- output({ results: result_count, rows: result_items })
- else
- ui.log "#{result_count} items found"
- ui.log("\n")
- result_items.each do |item|
- output(item)
- unless config[:id_only]
- ui.msg("\n")
- end
- end
- end
-
- # return a "failure" code to the shell so that knife search can be used in pipes similar to grep
- exit 1 if result_count == 0
- end
-
- def read_cli_args
- if config[:query]
- if @name_args[1]
- ui.error "Please specify query as an argument or an option via -q, not both"
- ui.msg opt_parser
- exit 1
- end
- @type = name_args[0]
- @query = config[:query]
- else
- case name_args.size
- when 0
- ui.error "No query specified"
- ui.msg opt_parser
- exit 1
- when 1
- @type = "node"
- @query = name_args[0]
- when 2
- @type = name_args[0]
- @query = name_args[1]
- end
- end
- end
-
- # This method turns a set of key value pairs in a string into the appropriate data structure that the
- # chef-server search api is expecting.
- # expected input is in the form of:
- # -f "return_var1=path.to.attribute, return_var2=shorter.path"
- #
- # a more concrete example might be:
- # -f "env=chef_environment, ruby_platform=languages.ruby.platform"
- #
- # The end result is a hash where the key is a symbol in the hash (the return variable)
- # and the path is an array with the path elements as strings (in order)
- # See lib/chef/search/query.rb for more examples of this.
- def create_result_filter(filter_string)
- final_filter = {}
- filter_string.delete!(" ")
- filters = filter_string.split(",")
- filters.each do |f|
- return_id, attr_path = f.split("=")
- final_filter[return_id.to_sym] = attr_path.split(".")
- end
- final_filter
- end
-
- def create_result_filter_from_attributes(filter_array)
- final_filter = {}
- filter_array.each do |f|
- final_filter[f] = f.split(".")
- end
- # adding magic filter so we can actually pull the name as before
- final_filter["__display_name"] = [ "name" ]
- final_filter
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/serve.rb b/lib/chef/knife/serve.rb
deleted file mode 100644
index d79e05aa85..0000000000
--- a/lib/chef/knife/serve.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# 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 "../knife"
-require_relative "../local_mode"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class Serve < Knife
-
- banner "knife serve (options)"
-
- option :repo_mode,
- long: "--repo-mode MODE",
- description: "Specifies the local repository layout. Values: static (only environments/roles/data_bags/cookbooks), everything (includes nodes/clients/users), hosted_everything (includes acls/groups/etc. for Enterprise/Hosted Chef). Default: everything/hosted_everything."
-
- option :chef_repo_path,
- long: "--chef-repo-path PATH",
- description: "Overrides the location of #{ChefUtils::Dist::Infra::PRODUCT} repo. Default is specified by chef_repo_path in the config."
-
- option :chef_zero_host,
- long: "--chef-zero-host IP",
- description: "Overrides the host upon which #{ChefUtils::Dist::Zero::PRODUCT} listens. Default is 127.0.0.1."
-
- def configure_chef
- super
- Chef::Config.local_mode = true
- Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
-
- # --chef-repo-path forcibly overrides all other paths
- if config[:chef_repo_path]
- Chef::Config.chef_repo_path = config[:chef_repo_path]
- %w{acl client cookbook container data_bag environment group node role user}.each do |variable_name|
- Chef::Config.delete("#{variable_name}_path".to_sym)
- end
- end
- end
-
- def run
- server = Chef::LocalMode.chef_zero_server
- begin
- output "Serving files from:\n#{Chef::LocalMode.chef_fs.fs_description}"
- server.stop
- server.start(stdout) # to print header
- ensure
- server.stop
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/show.rb b/lib/chef/knife/show.rb
deleted file mode 100644
index 0e5ab9d0fe..0000000000
--- a/lib/chef/knife/show.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Show < Chef::ChefFS::Knife
- banner "knife show [PATTERN1 ... PATTERNn] (options)"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- require_relative "../chef_fs/file_system/exceptions"
- end
-
- option :local,
- long: "--local",
- boolean: true,
- description: "Show local files instead of remote."
-
- def run
- # Get the matches (recursively)
- error = false
- entry_values = parallelize(pattern_args) do |pattern|
- parallelize(Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern)) do |entry|
- if entry.dir?
- ui.error "#{format_path(entry)}: is a directory" if pattern.exact_path
- error = true
- nil
- else
- begin
- [entry, entry.read]
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path(e.entry)}: #{e.reason}."
- error = true
- nil
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- error = true
- nil
- end
- end
- end
- end.flatten(1)
- entry_values.each do |entry, value|
- if entry
- output "#{format_path(entry)}:"
- output(format_for_display(value))
- end
- end
- if error
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
deleted file mode 100644
index a586bf37c2..0000000000
--- a/lib/chef/knife/ssh.rb
+++ /dev/null
@@ -1,643 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class Ssh < Knife
-
- deps do
- require_relative "../mixin/shell_out"
- require "net/ssh" unless defined?(Net::SSH)
- require "net/ssh/multi"
- require "readline"
- require_relative "../exceptions"
- require_relative "../search/query"
- require_relative "../util/path_helper"
-
- include Chef::Mixin::ShellOut
- end
-
- attr_writer :password
-
- banner "knife ssh QUERY COMMAND (options)"
-
- option :concurrency,
- short: "-C NUM",
- long: "--concurrency NUM",
- description: "The number of concurrent connections.",
- default: nil,
- proc: lambda { |o| o.to_i }
-
- option :ssh_attribute,
- short: "-a ATTR",
- long: "--attribute ATTR",
- description: "The attribute to use for opening the connection - default depends on the context."
-
- option :manual,
- short: "-m",
- long: "--manual-list",
- boolean: true,
- description: "QUERY is a space separated list of servers.",
- default: false
-
- option :prefix_attribute,
- long: "--prefix-attribute ATTR",
- description: "The attribute to use for prefixing the output - default depends on the context."
-
- option :ssh_user,
- short: "-x USERNAME",
- long: "--ssh-user USERNAME",
- description: "The ssh username."
-
- option :ssh_password,
- short: "-P [PASSWORD]",
- long: "--ssh-password [PASSWORD]",
- description: "The ssh password - will prompt if flag is specified but no password is given.",
- # default to a value that can not be a password (boolean)
- # so we can effectively test if this parameter was specified
- # without a value
- default: false
-
- option :ssh_port,
- short: "-p PORT",
- long: "--ssh-port PORT",
- description: "The ssh port.",
- proc: Proc.new { |key| key.strip }
-
- option :ssh_timeout,
- short: "-t SECONDS",
- long: "--ssh-timeout SECONDS",
- description: "The ssh connection timeout.",
- proc: Proc.new { |key| key.strip.to_i },
- default: 120
-
- option :ssh_gateway,
- short: "-G GATEWAY",
- long: "--ssh-gateway GATEWAY",
- description: "The ssh gateway.",
- proc: Proc.new { |key| key.strip }
-
- option :ssh_gateway_identity,
- long: "--ssh-gateway-identity SSH_GATEWAY_IDENTITY",
- description: "The SSH identity file used for gateway authentication."
-
- option :forward_agent,
- short: "-A",
- long: "--forward-agent",
- description: "Enable SSH agent forwarding.",
- boolean: true
-
- option :ssh_identity_file,
- short: "-i IDENTITY_FILE",
- long: "--ssh-identity-file IDENTITY_FILE",
- description: "The SSH identity file used for authentication."
-
- option :host_key_verify,
- long: "--[no-]host-key-verify",
- description: "Verify host key, enabled by default.",
- boolean: true,
- default: true
-
- option :on_error,
- short: "-e",
- long: "--exit-on-error",
- description: "Immediately exit if an error is encountered.",
- boolean: true,
- default: false
-
- option :duplicated_fqdns,
- long: "--duplicated-fqdns",
- description: "Behavior if FQDNs are duplicated, ignored by default.",
- proc: Proc.new { |key| key.strip.to_sym },
- default: :ignore
-
- option :tmux_split,
- long: "--tmux-split",
- description: "Split tmux window.",
- boolean: true,
- default: false
-
- def session
- ssh_error_handler = Proc.new do |server|
- if config[:on_error]
- # Net::SSH::Multi magic to force exception to be re-raised.
- throw :go, :raise
- else
- ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}"
- $!.backtrace.each { |l| Chef::Log.debug(l) }
- end
- end
-
- @session ||= Net::SSH::Multi.start(concurrent_connections: config[:concurrency], on_error: ssh_error_handler)
- end
-
- def configure_gateway
- if config[:ssh_gateway]
- gw_host, gw_user = config[:ssh_gateway].split("@").reverse
- gw_host, gw_port = gw_host.split(":")
- gw_opts = session_options(gw_host, gw_port, gw_user, gateway: true)
- user = gw_opts.delete(:user)
-
- begin
- # Try to connect with a key.
- session.via(gw_host, user, gw_opts)
- rescue Net::SSH::AuthenticationFailed
- prompt = "Enter the password for #{user}@#{gw_host}: "
- gw_opts[:password] = prompt_for_password(prompt)
- # Try again with a password.
- session.via(gw_host, user, gw_opts)
- end
- end
- end
-
- def configure_session
- list = config[:manual] ? @name_args[0].split(" ") : search_nodes
- if list.length == 0
- if @search_count == 0
- ui.fatal("No nodes returned from search")
- else
- ui.fatal("#{@search_count} #{@search_count > 1 ? "nodes" : "node"} found, " +
- "but does not have the required attribute to establish the connection. " +
- "Try setting another attribute to open the connection using --attribute.")
- end
- exit 10
- end
- if %i{warn fatal}.include?(config[:duplicated_fqdns])
- fqdns = list.map { |v| v[0] }
- if fqdns.count != fqdns.uniq.count
- duplicated_fqdns = fqdns.uniq
- ui.send(config[:duplicated_fqdns],
- "SSH #{duplicated_fqdns.count > 1 ? "nodes are" : "node is"} " +
- "duplicated: #{duplicated_fqdns.join(",")}")
- exit 10 if config[:duplicated_fqdns] == :fatal
- end
- end
- session_from_list(list)
- end
-
- def get_prefix_attribute(item)
- # Order of precedence for prefix
- # 1) config value (cli or knife config)
- # 2) nil
- msg = "Using node attribute '%s' as the prefix: %s"
- if item["prefix"]
- Chef::Log.debug(sprintf(msg, config[:prefix_attribute], item["prefix"]))
- item["prefix"]
- else
- nil
- end
- end
-
- def get_ssh_attribute(item)
- # Order of precedence for ssh target
- # 1) config value (cli or knife config)
- # 2) cloud attribute
- # 3) fqdn
- msg = "Using node attribute '%s' as the ssh target: %s"
- if item["target"]
- Chef::Log.debug(sprintf(msg, config[:ssh_attribute], item["target"]))
- item["target"]
- elsif !item.dig("cloud", "public_hostname").to_s.empty?
- Chef::Log.debug(sprintf(msg, "cloud.public_hostname", item["cloud"]["public_hostname"]))
- item["cloud"]["public_hostname"]
- else
- Chef::Log.debug(sprintf(msg, "fqdn", item["fqdn"]))
- item["fqdn"]
- end
- end
-
- def search_nodes
- list = []
- query = Chef::Search::Query.new
- required_attributes = { fqdn: ["fqdn"], cloud: ["cloud"] }
-
- separator = ui.presenter.attribute_field_separator
-
- if config[:prefix_attribute]
- required_attributes[:prefix] = config[:prefix_attribute].split(separator)
- end
-
- if config[:ssh_attribute]
- required_attributes[:target] = config[:ssh_attribute].split(separator)
- end
-
- @search_count = 0
- query.search(:node, @name_args[0], filter_result: required_attributes, fuzz: true) do |item|
- @search_count += 1
- # we should skip the loop to next iteration if the item
- # returned by the search is nil
- next if item.nil?
-
- # next if we couldn't find the specified attribute in the
- # returned node object
- host = get_ssh_attribute(item)
- next if host.nil?
-
- prefix = get_prefix_attribute(item)
- ssh_port = item.dig("cloud", "public_ssh_port")
- srv = [host, ssh_port, prefix]
- list.push(srv)
- end
-
- list
- end
-
- # Net::SSH session options hash for global options. These should be
- # options that will apply to the gateway connection in addition to the
- # main one.
- #
- # @since 12.5.0
- # @param host [String] Hostname for this session.
- # @param port [String] SSH port for this session.
- # @param user [String] Optional username for this session.
- # @param gateway [Boolean] Flag: host or gateway key
- # @return [Hash<Symbol, Object>]
- def session_options(host, port, user = nil, gateway: false)
- ssh_config = Net::SSH.configuration_for(host, true)
- {}.tap do |opts|
- opts[:user] = user || config[:ssh_user] || ssh_config[:user]
- if !gateway && config[:ssh_identity_file]
- opts[:keys] = File.expand_path(config[:ssh_identity_file])
- opts[:keys_only] = true
- elsif gateway && config[:ssh_gateway_identity]
- opts[:keys] = File.expand_path(config[:ssh_gateway_identity])
- opts[:keys_only] = true
- elsif config[:ssh_password]
- opts[:password] = config[:ssh_password]
- end
- # Don't set the keys to nil if we don't have them.
- forward_agent = config[:forward_agent] || ssh_config[:forward_agent]
- opts[:forward_agent] = forward_agent unless forward_agent.nil?
- port ||= ssh_config[:port]
- opts[:port] = port unless port.nil?
- opts[:logger] = Chef::Log.with_child(subsystem: "net/ssh") if Chef::Log.level == :trace
- unless config[:host_key_verify]
- opts[:verify_host_key] = false
- opts[:user_known_hosts_file] = "/dev/null"
- end
- if ssh_config[:keepalive]
- opts[:keepalive] = true
- opts[:keepalive_interval] = ssh_config[:keepalive_interval]
- end
- # maintain support for legacy key types / ciphers / key exchange algorithms.
- # most importantly this adds back support for DSS host keys
- # See https://github.com/net-ssh/net-ssh/pull/709
- opts[:append_all_supported_algorithms] = true
- end
- end
-
- def session_from_list(list)
- list.each do |item|
- host, ssh_port, prefix = item
- prefix = host unless prefix
- Chef::Log.debug("Adding #{host}")
- session_opts = session_options(host, ssh_port, gateway: false)
- # Handle port overrides for the main connection.
- session_opts[:port] = config[:ssh_port] if config[:ssh_port]
- # Handle connection timeout
- session_opts[:timeout] = config[:ssh_timeout] if config[:ssh_timeout]
- # Handle session prefix
- session_opts[:properties] = { prefix: prefix }
- # Create the hostspec.
- hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host
- # Connect a new session on the multi.
- session.use(hostspec, session_opts)
-
- @longest = prefix.length if prefix.length > @longest
- end
-
- session
- end
-
- def fixup_sudo(command)
- command.sub(/^sudo/, "sudo -p 'knife sudo password: '")
- end
-
- def print_data(host, data)
- @buffers ||= {}
- if leftover = @buffers[host]
- @buffers[host] = nil
- print_data(host, leftover + data)
- else
- if newline_index = data.index("\n")
- line = data.slice!(0...newline_index)
- data.slice!(0)
- print_line(host, line)
- print_data(host, data)
- else
- @buffers[host] = data
- end
- end
- end
-
- def print_line(host, data)
- padding = @longest - host.length
- str = ui.color(host, :cyan) + (" " * (padding + 1)) + data
- ui.msg(str)
- end
-
- def ssh_command(command, subsession = nil)
- exit_status = 0
- subsession ||= session
- command = fixup_sudo(command)
- command.force_encoding("binary") if command.respond_to?(:force_encoding)
- begin
- open_session(subsession, command)
- rescue => e
- open_session(subsession, command, true)
- end
- end
-
- def open_session(subsession, command, pty = false)
- stderr = ""
- exit_status = 0
- subsession.open_channel do |chan|
- if config[:on_error] && exit_status != 0
- chan.close
- else
- chan.request_pty if pty
- chan.exec command do |ch, success|
- raise ArgumentError, "Cannot execute #{command}" unless success
-
- ch.on_data do |ichannel, data|
- print_data(ichannel.connection[:prefix], data)
- if /^knife sudo password: /.match?(data)
- print_data(ichannel.connection[:prefix], "\n")
- ichannel.send_data("#{get_password}\n")
- end
- end
-
- ch.on_extended_data do |_, _type, data|
- stderr += data
- end
-
- ch.on_request "exit-status" do |ichannel, data|
- exit_status = [exit_status, data.read_long].max
- end
- end
- end
- end
- session.loop
- exit_status
- end
-
- def get_password
- @password ||= prompt_for_password
- end
-
- def prompt_for_password(prompt = "Enter your password: ")
- ui.ask(prompt, echo: false)
- end
-
- # Present the prompt and read a single line from the console. It also
- # detects ^D and returns "exit" in that case. Adds the input to the
- # history, unless the input is empty. Loops repeatedly until a non-empty
- # line is input.
- def read_line
- loop do
- command = reader.readline("#{ui.color("knife-ssh>", :bold)} ", true)
-
- if command.nil?
- command = "exit"
- puts(command)
- else
- command.strip!
- end
-
- unless command.empty?
- return command
- end
- end
- end
-
- def reader
- Readline
- end
-
- def interactive
- puts "Connected to #{ui.list(session.servers_for.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
- puts
- puts "To run a command on a list of servers, do:"
- puts " on SERVER1 SERVER2 SERVER3; COMMAND"
- puts " Example: on latte foamy; echo foobar"
- puts
- puts "To exit interactive mode, use 'quit!'"
- puts
- loop do
- command = read_line
- case command
- when "quit!"
- puts "Bye!"
- break
- when /^on (.+?); (.+)$/
- raw_list = $1.split(" ")
- server_list = []
- session.servers.each do |session_server|
- server_list << session_server if raw_list.include?(session_server.host)
- end
- command = $2
- ssh_command(command, session.on(*server_list))
- else
- ssh_command(command)
- end
- end
- end
-
- def screen
- tf = Tempfile.new("knife-ssh-screen")
- Chef::Util::PathHelper.home(".screenrc") do |screenrc_path|
- if File.exist? screenrc_path
- tf.puts("source #{screenrc_path}")
- end
- end
- tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'")
- tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'")
- window = 0
- session.servers_for.each do |server|
- tf.print("screen -t \"#{server.host}\" #{window} ssh ")
- tf.print("-i #{config[:ssh_identity_file]} ") if config[:ssh_identity_file]
- server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host)
- window += 1
- end
- tf.close
- exec("screen -c #{tf.path}")
- end
-
- def tmux
- ssh_dest = lambda do |server|
- identity = "-i #{config[:ssh_identity_file]} " if config[:ssh_identity_file]
- prefix = server.user ? "#{server.user}@" : ""
- "'ssh #{identity}#{prefix}#{server.host}'"
- end
-
- new_window_cmds = lambda do
- if session.servers_for.size > 1
- [""] + session.servers_for[1..].map do |server|
- if config[:tmux_split]
- "split-window #{ssh_dest.call(server)}; tmux select-layout tiled"
- else
- "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
- end
- end
- else
- []
- end.join(" \\; ")
- end
-
- tmux_name = "'knife ssh #{@name_args[0].tr(":.", "=-")}'"
- begin
- server = session.servers_for.first
- cmd = ["tmux new-session -d -s #{tmux_name}",
- "-n '#{server.host}'", ssh_dest.call(server),
- new_window_cmds.call].join(" ")
- shell_out!(cmd)
- exec("tmux attach-session -t #{tmux_name}")
- rescue Chef::Exceptions::Exec
- end
- end
-
- def macterm
- begin
- require "appscript" unless defined?(Appscript)
- rescue LoadError
- STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
- raise
- end
-
- Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate
- Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", using: :command_down)
- term = Appscript.app("Terminal")
- window = term.windows.first.get
-
- (session.servers_for.size - 1).times do |i|
- window.activate
- Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", using: :command_down)
- end
-
- session.servers_for.each_with_index do |server, tab_number|
- cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}"
- Appscript.app("Terminal").do_script(cmd, in: window.tabs[tab_number + 1].get)
- end
- end
-
- def cssh
- cssh_cmd = nil
- %w{csshX cssh}.each do |cmd|
-
- # Unix and Mac only
- cssh_cmd = shell_out!("which #{cmd}").stdout.strip
- break
- rescue Mixlib::ShellOut::ShellCommandFailed
-
- end
- raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
-
- # pass in the consolidated identity file option to cssh(X)
- if config[:ssh_identity_file]
- cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:ssh_identity_file])}'"
- end
-
- session.servers_for.each do |server|
- cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
- end
- Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}")
- exec(cssh_cmd)
- end
-
- def get_stripped_unfrozen_value(value)
- return nil unless value
-
- value.strip
- end
-
- def configure_user
- config[:ssh_user] = get_stripped_unfrozen_value(config[:ssh_user] ||
- Chef::Config[:knife][:ssh_user])
- end
-
- def configure_password
- if config.key?(:ssh_password) && config[:ssh_password].nil?
- # if we have an actual nil that means someone called "--ssh-password" with no value, so we prompt for a password
- config[:ssh_password] = get_password
- else
- # the false default of ssh_password results in a nil here
- config[:ssh_password] = get_stripped_unfrozen_value(config[:ssh_password])
- end
- end
-
- def configure_ssh_identity_file
- config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file])
- end
-
- def configure_ssh_gateway_identity
- config[:ssh_gateway_identity] = get_stripped_unfrozen_value(config[:ssh_gateway_identity])
- end
-
- def run
- @longest = 0
-
- if @name_args.length < 1
- show_usage
- ui.fatal("You must specify the SEARCH QUERY.")
- exit(1)
- end
-
- configure_user
- configure_password
- @password = config[:ssh_password] if config[:ssh_password]
-
- # If a password was not given, check for SSH identity file.
- unless @password
- configure_ssh_identity_file
- configure_ssh_gateway_identity
- end
-
- configure_gateway
- configure_session
-
- exit_status =
- case @name_args[1]
- when "interactive"
- interactive
- when "screen"
- screen
- when "tmux"
- tmux
- when "macterm"
- macterm
- when "cssh"
- cssh
- else
- ssh_command(@name_args[1..].join(" "))
- end
-
- session.close
- if exit_status && exit_status != 0
- exit exit_status
- else
- exit_status
- end
- end
-
- private :search_nodes
-
- end
- end
-end
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
deleted file mode 100644
index 0cc4141d42..0000000000
--- a/lib/chef/knife/ssl_check.rb
+++ /dev/null
@@ -1,284 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class SslCheck < Chef::Knife
-
- deps do
- require_relative "../config"
- require "pp" unless defined?(PP)
- require "socket" unless defined?(Socket)
- require "uri" unless defined?(URI)
- require_relative "../http/ssl_policies"
- require "openssl" unless defined?(OpenSSL)
- require_relative "../mixin/proxified_socket"
- include Chef::Mixin::ProxifiedSocket
- end
-
- banner "knife ssl check [URL] (options)"
-
- def initialize(*args)
- @host = nil
- @verify_peer_socket = nil
- @ssl_policy = HTTP::DefaultSSLPolicy
- super
- end
-
- def uri
- @uri ||= begin
- Chef::Log.trace("Checking SSL cert on #{given_uri}")
- URI.parse(given_uri)
- end
- end
-
- def given_uri
- (name_args[0] || Chef::Config.chef_server_url)
- end
-
- def host
- uri.host
- end
-
- def port
- uri.port
- end
-
- def validate_uri
- unless host && port
- invalid_uri!
- end
- rescue URI::Error
- invalid_uri!
- end
-
- def invalid_uri!
- ui.error("Given URI: `#{given_uri}' is invalid")
- show_usage
- exit 1
- end
-
- def verify_peer_socket
- @verify_peer_socket ||= begin
- tcp_connection = proxified_socket(host, port)
- ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
- ssl_client.hostname = host
- ssl_client
- end
- end
-
- def verify_peer_ssl_context
- @verify_peer_ssl_context ||= begin
- verify_peer_context = OpenSSL::SSL::SSLContext.new
- @ssl_policy.apply_to(verify_peer_context)
- verify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
- verify_peer_context
- end
- end
-
- def noverify_socket
- @noverify_socket ||= begin
- tcp_connection = proxified_socket(host, port)
- OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
- end
- end
-
- def noverify_peer_ssl_context
- @noverify_peer_ssl_context ||= begin
- noverify_peer_context = OpenSSL::SSL::SSLContext.new
- @ssl_policy.apply_to(noverify_peer_context)
- noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
- noverify_peer_context
- end
- end
-
- def verify_X509
- cert_debug_msg = ""
- trusted_certificates.each do |cert_name|
- message = check_X509_certificate(cert_name)
- unless message.nil?
- cert_debug_msg << File.expand_path(cert_name) + ": " + message + "\n"
- end
- end
-
- unless cert_debug_msg.empty?
- debug_invalid_X509(cert_debug_msg)
- end
-
- true # Maybe the bad certs won't hurt...
- end
-
- def verify_cert
- ui.msg("Connecting to host #{host}:#{port}")
- verify_peer_socket.connect
- true
- rescue OpenSSL::SSL::SSLError => e
- ui.error "The SSL certificate of #{host} could not be verified"
- Chef::Log.trace e.message
- debug_invalid_cert
- false
- end
-
- def verify_cert_host
- verify_peer_socket.post_connection_check(host)
- true
- rescue OpenSSL::SSL::SSLError => e
- ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname"
- Chef::Log.trace(e)
- debug_invalid_host
- false
- end
-
- def debug_invalid_X509(cert_debug_msg)
- ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n")
- debug_ssl_settings
- debug_chef_ssl_config
-
- ui.warn(<<~BAD_CERTS)
- There are invalid certificates in your trusted_certs_dir.
- OpenSSL will not use the following certificates when verifying SSL connections:
-
- #{cert_debug_msg}
-
- #{ui.color("TO FIX THESE WARNINGS:", :bold)}
-
- We are working on documentation for resolving common issues uncovered here.
-
- * If the certificate is generated by the server, you may try redownloading the
- server's certificate. By default, the certificate is stored in the following
- location on the host where your chef-server runs:
-
- /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
-
- Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
- using SSH/SCP or some other secure method, then re-run this command to confirm
- that the server's certificate is now trusted.
-
- BAD_CERTS
- # @TODO: ^ needs URL once documentation is posted.
- end
-
- def debug_invalid_cert
- noverify_socket.connect
- issuer_info = noverify_socket.peer_cert.issuer
- ui.msg("Certificate issuer data: #{issuer_info}")
-
- ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n")
- debug_ssl_settings
- debug_chef_ssl_config
-
- ui.err(<<~ADVICE)
-
- #{ui.color("TO FIX THIS ERROR:", :bold)}
-
- If the server you are connecting to uses a self-signed certificate, you must
- configure #{ChefUtils::Dist::Infra::PRODUCT} to trust that server's certificate.
-
- By default, the certificate is stored in the following location on the host
- where your chef-server runs:
-
- /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt
-
- Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir})
- using SSH/SCP or some other secure method, then re-run this command to confirm
- that the server's certificate is now trusted.
-
- ADVICE
- end
-
- def debug_invalid_host
- noverify_socket.connect
- subject = noverify_socket.peer_cert.subject
- cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
- cn = cn_field_tuple[1]
-
- ui.error("You are attempting to connect to: '#{host}'")
- ui.error("The server's certificate belongs to '#{cn}'")
- ui.err(<<~ADVICE)
-
- #{ui.color("TO FIX THIS ERROR:", :bold)}
-
- The solution for this issue depends on your networking configuration. If you
- are able to connect to this server using the hostname #{cn}
- instead of #{host}, then you can resolve this issue by updating chef_server_url
- in your configuration file.
-
- If you are not able to connect to the server using the hostname #{cn}
- you will have to update the certificate on the server to use the correct hostname.
- ADVICE
- end
-
- def debug_ssl_settings
- ui.err "OpenSSL Configuration:"
- ui.err "* Version: #{OpenSSL::OPENSSL_VERSION}"
- ui.err "* Certificate file: #{OpenSSL::X509::DEFAULT_CERT_FILE}"
- ui.err "* Certificate directory: #{OpenSSL::X509::DEFAULT_CERT_DIR}"
- end
-
- def debug_chef_ssl_config
- ui.err "#{ChefUtils::Dist::Infra::PRODUCT} SSL Configuration:"
- ui.err "* ssl_ca_path: #{configuration.ssl_ca_path.inspect}"
- ui.err "* ssl_ca_file: #{configuration.ssl_ca_file.inspect}"
- ui.err "* trusted_certs_dir: #{configuration.trusted_certs_dir.inspect}"
- end
-
- def configuration
- Chef::Config
- end
-
- def run
- validate_uri
-
- if verify_X509 && verify_cert && verify_cert_host
- ui.msg "Successfully verified certificates from `#{host}'"
- else
- exit 1
- end
- end
-
- private
-
- def trusted_certificates
- if configuration.trusted_certs_dir && Dir.exist?(configuration.trusted_certs_dir)
- glob_dir = ChefConfig::PathHelper.escape_glob_dir(configuration.trusted_certs_dir)
- Dir.glob(File.join(glob_dir, "*.{crt,pem}"))
- else
- []
- end
- end
-
- def check_X509_certificate(cert_file)
- store = OpenSSL::X509::Store.new
- cert = OpenSSL::X509::Certificate.new(IO.read(File.expand_path(cert_file)))
- begin
- store.add_cert(cert)
- # test if the store can verify the cert we just added
- unless store.verify(cert) # true if verified, false if not
- return store.error_string
- end
- rescue OpenSSL::X509::StoreError => e
- return e.message
- end
- nil
- end
- end
- end
-end
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
deleted file mode 100644
index cfbbc823b2..0000000000
--- a/lib/chef/knife/ssl_fetch.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class SslFetch < Chef::Knife
-
- deps do
- require_relative "../config"
- require "pp" unless defined?(PP)
- require "socket" unless defined?(Socket)
- require "uri" unless defined?(URI)
- require "openssl" unless defined?(OpenSSL)
- require_relative "../mixin/proxified_socket"
- include Chef::Mixin::ProxifiedSocket
- end
-
- banner "knife ssl fetch [URL] (options)"
-
- def initialize(*args)
- super
- @uri = nil
- end
-
- def uri
- @uri ||= begin
- Chef::Log.trace("Checking SSL cert on #{given_uri}")
- URI.parse(given_uri)
- end
- end
-
- def given_uri
- (name_args[0] || Chef::Config.chef_server_url)
- end
-
- def host
- uri.host
- end
-
- def port
- uri.port
- end
-
- def validate_uri
- unless host && port
- invalid_uri!
- end
- rescue URI::Error
- invalid_uri!
- end
-
- def invalid_uri!
- ui.error("Given URI: `#{given_uri}' is invalid")
- show_usage
- exit 1
- end
-
- def remote_cert_chain
- tcp_connection = proxified_socket(host, port)
- shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
- shady_ssl_connection.connect
- shady_ssl_connection.peer_cert_chain
- end
-
- def noverify_peer_ssl_context
- @noverify_peer_ssl_context ||= begin
- noverify_peer_context = OpenSSL::SSL::SSLContext.new
- noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
- noverify_peer_context
- end
- end
-
- def cn_of(certificate)
- subject = certificate.subject
- if cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" }
- cn_field_tuple[1]
- else
- nil
- end
- end
-
- # Convert the CN of a certificate into something that will work well as a
- # filename. To do so, all `*` characters are converted to the string
- # "wildcard" and then all characters other than alphanumeric and hyphen
- # characters are converted to underscores.
- # NOTE: There is some confusion about what the CN will contain when
- # using internationalized domain names. RFC 6125 mandates that the ascii
- # representation be used, but it is not clear whether this is followed in
- # practice.
- # https://tools.ietf.org/html/rfc6125#section-6.4.2
- def normalize_cn(cn)
- cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, "_")
- end
-
- def configuration
- Chef::Config
- end
-
- def trusted_certs_dir
- configuration.trusted_certs_dir
- end
-
- def write_cert(cert)
- FileUtils.mkdir_p(trusted_certs_dir)
- cn = cn_of(cert)
- filename = cn.nil? ? "#{host}_#{Time.new.to_i}" : normalize_cn(cn)
- full_path = File.join(trusted_certs_dir, "#{filename}.crt")
- ui.msg("Adding certificate for #{filename} in #{full_path}")
- File.open(full_path, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f|
- f.print(cert.to_s)
- end
- end
-
- def run
- validate_uri
- ui.warn(<<~TRUST_TRUST)
- Certificates from #{host} will be fetched and placed in your trusted_cert
- directory (#{trusted_certs_dir}).
-
- Knife has no means to verify these are the correct certificates. You should
- verify the authenticity of these certificates after downloading.
-
- TRUST_TRUST
- remote_cert_chain.each do |cert|
- write_cert(cert)
- end
- rescue OpenSSL::SSL::SSLError => e
- # 'unknown protocol' usually means you tried to connect to a non-ssl
- # service. We handle that specially here, any other error we let bubble
- # up (probably a bug of some sort).
- raise unless e.message.include?("unknown protocol")
-
- ui.error("The service at the given URI (#{uri}) does not accept SSL connections")
-
- if uri.scheme == "http"
- https_uri = uri.to_s.sub(/^http/, "https")
- ui.error("Perhaps you meant to connect to '#{https_uri}'?")
- end
- exit 1
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
deleted file mode 100644
index ea5dffdf6c..0000000000
--- a/lib/chef/knife/status.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
-# Copyright:: Copyright 2010-2020, Ian Meyer
-# 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 "../knife"
-require_relative "core/status_presenter"
-require_relative "core/node_presenter"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class Status < Knife
- include Knife::Core::NodeFormattingOptions
-
- deps do
- require_relative "../search/query"
- end
-
- banner "knife status QUERY (options)"
-
- option :run_list,
- short: "-r",
- long: "--run-list",
- description: "Show the run list"
-
- option :sort_reverse,
- short: "-s",
- long: "--sort-reverse",
- description: "Sort the status list by last run time descending"
-
- option :hide_by_mins,
- long: "--hide-by-mins MINS",
- description: "Hide nodes that have run #{ChefUtils::Dist::Infra::CLIENT} in the last MINS minutes"
-
- def append_to_query(term)
- @query << " AND " unless @query.empty?
- @query << term
- end
-
- def run
- ui.use_presenter Knife::Core::StatusPresenter
-
- if config[:long_output]
- opts = {}
- else
- opts = { filter_result:
- { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
- cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"],
- platform_version: ["platform_version"], chef_environment: ["chef_environment"] } }
- end
-
- @query ||= ""
- append_to_query(@name_args[0]) if @name_args[0]
- append_to_query("chef_environment:#{config[:environment]}") if config[:environment]
-
- if config[:hide_by_mins]
- hidemins = config[:hide_by_mins].to_i
- time = Time.now.to_i
- # AND NOT is not valid lucene syntax, so don't use append_to_query
- @query << " " unless @query.empty?
- @query << "NOT ohai_time:[#{(time - hidemins * 60)} TO #{time}]"
- end
-
- @query = @query.empty? ? "*:*" : @query
-
- all_nodes = []
- q = Chef::Search::Query.new
- Chef::Log.info("Sending query: #{@query}")
- q.search(:node, @query, opts) do |node|
- all_nodes << node
- end
-
- output(all_nodes.sort do |n1, n2|
- if config[:sort_reverse] || config[:sort_status_reverse]
- (n2["ohai_time"] || 0) <=> (n1["ohai_time"] || 0)
- else
- (n1["ohai_time"] || 0) <=> (n2["ohai_time"] || 0)
- end
- end)
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/supermarket_download.rb b/lib/chef/knife/supermarket_download.rb
deleted file mode 100644
index 5acd733b78..0000000000
--- a/lib/chef/knife/supermarket_download.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-#
-# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class SupermarketDownload < Knife
-
- banner "knife supermarket download COOKBOOK [VERSION] (options)"
- category "supermarket"
-
- deps do
- require "fileutils" unless defined?(FileUtils)
- end
-
- option :file,
- short: "-f FILE",
- long: "--file FILE",
- description: "The filename to write to."
-
- option :force,
- long: "--force",
- description: "Force download deprecated version."
-
- option :supermarket_site,
- short: "-m SUPERMARKET_SITE",
- long: "--supermarket-site SUPERMARKET_SITE",
- description: "The URL of the Supermarket site.",
- default: "https://supermarket.chef.io"
-
- def run
- if current_cookbook_deprecated?
- message = "DEPRECATION: This cookbook has been deprecated. "
- replacement = replacement_cookbook
- if !replacement.to_s.strip.empty?
- message << "It has been replaced by #{replacement}."
- else
- message << "No replacement has been defined."
- end
- ui.warn message
-
- unless config[:force]
- ui.warn "Use --force to force download deprecated cookbook."
- return
- end
- end
-
- download_cookbook
- end
-
- def version
- @version = desired_cookbook_data["version"]
- end
-
- private
-
- def cookbooks_api_url
- "#{config[:supermarket_site]}/api/v1/cookbooks"
- end
-
- def current_cookbook_data
- @current_cookbook_data ||= begin
- noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}"
- end
- end
-
- def current_cookbook_deprecated?
- current_cookbook_data["deprecated"] == true
- end
-
- def desired_cookbook_data
- @desired_cookbook_data ||= begin
- uri = if @name_args.length == 1
- current_cookbook_data["latest_version"]
- else
- specific_cookbook_version_url
- end
-
- noauth_rest.get uri
- end
- end
-
- def download_cookbook
- ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
- tf = noauth_rest.streaming_request(desired_cookbook_data["file"])
-
- ::FileUtils.cp tf.path, download_location
- ui.info "Cookbook saved: #{download_location}"
- end
-
- def download_location
- config[:file] ||= File.join Dir.pwd, "#{@name_args[0]}-#{version}.tar.gz"
- config[:file]
- end
-
- def replacement_cookbook
- File.basename(current_cookbook_data["replacement"] || "")
- end
-
- def specific_cookbook_version_url
- "#{cookbooks_api_url}/#{@name_args[0]}/versions/#{@name_args[1].tr(".", "_")}"
- end
- end
- end
-end
diff --git a/lib/chef/knife/supermarket_install.rb b/lib/chef/knife/supermarket_install.rb
deleted file mode 100644
index a3d3aa7a5d..0000000000
--- a/lib/chef/knife/supermarket_install.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-#
-# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class SupermarketInstall < Knife
-
- deps do
- require_relative "../exceptions"
- require "shellwords" unless defined?(Shellwords)
- require "mixlib/archive" unless defined?(Mixlib::Archive)
- require_relative "core/cookbook_scm_repo"
- require_relative "../cookbook/metadata"
- end
-
- banner "knife supermarket install COOKBOOK [VERSION] (options)"
- category "supermarket"
-
- option :no_deps,
- short: "-D",
- long: "--skip-dependencies",
- boolean: true,
- default: false,
- description: "Skips automatic dependency installation."
-
- option :cookbook_path,
- short: "-o PATH:PATH",
- long: "--cookbook-path PATH:PATH",
- description: "A colon-separated path to look for cookbooks in.",
- proc: lambda { |o| o.split(":") }
-
- option :default_branch,
- short: "-B BRANCH",
- long: "--branch BRANCH",
- description: "Default branch to work with.",
- default: "master"
-
- option :use_current_branch,
- short: "-b",
- long: "--use-current-branch",
- description: "Use the current branch.",
- boolean: true,
- default: false
-
- option :supermarket_site,
- short: "-m SUPERMARKET_SITE",
- long: "--supermarket-site SUPERMARKET_SITE",
- description: "The URL of the Supermarket site.",
- default: "https://supermarket.chef.io"
-
- attr_reader :cookbook_name
- attr_reader :vendor_path
-
- def run
- if config[:cookbook_path]
- Chef::Config[:cookbook_path] = config[:cookbook_path]
- else
- config[:cookbook_path] = Chef::Config[:cookbook_path]
- end
-
- @cookbook_name = parse_name_args!
- # Check to ensure we have a valid source of cookbooks before continuing
- #
- @install_path = File.expand_path(Array(config[:cookbook_path]).first)
- ui.info "Installing #{@cookbook_name} to #{@install_path}"
-
- @repo = CookbookSCMRepo.new(@install_path, ui, config)
- # cookbook_path = File.join(vendor_path, name_args[0])
- upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
-
- @repo.sanity_check
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- @repo.prepare_to_import(@cookbook_name)
- end
-
- downloader = download_cookbook_to(upstream_file)
- clear_existing_files(File.join(@install_path, @cookbook_name))
- extract_cookbook(upstream_file, downloader.version)
-
- # TODO: it'd be better to store these outside the cookbook repo and
- # keep them around, e.g., in ~/Library/Caches on macOS.
- ui.info("Removing downloaded tarball")
- File.unlink(upstream_file)
-
- if @repo.finalize_updates_to(@cookbook_name, downloader.version)
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- end
- @repo.merge_updates_from(@cookbook_name, downloader.version)
- else
- unless config[:use_current_branch]
- @repo.reset_to_default_state
- end
- end
-
- unless config[:no_deps]
- preferred_metadata.dependencies.each_key do |cookbook|
- # Doesn't do versions.. yet
- nv = self.class.new
- nv.config = config
- nv.name_args = [ cookbook ]
- nv.run
- end
- end
- end
-
- def parse_name_args!
- if name_args.empty?
- ui.error("Please specify a cookbook to download and install.")
- exit 1
- elsif name_args.size >= 2
- unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2
- ui.error("Installing multiple cookbooks at once is not supported.")
- exit 1
- end
- end
- name_args.first
- end
-
- def download_cookbook_to(download_path)
- downloader = Chef::Knife::SupermarketDownload.new
- downloader.config[:file] = download_path
- downloader.config[:supermarket_site] = config[:supermarket_site]
- downloader.name_args = name_args
- downloader.run
- downloader
- end
-
- def extract_cookbook(upstream_file, version)
- ui.info("Uncompressing #{@cookbook_name} version #{version}.")
- Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false)
- end
-
- def clear_existing_files(cookbook_path)
- ui.info("Removing pre-existing version.")
- FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
- end
-
- def convert_path(upstream_file)
- # converts a Windows path (C:\foo) to a mingw path (/c/foo)
- if ENV["MSYSTEM"] == "MINGW32"
- upstream_file.sub(/^([[:alpha:]]):/, '/\1')
- else
- Shellwords.escape upstream_file
- end
- end
-
- # Get the preferred metadata path on disk. Chef prefers the metadata.rb
- # over the metadata.json.
- #
- # @raise if there is no metadata in the cookbook
- #
- # @return [Chef::Cookbook::Metadata]
- def preferred_metadata
- md = Chef::Cookbook::Metadata.new
-
- rb = File.join(@install_path, @cookbook_name, "metadata.rb")
- if File.exist?(rb)
- md.from_file(rb)
- return md
- end
-
- json = File.join(@install_path, @cookbook_name, "metadata.json")
- if File.exist?(json)
- json = IO.read(json)
- md.from_json(json)
- return md
- end
-
- raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/supermarket_share.rb b/lib/chef/knife/supermarket_share.rb
deleted file mode 100644
index 49b3474566..0000000000
--- a/lib/chef/knife/supermarket_share.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-#
-# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class SupermarketShare < Knife
-
- include Chef::Mixin::ShellOut
-
- deps do
- require_relative "../cookbook_loader"
- require_relative "../cookbook_uploader"
- require_relative "../cookbook_site_streaming_uploader"
- require_relative "../mixin/shell_out"
- end
-
- banner "knife supermarket share COOKBOOK [CATEGORY] (options)"
- category "supermarket"
-
- option :cookbook_path,
- short: "-o PATH:PATH",
- long: "--cookbook-path PATH:PATH",
- description: "A colon-separated path to look for cookbooks in.",
- proc: lambda { |o| Chef::Config.cookbook_path = o.split(":") }
-
- option :dry_run,
- long: "--dry-run",
- short: "-n",
- boolean: true,
- default: false,
- description: "Don't take action, only print what files will be uploaded to Supermarket."
-
- option :supermarket_site,
- short: "-m SUPERMARKET_SITE",
- long: "--supermarket-site SUPERMARKET_SITE",
- description: "The URL of the Supermarket site.",
- default: "https://supermarket.chef.io"
-
- def run
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- if @name_args.length < 1
- show_usage
- ui.fatal("You must specify the cookbook name.")
- exit(1)
- elsif @name_args.length < 2
- cookbook_name = @name_args[0]
- category = get_category(cookbook_name)
- else
- cookbook_name = @name_args[0]
- category = @name_args[1]
- end
-
- cl = Chef::CookbookLoader.new(config[:cookbook_path])
- if cl.cookbook_exists?(cookbook_name)
- cookbook = cl[cookbook_name]
- Chef::CookbookUploader.new(cookbook).validate_cookbooks
- tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
- begin
- Chef::Log.trace("Temp cookbook directory is #{tmp_cookbook_dir.inspect}")
- ui.info("Making tarball #{cookbook_name}.tgz")
- shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", cwd: tmp_cookbook_dir)
- rescue => e
- ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.")
- Chef::Log.trace("\n#{e.backtrace.join("\n")}")
- exit(1)
- end
-
- if config[:dry_run]
- ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.")
- result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", cwd: tmp_cookbook_dir)
- ui.info(result.stdout)
- FileUtils.rm_rf tmp_cookbook_dir
- return
- end
-
- begin
- do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key])
- ui.info("Upload complete")
- Chef::Log.trace("Removing local staging directory at #{tmp_cookbook_dir}")
- FileUtils.rm_rf tmp_cookbook_dir
- rescue => e
- ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
- Chef::Log.trace("\n#{e.backtrace.join("\n")}")
- exit(1)
- end
-
- else
- ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.")
- exit(1)
- end
- end
-
- def get_category(cookbook_name)
- data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}")
- data["category"]
- rescue => e
- return "Other" if e.is_a?(Net::HTTPClientException) && e.response.code == "404"
-
- ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
- Chef::Log.trace("\n#{e.backtrace.join("\n")}")
- exit(1)
- end
-
- def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
- uri = "#{config[:supermarket_site]}/api/v1/cookbooks"
-
- category_string = Chef::JSONCompat.to_json({ "category" => cookbook_category })
-
- http_resp = Chef::CookbookSiteStreamingUploader.post(uri, user_id, user_secret_filename, {
- tarball: File.open(cookbook_filename),
- cookbook: category_string,
- })
-
- res = Chef::JSONCompat.from_json(http_resp.body)
- if http_resp.code.to_i != 201
- if res["error_messages"]
- if /Version already exists/.match?(res["error_messages"][0])
- ui.error "The same version of this cookbook already exists on Supermarket."
- exit(1)
- else
- ui.error (res["error_messages"][0]).to_s
- exit(1)
- end
- else
- ui.error "Unknown error while sharing cookbook"
- ui.error "Server response: #{http_resp.body}"
- exit(1)
- end
- end
- res
- end
-
- def tar_cmd
- unless @tar_cmd
- @tar_cmd = "tar"
- begin
- # Unix and Mac only - prefer gnutar
- if shell_out("which gnutar").exitstatus.equal?(0)
- @tar_cmd = "gnutar"
- end
- rescue Errno::ENOENT
- end
- end
- @tar_cmd
- end
- end
- end
-end
diff --git a/lib/chef/knife/supermarket_unshare.rb b/lib/chef/knife/supermarket_unshare.rb
deleted file mode 100644
index 686d95f47a..0000000000
--- a/lib/chef/knife/supermarket_unshare.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class SupermarketUnshare < Knife
-
- deps do
- require_relative "../json_compat"
- end
-
- banner "knife supermarket unshare COOKBOOK"
- category "supermarket"
-
- option :supermarket_site,
- short: "-m SUPERMARKET_SITE",
- long: "--supermarket-site SUPERMARKET_SITE",
- description: "The URL of the Supermarket site.",
- default: "https://supermarket.chef.io"
-
- def run
- @cookbook_name = @name_args[0]
- if @cookbook_name.nil?
- show_usage
- ui.fatal "You must provide the name of the cookbook to unshare"
- exit 1
- end
-
- confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
-
- begin
- rest.delete "#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}"
- rescue Net::HTTPClientException => e
- raise e unless /Forbidden/.match?(e.message)
-
- ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
- exit 1
- end
-
- ui.info "Unshared all versions of the cookbook #{@cookbook_name}"
- end
- end
- end
-end
diff --git a/lib/chef/knife/tag_create.rb b/lib/chef/knife/tag_create.rb
deleted file mode 100644
index 2f0d302e74..0000000000
--- a/lib/chef/knife/tag_create.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
-
-class Chef
- class Knife
- class TagCreate < Knife
-
- deps do
- require_relative "../node"
- end
-
- banner "knife tag create NODE TAG ..."
-
- def run
- name = @name_args[0]
- tags = @name_args[1..]
-
- if name.nil? || tags.nil? || tags.empty?
- show_usage
- ui.fatal("You must specify a node name and at least one tag.")
- exit 1
- end
-
- node = Chef::Node.load name
- tags.each do |tag|
- (node.tags << tag).uniq!
- end
- node.save
- ui.info("Created tags #{tags.join(", ")} for node #{name}.")
- end
- end
- end
-end
diff --git a/lib/chef/knife/tag_delete.rb b/lib/chef/knife/tag_delete.rb
deleted file mode 100644
index 85fa6a9e27..0000000000
--- a/lib/chef/knife/tag_delete.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
-
-class Chef
- class Knife
- class TagDelete < Knife
-
- deps do
- require_relative "../node"
- end
-
- banner "knife tag delete NODE TAG ..."
-
- def run
- name = @name_args[0]
- tags = @name_args[1..]
-
- if name.nil? || tags.nil? || tags.empty?
- show_usage
- ui.fatal("You must specify a node name and at least one tag.")
- exit 1
- end
-
- node = Chef::Node.load name
- deleted_tags = []
- tags.each do |tag|
- unless node.tags.delete(tag).nil?
- deleted_tags << tag
- end
- end
- node.save
- message = if deleted_tags.empty?
- "Nothing has changed. The tags requested to be deleted do not exist."
- else
- "Deleted tags #{deleted_tags.join(", ")} for node #{name}."
- end
- ui.info(message)
- end
- end
- end
-end
diff --git a/lib/chef/knife/tag_list.rb b/lib/chef/knife/tag_list.rb
deleted file mode 100644
index 8b91034609..0000000000
--- a/lib/chef/knife/tag_list.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# Author:: Ryan Davis (<ryand-ruby@zenspider.com>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, 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 "../knife"
-
-class Chef
- class Knife
- class TagList < Knife
-
- deps do
- require_relative "../node"
- end
-
- banner "knife tag list NODE"
-
- def run
- name = @name_args[0]
-
- if name.nil?
- show_usage
- ui.fatal("You must specify a node name.")
- exit 1
- end
-
- node = Chef::Node.load(name)
- output(node.tags)
- end
- end
- end
-end
diff --git a/lib/chef/knife/upload.rb b/lib/chef/knife/upload.rb
deleted file mode 100644
index 190549d86a..0000000000
--- a/lib/chef/knife/upload.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Upload < Chef::ChefFS::Knife
- banner "knife upload PATTERNS (options)"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/command_line"
- end
-
- option :recurse,
- long: "--[no-]recurse",
- boolean: true,
- default: true,
- description: "List directories recursively."
-
- option :purge,
- long: "--[no-]purge",
- boolean: true,
- default: false,
- description: "Delete matching local files and directories that do not exist remotely."
-
- option :force,
- long: "--[no-]force",
- boolean: true,
- default: false,
- description: "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks."
-
- option :freeze,
- long: "--[no-]freeze",
- boolean: true,
- default: false,
- description: "Freeze cookbooks that get uploaded."
-
- option :dry_run,
- long: "--dry-run",
- short: "-n",
- boolean: true,
- default: false,
- description: "Don't take action, only print what would happen."
-
- option :diff,
- long: "--[no-]diff",
- boolean: true,
- default: true,
- description: "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff."
-
- def run
- if name_args.length == 0
- show_usage
- ui.fatal("You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"")
- exit 1
- end
-
- error = false
- pattern_args.each do |pattern|
- if Chef::ChefFS::FileSystem.copy_to(pattern, local_fs, chef_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
- error = true
- end
- end
- if error
- exit 1
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
deleted file mode 100644
index 6d68f3ebbb..0000000000
--- a/lib/chef/knife/user_create.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Knife
- class UserCreate < Knife
-
- attr_accessor :user_field
-
- deps do
- require_relative "../user_v1"
- end
-
- option :file,
- short: "-f FILE",
- long: "--file FILE",
- description: "Write the private key to a file if the server generated one."
-
- option :user_key,
- long: "--user-key FILENAME",
- description: "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)."
-
- option :prevent_keygen,
- short: "-k",
- long: "--prevent-keygen",
- description: "API V1 (#{ChefUtils::Dist::Server::PRODUCT} 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
- boolean: true
-
- banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
-
- def user
- @user_field ||= Chef::UserV1.new
- end
-
- def create_user_from_hash(hash)
- Chef::UserV1.from_hash(hash).create
- end
-
- def run
- test_mandatory_field(@name_args[0], "username")
- user.username @name_args[0]
-
- test_mandatory_field(@name_args[1], "display name")
- user.display_name @name_args[1]
-
- test_mandatory_field(@name_args[2], "first name")
- user.first_name @name_args[2]
-
- test_mandatory_field(@name_args[3], "last name")
- user.last_name @name_args[3]
-
- test_mandatory_field(@name_args[4], "email")
- user.email @name_args[4]
-
- test_mandatory_field(@name_args[5], "password")
- user.password @name_args[5]
-
- if config[:user_key] && config[:prevent_keygen]
- show_usage
- ui.fatal("You cannot pass --user-key and --prevent-keygen")
- exit 1
- end
-
- if !config[:prevent_keygen] && !config[:user_key]
- user.create_key(true)
- end
-
- if config[:user_key]
- user.public_key File.read(File.expand_path(config[:user_key]))
- end
-
- output = edit_hash(user)
- final_user = create_user_from_hash(output)
-
- ui.info("Created #{user}")
- if final_user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(final_user.private_key)
- end
- else
- ui.msg final_user.private_key
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
deleted file mode 100644
index 87c1f734bb..0000000000
--- a/lib/chef/knife/user_delete.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class UserDelete < Knife
-
- deps do
- require_relative "../user_v1"
- end
-
- banner "knife user delete USER (options)"
-
- def run
- @user_name = @name_args[0]
-
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
-
- delete_object(Chef::UserV1, @user_name)
- end
- end
- end
-end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
deleted file mode 100644
index ad9dfac079..0000000000
--- a/lib/chef/knife/user_edit.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class UserEdit < Knife
-
- deps do
- require_relative "../user_v1"
- end
-
- banner "knife user edit USER (options)"
-
- def run
- @user_name = @name_args[0]
-
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
-
- original_user = Chef::UserV1.load(@user_name).to_hash
- edited_user = edit_hash(original_user)
- if original_user != edited_user
- user = Chef::UserV1.from_hash(edited_user)
- user.update
- ui.msg("Saved #{user}.")
- else
- ui.msg("User unchanged, not saving.")
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
deleted file mode 100644
index f6aa7bcfc4..0000000000
--- a/lib/chef/knife/user_list.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class UserList < Knife
-
- deps do
- require_relative "../user_v1"
- end
-
- banner "knife user list (options)"
-
- option :with_uri,
- short: "-w",
- long: "--with-uri",
- description: "Show corresponding URIs."
-
- def run
- output(format_list_for_display(Chef::UserV1.list))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
deleted file mode 100644
index ee58c19d9f..0000000000
--- a/lib/chef/knife/user_reregister.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class UserReregister < Knife
-
- deps do
- require_relative "../user_v1"
- end
-
- banner "knife user reregister USER (options)"
-
- option :file,
- short: "-f FILE",
- long: "--file FILE",
- description: "Write the private key to a file."
-
- def run
- @user_name = @name_args[0]
-
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
-
- user = Chef::UserV1.load(@user_name)
- user.reregister
- Chef::Log.trace("Updated user data: #{user.inspect}")
- key = user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(key)
- end
- else
- ui.msg key
- end
- end
- end
- end
-end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
deleted file mode 100644
index e59f969e9a..0000000000
--- a/lib/chef/knife/user_show.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "../knife"
-
-class Chef
- class Knife
- class UserShow < Knife
-
- include Knife::Core::MultiAttributeReturnOption
-
- deps do
- require_relative "../user_v1"
- end
-
- banner "knife user show USER (options)"
-
- def run
- @user_name = @name_args[0]
-
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
-
- user = Chef::UserV1.load(@user_name)
- output(format_for_display(user))
- end
-
- end
- end
-end
diff --git a/lib/chef/knife/xargs.rb b/lib/chef/knife/xargs.rb
deleted file mode 100644
index 9dcc724d38..0000000000
--- a/lib/chef/knife/xargs.rb
+++ /dev/null
@@ -1,282 +0,0 @@
-#
-# 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 "../chef_fs/knife"
-
-class Chef
- class Knife
- class Xargs < Chef::ChefFS::Knife
- banner "knife xargs [COMMAND] (options)"
-
- category "path-based"
-
- deps do
- require_relative "../chef_fs/file_system"
- require_relative "../chef_fs/file_system/exceptions"
- end
-
- # TODO modify to remote-only / local-only pattern (more like delete)
- option :local,
- long: "--local",
- boolean: true,
- description: "Xargs local files instead of remote."
-
- option :patterns,
- long: "--pattern [PATTERN]",
- short: "-p [PATTERN]",
- description: "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
- arg_arity: [1, -1]
-
- option :diff,
- long: "--[no-]diff",
- default: true,
- boolean: true,
- description: "Whether to show a diff when files change (default: true)."
-
- option :dry_run,
- long: "--dry-run",
- boolean: true,
- description: "Prevents changes from actually being uploaded to the server."
-
- option :force,
- long: "--[no-]force",
- boolean: true,
- default: false,
- description: "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)."
-
- option :replace_first,
- long: "--replace-first REPLACESTR",
- short: "-J REPLACESTR",
- description: "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
-
- option :replace_all,
- long: "--replace REPLACESTR",
- short: "-I REPLACESTR",
- description: "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
-
- option :max_arguments_per_command,
- long: "--max-args MAXARGS",
- short: "-n MAXARGS",
- description: "Maximum number of arguments per command line."
-
- option :max_command_line,
- long: "--max-chars LENGTH",
- short: "-s LENGTH",
- description: "Maximum size of command line, in characters."
-
- option :verbose_commands,
- short: "-t",
- description: "Print command to be run on the command line."
-
- option :null_separator,
- short: "-0",
- boolean: true,
- description: "Use the NULL character (\0) as a separator, instead of whitespace."
-
- def run
- error = false
- # Get the matches (recursively)
- files = []
- pattern_args_from(get_patterns).each do |pattern|
- Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
- if result.dir?
- # TODO option to include directories
- ui.warn "#{format_path(result)}: is a directory. Will not run #{command} on it."
- else
- files << result
- ran = false
-
- # If the command would be bigger than max command line, back it off a bit
- # and run a slightly smaller command (with one less arg)
- if config[:max_command_line]
- command, tempfiles = create_command(files)
- begin
- if command.length > config[:max_command_line].to_i
- if files.length > 1
- command, tempfiles_minus_one = create_command(files[0..-2])
- begin
- error = true if xargs_files(command, tempfiles_minus_one)
- files = [ files[-1] ]
- ran = true
- ensure
- destroy_tempfiles(tempfiles)
- end
- else
- error = true if xargs_files(command, tempfiles)
- files = [ ]
- ran = true
- end
- end
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
-
- # If the command has hit the limit for the # of arguments, run it
- if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
- command, tempfiles = create_command(files)
- begin
- error = true if xargs_files(command, tempfiles)
- files = []
- ran = true
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
- end
- end
- end
-
- # Any leftovers commands shall be run
- if files.size > 0
- command, tempfiles = create_command(files)
- begin
- error = true if xargs_files(command, tempfiles)
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
-
- if error
- exit 1
- end
- end
-
- def get_patterns
- if config[:patterns]
- [ config[:patterns] ].flatten
- elsif config[:null_separator]
- stdin.binmode
- stdin.read.split("\000")
- else
- stdin.read.split(/\s+/)
- end
- end
-
- def create_command(files)
- command = name_args.join(" ")
-
- # Create the (empty) tempfiles
- tempfiles = {}
- begin
- # Create the temporary files
- files.each do |file|
- tempfile = Tempfile.new(file.name)
- tempfiles[tempfile] = { file: file }
- end
- rescue
- destroy_tempfiles(files)
- raise
- end
-
- # Create the command
- paths = tempfiles.keys.map(&:path).join(" ")
- if config[:replace_all]
- final_command = command.gsub(config[:replace_all], paths)
- elsif config[:replace_first]
- final_command = command.sub(config[:replace_first], paths)
- else
- final_command = "#{command} #{paths}"
- end
-
- [final_command, tempfiles]
- end
-
- def destroy_tempfiles(tempfiles)
- # Unlink the files now that we're done with them
- tempfiles.each_key(&:close!)
- end
-
- def xargs_files(command, tempfiles)
- error = false
- # Create the temporary files
- tempfiles.each_pair do |tempfile, file|
-
- value = file[:file].read
- file[:value] = value
- tempfile.open
- tempfile.write(value)
- tempfile.close
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path(e.entry)}: #{e.reason}."
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
-
- end
-
- return error if error && tempfiles.size == 0
-
- # Run the command
- if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
- output sub_filenames(command, tempfiles)
- end
- command_output = `#{command}`
- command_output = sub_filenames(command_output, tempfiles)
- stdout.write command_output
-
- # Check if the output is different
- tempfiles.each_pair do |tempfile, file|
- # Read the new output
- new_value = IO.binread(tempfile.path)
-
- # Upload the output if different
- if config[:force] || new_value != file[:value]
- if config[:dry_run]
- output "Would update #{format_path(file[:file])}"
- else
- file[:file].write(new_value)
- output "Updated #{format_path(file[:file])}"
- end
- end
-
- # Print a diff of what was uploaded
- if config[:diff] && new_value != file[:value]
- old_file = Tempfile.open(file[:file].name)
- begin
- old_file.write(file[:value])
- old_file.close
-
- diff = `diff -u #{old_file.path} #{tempfile.path}`
- diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
- diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
- stdout.write diff
- ensure
- old_file.close!
- end
- end
- end
-
- error
- end
-
- def sub_filenames(str, tempfiles)
- tempfiles.each_pair do |tempfile, file|
- str = str.gsub(tempfile.path, format_path(file[:file]))
- end
- str
- end
-
- end
- end
-end
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 178884c7ae..88e09758cb 100644
--- a/lib/chef/mixin/get_source_from_package.rb
+++ b/lib/chef/mixin/get_source_from_package.rb
@@ -38,7 +38,7 @@ class Chef
# if we're passed something that looks like a filesystem path, with no source, use it
# - require at least one '/' in the path to avoid gem_package "foo" breaking if a file named 'foo' exists in the cwd
- if new_resource.source.nil? && new_resource.package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(new_resource.package_name)
+ if new_resource.source.nil? && new_resource.package_name.include?(::File::SEPARATOR) && ::File.exist?(new_resource.package_name)
Chef::Log.trace("No package source specified, but #{new_resource.package_name} exists on the filesystem, copying to package source")
new_resource.source(new_resource.package_name)
end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index d569eeda38..45e8855326 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -687,6 +687,25 @@ class Chef
name <=> other.name
end
+ # Returns hash of node data with attributes based on whitelist/blacklist rules.
+ def data_for_save
+ data = for_json
+ %w{automatic default normal override}.each do |level|
+ allowlist = allowlist_or_whitelist_config(level)
+ unless allowlist.nil? # nil => save everything
+ logger.info("Allowing #{level} node attributes for save.")
+ data[level] = Chef::AttributeAllowlist.filter(data[level], allowlist)
+ end
+
+ blocklist = blocklist_or_blacklist_config(level)
+ unless blocklist.nil? # nil => remove nothing
+ logger.info("Blocking #{level} node attributes for save")
+ data[level] = Chef::AttributeBlocklist.filter(data[level], blocklist)
+ end
+ end
+ data
+ end
+
private
def save_without_policyfile_attrs
@@ -712,7 +731,7 @@ class Chef
# @param [String] level the attribute level
def allowlist_or_whitelist_config(level)
if Chef::Config["#{level}_attribute_whitelist".to_sym]
- Chef.deprecated(:attribute_blacklist_configuration, "Attribute whitelist configurations have been deprecated. Use the allowed_LEVEL_attribute configs instead")
+ Chef.deprecated(:attribute_whitelist_configuration, "Attribute whitelist configurations have been deprecated. Use the allowed_LEVEL_attribute configs instead")
Chef::Config["#{level}_attribute_whitelist".to_sym]
else
Chef::Config["allowed_#{level}_attributes".to_sym]
@@ -732,24 +751,6 @@ class Chef
end
end
- def data_for_save
- data = for_json
- %w{automatic default normal override}.each do |level|
- allowlist = allowlist_or_whitelist_config(level)
- unless allowlist.nil? # nil => save everything
- logger.info("Allowing #{level} node attributes for save.")
- data[level] = Chef::AttributeAllowlist.filter(data[level], allowlist)
- end
-
- blocklist = blocklist_or_blacklist_config(level)
- unless blocklist.nil? # nil => remove nothing
- logger.info("Blocking #{level} node attributes for save")
- data[level] = Chef::AttributeBlocklist.filter(data[level], blocklist)
- end
- end
- data
- end
-
# Returns a UUID that uniquely identifies this node for reporting reasons.
#
# The node is read in from disk if it exists, or it's generated if it does
@@ -761,7 +762,7 @@ class Chef
path = File.expand_path(Chef::Config[:chef_guid_path])
dir = File.dirname(path)
- unless File.exists?(path)
+ unless File.exist?(path)
FileUtils.mkdir_p(dir)
File.write(path, SecureRandom.uuid)
end
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 29b60a98d5..6a8e72004b 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -158,8 +158,10 @@ class Chef
}.freeze
ENUM_METHODS.each do |delegated_method|
- define_method(delegated_method) do |*args, &block|
- merged_attributes.send(delegated_method, *args, &block)
+ if Hash.public_method_defined?(delegated_method)
+ define_method(delegated_method) do |*args, &block|
+ merged_attributes.send(delegated_method, *args, &block)
+ end
end
end
@@ -596,7 +598,7 @@ class Chef
merge_with.each do |key, merge_with_value|
value =
if merge_onto.key?(key)
- deep_merge!(safe_dup(merge_onto[key]), merge_with_value)
+ deep_merge!(safe_dup(merge_onto.internal_get(key)), merge_with_value)
else
merge_with_value
end
@@ -632,7 +634,7 @@ class Chef
merge_with.each do |key, merge_with_value|
value =
if merge_onto.key?(key)
- hash_only_merge!(safe_dup(merge_onto[key]), merge_with_value)
+ hash_only_merge!(safe_dup(merge_onto.internal_get(key)), merge_with_value)
else
merge_with_value
end
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
index 49dc0256b9..79b1983a05 100644
--- a/lib/chef/node/immutable_collections.rb
+++ b/lib/chef/node/immutable_collections.rb
@@ -19,6 +19,7 @@ require_relative "common_api"
require_relative "mixin/state_tracking"
require_relative "mixin/immutablize_array"
require_relative "mixin/immutablize_hash"
+require_relative "../delayed_evaluator"
class Chef
class Node
@@ -109,6 +110,12 @@ class Chef
key
end
+ def [](*args)
+ value = super
+ value = value.call while value.is_a?(::Chef::DelayedEvaluator)
+ value
+ end
+
prepend Chef::Node::Mixin::StateTracking
prepend Chef::Node::Mixin::ImmutablizeArray
end
@@ -187,6 +194,12 @@ class Chef
e
end
+ def [](*args)
+ value = super
+ value = value.call while value.is_a?(::Chef::DelayedEvaluator)
+ value
+ end
+
prepend Chef::Node::Mixin::StateTracking
prepend Chef::Node::Mixin::ImmutablizeHash
end
diff --git a/lib/chef/node/mixin/deep_merge_cache.rb b/lib/chef/node/mixin/deep_merge_cache.rb
index e88e079895..8978d77ea0 100644
--- a/lib/chef/node/mixin/deep_merge_cache.rb
+++ b/lib/chef/node/mixin/deep_merge_cache.rb
@@ -15,6 +15,8 @@
# limitations under the License.
#
+require_relative "../../delayed_evaluator"
+
class Chef
class Node
module Mixin
@@ -46,13 +48,15 @@ class Chef
alias :reset :reset_cache
def [](key)
- if deep_merge_cache.key?(key.to_s)
- # return the cache of the deep merged values by top-level key
- deep_merge_cache[key.to_s]
- else
- # save all the work of computing node[key]
- deep_merge_cache[key.to_s] = merged_attributes(key)
- end
+ ret = if deep_merge_cache.key?(key.to_s)
+ # return the cache of the deep merged values by top-level key
+ deep_merge_cache[key.to_s]
+ else
+ # save all the work of computing node[key]
+ deep_merge_cache[key.to_s] = merged_attributes(key)
+ end
+ ret = ret.call while ret.is_a?(::Chef::DelayedEvaluator)
+ ret
end
end
diff --git a/lib/chef/org.rb b/lib/chef/org.rb
index e2b7c49051..8f65f3ddd1 100644
--- a/lib/chef/org.rb
+++ b/lib/chef/org.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@chef.io)
-# Copyright:: Copyright (c) Chef Software Inc.
+# Copyright:: Copyright (c) Chef Software Inc
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,10 @@
require_relative "json_compat"
require_relative "mixin/params_validate"
require_relative "server_api"
+require_relative "group"
class Chef
- class Org
+ class Org < Group
include Chef::Mixin::ParamsValidate
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index bac015be42..35282bf915 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -475,7 +475,7 @@ class Chef
end
# @api private
- # Fetches the CookbookVersion object for the given name and identifer
+ # Fetches the CookbookVersion object for the given name and identifier
# specified in the lock_data.
# TODO: This only implements Chef 11 compatibility mode, which means that
# cookbooks are fetched by the "dotted_decimal_identifier": a
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
index bbf06854d6..0813005845 100644
--- a/lib/chef/property.rb
+++ b/lib/chef/property.rb
@@ -412,6 +412,7 @@ class Chef
# Otherwise, we have to validate it now.
value = input_to_stored_value(resource, default, is_default: true)
end
+ value = deep_dup(value)
value = stored_value_to_output(resource, value)
# If the value is mutable (non-frozen), we set it on the instance
@@ -748,5 +749,22 @@ class Chef
result
end
+
+ # recursively dup the value
+ def deep_dup(value)
+ return value if value.is_a?(DelayedEvaluator)
+
+ visitor = lambda do |obj|
+ obj = obj.dup rescue obj
+ case obj
+ when Hash
+ obj.each { |k, v| obj[k] = visitor.call(v) }
+ when Array
+ obj.each_with_index { |v, i| obj[i] = visitor.call(v) }
+ end
+ obj
+ end
+ visitor.call(value)
+ end
end
end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 4d5631397b..81ed530fc7 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -435,7 +435,7 @@ class Chef
# this block cannot interact with resources outside, e.g.,
# manipulating notifies.
- converge_by ("evaluate block and run any associated actions") do
+ converge_by("evaluate block and run any associated actions") do
saved_run_context = run_context
begin
@run_context = run_context.create_child
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 7d37f34b1a..77c1973fb4 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -101,7 +101,7 @@ class Chef
if @cron_exists
unless cron_different?
- logger.trace("Skipping existing cron entry '#{new_resource.name}'")
+ logger.debug("#{new_resource}: Skipping existing cron entry")
return
end
read_crontab.each_line do |line|
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 555340d91e..6a20556ccd 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -32,7 +32,7 @@ class Chef
def load_current_resource
@current_resource = Chef::Resource::Directory.new(new_resource.name)
current_resource.path(new_resource.path)
- if ::File.exists?(current_resource.path) && @action != :create_if_missing
+ if ::File.exist?(current_resource.path) && @action != :create_if_missing
load_resource_attributes_from_file(current_resource)
end
current_resource
@@ -73,7 +73,7 @@ class Chef
# make sure we have write permissions to that directory
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
- if ::File.exists?(base_dir)
+ if ::File.exist?(base_dir)
if Chef::FileAccessControl.writable?(base_dir)
true
elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
@@ -89,7 +89,7 @@ class Chef
else
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
- if !whyrun_mode? || ::File.exists?(parent_directory)
+ if !whyrun_mode? || ::File.exist?(parent_directory)
if Chef::FileAccessControl.writable?(parent_directory)
true
elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
@@ -108,7 +108,7 @@ class Chef
requirements.assert(:delete) do |a|
a.assertion do
- if ::File.exists?(new_resource.path)
+ if ::File.exist?(new_resource.path)
::File.directory?(new_resource.path) && Chef::FileAccessControl.writable?(new_resource.path)
else
true
@@ -122,7 +122,7 @@ class Chef
end
action :create do
- unless ::File.exists?(new_resource.path)
+ unless ::File.exist?(new_resource.path)
converge_by("create new directory #{new_resource.path}") do
if new_resource.recursive == true
::FileUtils.mkdir_p(new_resource.path)
@@ -138,7 +138,7 @@ class Chef
end
action :delete do
- if ::File.exists?(new_resource.path)
+ if ::File.exist?(new_resource.path)
converge_by("delete existing directory #{new_resource.path}") do
if new_resource.recursive == true
# we don't use rm_rf here because it masks all errors, including
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index 20b8a40bf1..b4020b8620 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -27,7 +27,7 @@ class Chef
provides :execute, target_mode: true
- def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated, :default_env, :timeout, :input
+ def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated, :default_env, :timeout, :input, :login
def load_current_resource
current_resource = Chef::Resource::Execute.new(new_resource.name)
@@ -92,6 +92,7 @@ class Chef
opts[:cwd] = cwd if cwd
opts[:umask] = umask if umask
opts[:input] = input if input
+ opts[:login] = login if login
opts[:default_env] = default_env
opts[:log_level] = :info
opts[:log_tag] = new_resource.to_s
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index e2c07ad9f7..59724a8340 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -152,7 +152,7 @@ class Chef
unless ::File.exist?(new_resource.path)
action_create
else
- logger.trace("#{new_resource} exists at #{new_resource.path} taking no action.")
+ logger.debug("#{new_resource} exists at #{new_resource.path} taking no action.")
end
end
@@ -338,7 +338,7 @@ class Chef
raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum))
end
- if tempfile
+ if tempfile && contents_changed?
new_resource.verify.each do |v|
unless v.verify(tempfile.path)
backupfile = "#{Chef::Config[:file_cache_path]}/failed_validations/#{::File.basename(tempfile.path)}"
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index f1c8380307..1c0b9f562e 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -68,9 +68,9 @@ class Chef
a.assertion { !(new_resource.revision =~ %r{^origin/}) }
a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
"Deploying remote branches is not supported. " +
- "Specify the remote branch as a local branch for " +
- "the git repository you're deploying from " +
- "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
+ "Specify the remote branch as a local branch for " +
+ "the git repository you're deploying from " +
+ "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
end
requirements.assert(:all_actions) do |a|
@@ -80,8 +80,8 @@ class Chef
a.assertion { !target_revision.nil? }
a.failure_message Chef::Exceptions::UnresolvableGitReference,
"Unable to parse SHA reference for '#{new_resource.revision}' in repository '#{new_resource.repository}'. " +
- "Verify your (case-sensitive) repository URL and revision.\n" +
- "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
+ "Verify your (case-sensitive) repository URL and revision.\n" +
+ "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
end
end
@@ -94,7 +94,7 @@ class Chef
enable_submodules
add_remotes
else
- logger.trace "#{new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
+ logger.debug "#{new_resource} checkout destination #{cwd} already exists or is a non-empty directory"
end
end
@@ -281,12 +281,10 @@ class Chef
def target_revision
@target_revision ||=
- begin
- if sha_hash?(new_resource.revision)
- @target_revision = new_resource.revision
- else
- @target_revision = remote_resolve_reference
- end
+ if sha_hash?(new_resource.revision)
+ @target_revision = new_resource.revision
+ else
+ @target_revision = remote_resolve_reference
end
end
diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb
index 63f1690f5e..b427498a8c 100644
--- a/lib/chef/provider/group/groupadd.rb
+++ b/lib/chef/provider/group/groupadd.rb
@@ -67,7 +67,7 @@ class Chef
members_to_be_added << member unless current_resource.members.include?(member)
end
members_to_be_added.each do |member|
- logger.trace("#{new_resource} appending member #{member} to group #{new_resource.group_name}")
+ logger.debug("#{new_resource} appending member #{member} to group #{new_resource.group_name}")
add_member(member)
end
end
@@ -79,13 +79,13 @@ class Chef
end
members_to_be_removed.each do |member|
- logger.trace("#{new_resource} removing member #{member} from group #{new_resource.group_name}")
+ logger.debug("#{new_resource} removing member #{member} from group #{new_resource.group_name}")
remove_member(member)
end
end
else
members_description = new_resource.members.empty? ? "none" : new_resource.members.join(", ")
- logger.trace("#{new_resource} setting group members to: #{members_description}")
+ logger.debug("#{new_resource} setting group members to: #{members_description}")
set_members(new_resource.members)
end
end
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index e4d69f34bf..d51272fcbe 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -66,13 +66,13 @@ class Chef
end
end
- logger.trace("#{new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
+ logger.debug("#{new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty?
add_group_members(members_to_be_added)
else
# We are resetting the members of a group so use the same trick
reset_group_membership
- logger.trace("#{new_resource} setting group members to: none") if new_resource.members.empty?
+ logger.debug("#{new_resource} setting group members to: none") if new_resource.members.empty?
add_group_members(new_resource.members)
end
end
@@ -84,7 +84,7 @@ class Chef
# Adds a list of usernames to the group using `user mod`
def add_group_members(members)
- logger.trace("#{new_resource} adding members #{members.join(", ")}") unless members.empty?
+ logger.debug("#{new_resource} adding members #{members.join(", ")}") unless members.empty?
members.each do |user|
shell_out!("user", "mod", "-G", new_resource.group_name, user)
end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 0639b8fdb5..70b43478cc 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -44,7 +44,7 @@ class Chef
# new or existing group. Because pw groupadd does not support the -m
# and -d options used by manage_group, we treat group creation as a
# special case and use -M.
- logger.trace("#{new_resource} setting group members: #{new_resource.members.join(",")}")
+ logger.debug("#{new_resource} setting group members: #{new_resource.members.join(",")}")
command += [ "-M", new_resource.members.join(",") ]
end
@@ -119,12 +119,12 @@ class Chef
end
unless members_to_be_added.empty?
- logger.trace("#{new_resource} adding group members: #{members_to_be_added.join(",")}")
+ logger.debug("#{new_resource} adding group members: #{members_to_be_added.join(",")}")
opts << [ "-m", members_to_be_added.join(",") ]
end
unless members_to_be_removed.empty?
- logger.trace("#{new_resource} removing group members: #{members_to_be_removed.join(",")}")
+ logger.debug("#{new_resource} removing group members: #{members_to_be_removed.join(",")}")
opts << [ "-d", members_to_be_removed.join(",") ]
end
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index d08564e75d..69d95b260c 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -205,7 +205,7 @@ class Chef
logger.info("#{new_resource} deleted")
end
else
- logger.trace("#{new_resource} does not exist - nothing to do")
+ logger.debug("#{new_resource} does not exist - nothing to do")
end
delete_config
end
@@ -220,7 +220,7 @@ class Chef
logger.info("#{new_resource} disabled")
end
else
- logger.trace("#{new_resource} does not exist - nothing to do")
+ logger.debug("#{new_resource} does not exist - nothing to do")
end
end
diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb
index 900d0516af..113807e49a 100644
--- a/lib/chef/provider/link.rb
+++ b/lib/chef/provider/link.rb
@@ -73,7 +73,7 @@ class Chef
end
def canonicalize(path)
- ChefUtils.windows? ? path.tr("/", '\\') : path
+ ChefUtils.windows? ? path.tr("/", "\\") : path
end
action :create do
@@ -106,7 +106,7 @@ class Chef
if new_resource.link_type == :symbolic
converge_by("create symlink at #{new_resource.target_file} to #{new_resource.to}") do
file_class.symlink(canonicalize(new_resource.to), new_resource.target_file)
- logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.debug("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
logger.info("#{new_resource} created")
# file_class.symlink will create the link with default access controls.
# This means that the access controls of the file could be different
@@ -118,7 +118,7 @@ class Chef
elsif new_resource.link_type == :hard
converge_by("create hard link at #{new_resource.target_file} to #{new_resource.to}") do
file_class.link(new_resource.to, new_resource.target_file)
- logger.trace("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
+ logger.debug("#{new_resource} created #{new_resource.link_type} link from #{new_resource.target_file} -> #{new_resource.to}")
logger.info("#{new_resource} created")
end
end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 44fb94ca01..998ea3ce9b 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -42,7 +42,7 @@ class Chef
logger.info("#{new_resource} mounted")
end
else
- logger.trace("#{new_resource} is already mounted")
+ logger.debug("#{new_resource} is already mounted")
end
end
@@ -53,7 +53,7 @@ class Chef
logger.info("#{new_resource} unmounted")
end
else
- logger.trace("#{new_resource} is already unmounted")
+ logger.debug("#{new_resource} is already unmounted")
end
end
@@ -76,7 +76,7 @@ class Chef
end
end
else
- logger.trace("#{new_resource} not mounted, nothing to remount")
+ logger.debug("#{new_resource} not mounted, nothing to remount")
end
end
@@ -87,7 +87,7 @@ class Chef
logger.info("#{new_resource} enabled")
end
else
- logger.trace("#{new_resource} already enabled")
+ logger.debug("#{new_resource} already enabled")
end
end
@@ -98,7 +98,7 @@ class Chef
logger.info("#{new_resource} disabled")
end
else
- logger.trace("#{new_resource} already disabled")
+ logger.debug("#{new_resource} already disabled")
end
end
@@ -175,8 +175,13 @@ class Chef
# Returns the new_resource device as per device_type
def device_fstab
- # Removed "/" from the end of str, because it was causing idempotency issue.
- device = @new_resource.device == "/" ? @new_resource.device : @new_resource.device.chomp("/")
+ # Removed "/" from the end of str unless it's a network mount, because it was causing idempotency issue.
+ device =
+ if @new_resource.device == "/" || @new_resource.device.match?(":/$")
+ @new_resource.device
+ else
+ @new_resource.device.chomp("/")
+ end
case @new_resource.device_type
when :device
device
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 2cb29f5858..1a72eecbd3 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -140,7 +140,7 @@ class Chef
shell_out!(command)
logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
@@ -154,7 +154,7 @@ class Chef
def enable_fs
if @current_resource.enabled && mount_options_unchanged?
- logger.trace("#{@new_resource} is already enabled - nothing to do")
+ logger.debug("#{@new_resource} is already enabled - nothing to do")
return nil
end
@@ -211,7 +211,7 @@ class Chef
contents.each { |line| fstab.puts line }
end
else
- logger.trace("#{@new_resource} is not enabled - nothing to do")
+ logger.debug("#{@new_resource} is not enabled - nothing to do")
end
end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 0bd81d5453..802ee11c23 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -120,7 +120,7 @@ class Chef
shell_out!(*command)
logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
@@ -129,7 +129,7 @@ class Chef
shell_out!("umount", @new_resource.mount_point)
logger.trace("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
else
- logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ logger.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
end
end
@@ -147,7 +147,7 @@ class Chef
sleep 1
mount_fs
else
- logger.trace("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
+ logger.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
end
end
@@ -158,7 +158,7 @@ class Chef
def enable_fs
if @current_resource.enabled && mount_options_unchanged? && device_unchanged?
- logger.trace("#{@new_resource} is already enabled - nothing to do")
+ logger.debug("#{@new_resource} is already enabled - nothing to do")
return nil
end
@@ -203,7 +203,7 @@ class Chef
end
end
# Removed "/" from the end of str, because it was causing idempotency issue.
- @real_device == "/" ? @real_device : @real_device.chomp("/")
+ (@real_device == "/" || @real_device.match?(":/$")) ? @real_device : @real_device.chomp("/")
end
def device_logstring
@@ -272,7 +272,7 @@ class Chef
contents.reverse_each { |line| fstab.puts line }
end
else
- logger.trace("#{@new_resource} is not enabled - nothing to do")
+ logger.debug("#{@new_resource} is not enabled - nothing to do")
end
end
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index 81e9ad6b1d..5b68417ab0 100644
--- a/lib/chef/provider/mount/windows.rb
+++ b/lib/chef/provider/mount/windows.rb
@@ -67,7 +67,7 @@ class Chef
password: @new_resource.password)
logger.trace("#{@new_resource} is mounted at #{@new_resource.mount_point}")
else
- logger.trace("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ logger.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
end
end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 6cbc8c7b24..65d2254258 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -28,14 +28,41 @@ class Chef
class Package < Chef::Provider
extend Chef::Mixin::SubclassDirective
- # subclasses declare this if they want all their arguments as arrays of packages and names
+ # subclasses declare this if they want all their arguments as arrays of packages and names.
+ # any new packages using this should also use allow_nils below.
+ #
subclass_directive :use_multipackage_api
- # subclasses declare this if they want sources (filenames) pulled from their package names
+
+ # subclasses declare this if they want sources (filenames) pulled from their package names.
+ # this is for package providers that take a path into the filesystem (rpm, dpkg).
+ #
subclass_directive :use_package_name_for_source
+
# keeps package_names_for_targets and versions_for_targets indexed the same as package_name at
- # the cost of having the subclass needing to deal with nils
+ # the cost of having the subclass needing to deal with nils. all providers are encouraged to
+ # migrate to using this as it simplifies dealing with package aliases in subclasses.
+ #
subclass_directive :allow_nils
+ # subclasses that implement complex pattern matching using constraints, particularly the yum and
+ # dnf classes, should filter the installed version against the desired version constraint and
+ # return nil if it does not match. this means that 'nil' does not mean that no version of the
+ # package is installed, but that the installed version does not satisfy the desired constraints.
+ # (the package plus the constraints are not installed)
+ #
+ # [ this may arguably be useful for all package providers and it greatly simplifies the logic
+ # in the superclass that gets executed, so maybe this should always be used now? ]
+ #
+ # note that when using this feature that the current_resource.version must be loaded with the
+ # correct currently installed version, without doing the filtering -- for reporting and for
+ # correctly displaying version upgrades. that means there are 3 different arrays which must be
+ # loaded by the subclass: candidate_version, magic_version and current_resource.version.
+ #
+ # NOTE: magic_version is a terrible name, but I couldn't think of anything better, at least this
+ # way it stands out clearly.
+ #
+ subclass_directive :use_magic_version
+
#
# Hook that subclasses use to populate the candidate_version(s)
#
@@ -82,7 +109,7 @@ class Chef
action :install do
unless target_version_array.any?
- logger.trace("#{new_resource} is already installed - nothing to do")
+ logger.debug("#{new_resource} is already installed - nothing to do")
return
end
@@ -111,7 +138,7 @@ class Chef
action :upgrade do
unless target_version_array.any?
- logger.trace("#{new_resource} no versions to upgrade - nothing to do")
+ logger.debug("#{new_resource} no versions to upgrade - nothing to do")
return
end
@@ -150,7 +177,7 @@ class Chef
logger.info("#{new_resource} removed")
end
else
- logger.trace("#{new_resource} package does not exist - nothing to do")
+ logger.debug("#{new_resource} package does not exist - nothing to do")
end
end
@@ -202,7 +229,7 @@ class Chef
end
end
else
- logger.trace("#{new_resource} is already locked")
+ logger.debug("#{new_resource} is already locked")
end
end
@@ -221,7 +248,7 @@ class Chef
end
end
else
- logger.trace("#{new_resource} is already unlocked")
+ logger.debug("#{new_resource} is already unlocked")
end
end
@@ -414,19 +441,21 @@ class Chef
each_package do |package_name, new_version, current_version, candidate_version|
case action
when :upgrade
- if version_equals?(current_version, new_version)
- # this is an odd use case
- logger.trace("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future")
- target_version_array.push(nil)
- elsif version_equals?(current_version, candidate_version)
- logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
+ if current_version.nil?
+ # with use_magic_version there may be a package installed, but it fails the user's
+ # requested new_resource.version constraints
+ logger.trace("#{new_resource} has no existing installed version. Installing install #{candidate_version}")
+ target_version_array.push(candidate_version)
+ elsif !use_magic_version? && version_equals?(current_version, new_version)
+ # this is a short-circuit (mostly for the rubygems provider) to avoid needing to expensively query the candidate_version which must come later
+ logger.trace("#{new_resource} #{package_name} #{new_version} is already installed")
target_version_array.push(nil)
elsif candidate_version.nil?
logger.trace("#{new_resource} #{package_name} has no candidate_version to upgrade to")
target_version_array.push(nil)
- elsif current_version.nil?
- logger.trace("#{new_resource} has no existing installed version. Installing install #{candidate_version}")
- target_version_array.push(candidate_version)
+ elsif version_equals?(current_version, candidate_version)
+ logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
+ target_version_array.push(nil)
elsif !allow_downgrade && version_compare(current_version, candidate_version) == 1
logger.trace("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{candidate_version}. Skipping...)")
target_version_array.push(nil)
@@ -436,21 +465,20 @@ class Chef
end
when :install
- if new_version
+ if new_version && !use_magic_version?
if version_requirement_satisfied?(current_version, new_version)
logger.trace("#{new_resource} #{package_name} #{current_version} satisfies #{new_version} requirement")
target_version_array.push(nil)
elsif current_version && !allow_downgrade && version_compare(current_version, new_version) == 1
logger.warn("#{new_resource} #{package_name} has installed version #{current_version}, which is newer than available version #{new_version}. Skipping...)")
target_version_array.push(nil)
- elsif version_equals?(current_version, candidate_version)
- logger.trace("#{new_resource} #{package_name} #{candidate_version} is already installed")
- target_version_array.push(nil)
else
logger.trace("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}")
target_version_array.push(new_version)
end
elsif current_version.nil?
+ # with use_magic_version there may be a package installed, but it fails the user's
+ # requested new_resource.version constraints
logger.trace("#{new_resource} #{package_name} not installed, installing #{candidate_version}")
target_version_array.push(candidate_version)
else
@@ -511,8 +539,14 @@ class Chef
each_package do |package_name, new_version, current_version, candidate_version|
next if new_version.nil? || current_version.nil?
- if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
- missing.push(package_name)
+ if use_magic_version?
+ if !magic_version && candidate_version.nil?
+ missing.push(package_name)
+ end
+ else
+ if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil?
+ missing.push(package_name)
+ end
end
end
missing
@@ -525,7 +559,7 @@ class Chef
def each_package
package_name_array.each_with_index do |package_name, i|
candidate_version = candidate_version_array[i]
- current_version = current_version_array[i]
+ current_version = use_magic_version? ? magic_version[i] : current_version_array[i]
new_version = new_version_array[i]
yield package_name, new_version, current_version, candidate_version
end
@@ -564,12 +598,10 @@ class Chef
# @return [Array] new_resource.source as an array
def source_array
@source_array ||=
- begin
- if new_resource.source.nil?
- package_name_array.map { nil }
- else
- [ new_resource.source ].flatten
- end
+ if new_resource.source.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.source ].flatten
end
end
@@ -578,16 +610,14 @@ class Chef
# @return [Array] Array of sources with package_names converted to sources
def resolved_source_array
@resolved_source_array ||=
- begin
- source_array.each_with_index.map do |source, i|
- package_name = package_name_array[i]
- # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
- if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
- logger.trace("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
- package_name
- else
- source
- end
+ source_array.each_with_index.map do |source, i|
+ package_name = package_name_array[i]
+ # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
+ if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
+ logger.trace("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
+ package_name
+ else
+ source
end
end
end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index dbacaedb07..6f2f645ead 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -176,6 +176,7 @@ class Chef
def resolve_package_versions(pkg)
current_version = nil
candidate_version = nil
+ all_versions = []
run_noninteractive("apt-cache", default_release_options, "policy", pkg).stdout.each_line do |line|
case line
when /^\s{2}Installed: (.+)$/
@@ -184,9 +185,34 @@ class Chef
when /^\s{2}Candidate: (.+)$/
candidate_version = ( $1 != "(none)" ) ? $1 : nil
logger.trace("#{new_resource} candidate version for #{pkg} is #{$1}")
+ when /\s+(?:\*\*\* )?(\S+) \d+/
+ all_versions << $1
end
end
- [ current_version, candidate_version ]
+ # This is a bit ugly... really this whole provider needs
+ # to be rewritten to use target_version_array and friends, but
+ # for now this gets us moving
+ idx = package_name_array.index(pkg)
+ chosen_version =
+ if idx
+ user_ver = new_version_array[idx]
+ if user_ver
+ if all_versions.include?(user_ver)
+ user_ver
+ else
+ logger.debug("User specified a version that's not available")
+ nil
+ end
+ else
+ # user didn't specify a version, use candidate
+ candidate_version
+ end
+ else
+ # this probably means we're redirected from a virtual
+ # package, so... just go with candidate version
+ candidate_version
+ end
+ [ current_version, chosen_version ]
end
def resolve_virtual_package_name(pkg)
diff --git a/lib/chef/provider/package/deb.rb b/lib/chef/provider/package/deb.rb
index 1b1b151c93..8448535303 100644
--- a/lib/chef/provider/package/deb.rb
+++ b/lib/chef/provider/package/deb.rb
@@ -28,12 +28,12 @@ class Chef
action :reconfig do
if current_resource.version.nil?
- logger.trace("#{new_resource} is NOT installed - nothing to do")
+ logger.debug("#{new_resource} is NOT installed - nothing to do")
return
end
unless new_resource.response_file
- logger.trace("#{new_resource} no response_file provided - nothing to do")
+ logger.debug("#{new_resource} no response_file provided - nothing to do")
return
end
@@ -46,7 +46,7 @@ class Chef
logger.info("#{new_resource} reconfigured")
end
else
- logger.trace("#{new_resource} preseeding has not changed - nothing to do")
+ logger.debug("#{new_resource} preseeding has not changed - nothing to do")
end
end
diff --git a/lib/chef/provider/package/dnf.rb b/lib/chef/provider/package/dnf.rb
index 76961b5bde..5c74ad0414 100644
--- a/lib/chef/provider/package/dnf.rb
+++ b/lib/chef/provider/package/dnf.rb
@@ -34,6 +34,7 @@ class Chef
allow_nils
use_multipackage_api
use_package_name_for_source
+ use_magic_version
# all rhel variants >= 8 will use DNF
provides :package, platform_family: "rhel", platform_version: ">= 8"
@@ -71,6 +72,16 @@ class Chef
current_resource
end
+ def load_after_resource
+ # force the installed version array to repopulate
+ @current_version = []
+ @after_resource = Chef::Resource::DnfPackage.new(new_resource.name)
+ after_resource.package_name(new_resource.package_name)
+ after_resource.version(get_current_versions)
+
+ after_resource
+ end
+
def define_resource_requirements
requirements.assert(:install, :upgrade, :remove, :purge) do |a|
a.assertion { !new_resource.source || ::File.exist?(new_resource.source) }
@@ -87,9 +98,15 @@ class Chef
end
end
+ def magic_version
+ package_name_array.each_with_index.map do |pkg, i|
+ magical_version(i).version_with_arch
+ end
+ end
+
def get_current_versions
package_name_array.each_with_index.map do |pkg, i|
- installed_version(i).version_with_arch
+ current_version(i).version_with_arch
end
end
@@ -107,7 +124,7 @@ class Chef
alias upgrade_package install_package
def remove_package(names, versions)
- resolved_names = names.each_with_index.map { |name, i| installed_version(i).to_s unless name.nil? }
+ resolved_names = names.each_with_index.map { |name, i| magical_version(i).to_s unless name.nil? }
dnf(options, "-y", "remove", resolved_names)
flushcache
end
@@ -137,10 +154,10 @@ class Chef
def resolved_package_lock_names(names)
names.each_with_index.map do |name, i|
unless name.nil?
- if installed_version(i).version.nil?
+ if magical_version(i).version.nil?
available_version(i).name
else
- installed_version(i).name
+ magical_version(i).name
end
end
end
@@ -205,14 +222,24 @@ class Chef
end
# @return [Array<Version>]
- def installed_version(index)
- @installed_version ||= []
- @installed_version[index] ||= if new_resource.source
- python_helper.package_query(:whatinstalled, available_version(index).name, arch: safe_arch_array[index], options: options)
- else
- python_helper.package_query(:whatinstalled, package_name_array[index], arch: safe_arch_array[index], options: options)
- end
- @installed_version[index]
+ def magical_version(index)
+ @magical_version ||= []
+ @magical_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], version: safe_version_array[index], arch: safe_arch_array[index], options: options)
+ end
+ @magical_version[index]
+ end
+
+ def current_version(index)
+ @current_version ||= []
+ @current_version[index] ||= if new_resource.source
+ python_helper.package_query(:whatinstalled, available_version(index).name, arch: safe_arch_array[index], options: options)
+ else
+ python_helper.package_query(:whatinstalled, package_name_array[index], arch: safe_arch_array[index], options: options)
+ end
+ @current_version[index]
end
# cache flushing is accomplished by simply restarting the python helper. this produces a roughly
diff --git a/lib/chef/provider/package/dnf/dnf_helper.py b/lib/chef/provider/package/dnf/dnf_helper.py
index f4c031dec0..cfe584a142 100644
--- a/lib/chef/provider/package/dnf/dnf_helper.py
+++ b/lib/chef/provider/package/dnf/dnf_helper.py
@@ -64,7 +64,7 @@ def version_tuple(versionstr):
tmp = versionstr[colon_index + 1:dash_index]
if tmp != '':
v = tmp
- arch_index = versionstr.find('.', dash_index)
+ arch_index = versionstr.rfind('.', dash_index)
if arch_index > 0:
r = versionstr[dash_index + 1:arch_index]
else:
@@ -78,12 +78,12 @@ def version_tuple(versionstr):
def versioncompare(versions):
sack = get_sack()
if (versions[0] is None) or (versions[1] is None):
- outpipe.write('0\n')
- outpipe.flush()
+ outpipe.write('0\n')
+ outpipe.flush()
else:
- evr_comparison = dnf.rpm.rpm.labelCompare(version_tuple(versions[0]), version_tuple(versions[1]))
- outpipe.write('{}\n'.format(evr_comparison))
- outpipe.flush()
+ evr_comparison = dnf.rpm.rpm.labelCompare(version_tuple(versions[0]), version_tuple(versions[1]))
+ outpipe.write('{}\n'.format(evr_comparison))
+ outpipe.flush()
def query(command):
sack = get_sack()
@@ -98,14 +98,27 @@ def query(command):
q = q.available()
if 'epoch' in command:
- q = q.filterm(epoch=int(command['epoch']))
+ # We assume that any glob is "*" so just omit the filter since the dnf libraries have no
+ # epoch__glob filter. That means "?" wildcards in epochs will fail. The workaround is to
+ # not use the version filter here but to put the version with all the globs in the package name.
+ if not dnf.util.is_glob_pattern(command['epoch']):
+ q = q.filterm(epoch=int(command['epoch']))
if 'version' in command:
- q = q.filterm(version__glob=command['version'])
+ if dnf.util.is_glob_pattern(command['version']):
+ q = q.filterm(version__glob=command['version'])
+ else:
+ q = q.filterm(version=command['version'])
if 'release' in command:
- q = q.filterm(release__glob=command['release'])
+ if dnf.util.is_glob_pattern(command['release']):
+ q = q.filterm(release__glob=command['release'])
+ else:
+ q = q.filterm(release=command['release'])
if 'arch' in command:
- q = q.filterm(arch__glob=command['arch'])
+ if dnf.util.is_glob_pattern(command['arch']):
+ q = q.filterm(arch__glob=command['arch'])
+ else:
+ q = q.filterm(arch=command['arch'])
# only apply the default arch query filter if it returns something
archq = q.filter(arch=[ 'noarch', hawkey.detect_arch() ])
@@ -139,22 +152,27 @@ def setup_exit_handler():
signal.signal(signal.SIGQUIT, exit_handler)
if len(sys.argv) < 3:
- inpipe = sys.stdin
- outpipe = sys.stdout
+ inpipe = sys.stdin
+ outpipe = sys.stdout
else:
- inpipe = os.fdopen(int(sys.argv[1]), "r")
- outpipe = os.fdopen(int(sys.argv[2]), "w")
+ os.set_blocking(int(sys.argv[1]), True)
+ inpipe = os.fdopen(int(sys.argv[1]), "r")
+ outpipe = os.fdopen(int(sys.argv[2]), "w")
try:
+ setup_exit_handler()
while 1:
- # kill self if we get orphaned (tragic)
+ # stop the process if the parent proc goes away
ppid = os.getppid()
if ppid == 1:
raise RuntimeError("orphaned")
- setup_exit_handler()
line = inpipe.readline()
+ # only way to detect EOF in python
+ if line == "":
+ break
+
try:
command = json.loads(line)
except ValueError:
@@ -170,4 +188,4 @@ try:
raise RuntimeError("bad command")
finally:
if base is not None:
- base.closeRpmDB()
+ base.close()
diff --git a/lib/chef/provider/package/dnf/python_helper.rb b/lib/chef/provider/package/dnf/python_helper.rb
index 9dfcc7808a..b774c9a269 100644
--- a/lib/chef/provider/package/dnf/python_helper.rb
+++ b/lib/chef/provider/package/dnf/python_helper.rb
@@ -42,17 +42,16 @@ class Chef
def dnf_command
# platform-python is used for system tools on RHEL 8 and is installed under /usr/libexec
@dnf_command ||= begin
- cmd = which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
- shell_out("#{f} -c 'import dnf'").exitstatus == 0
- end
- raise Chef::Exceptions::Package, "cannot find dnf libraries, you may need to use yum_package" unless cmd
+ cmd = which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
+ shell_out("#{f} -c 'import dnf'").exitstatus == 0
+ end
+ raise Chef::Exceptions::Package, "cannot find dnf libraries, you may need to use yum_package" unless cmd
- "#{cmd} #{DNF_HELPER}"
- end
+ "#{cmd} #{DNF_HELPER}"
+ end
end
def start
- ENV["PYTHONUNBUFFERED"] = "1"
@inpipe, inpipe_write = IO.pipe
outpipe_read, @outpipe = IO.pipe
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{dnf_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false)
@@ -75,6 +74,7 @@ class Chef
stderr.close unless stderr.nil?
inpipe.close unless inpipe.nil?
outpipe.close unless outpipe.nil?
+ @stdin = @stdout = @stderr = @inpipe = @outpipe = @wait_thr = nil
end
end
@@ -114,7 +114,7 @@ class Chef
query_output = query(action, parameters)
version = parse_response(query_output.lines.last)
Chef::Log.trace "parsed #{version} from python helper"
- restart unless repo_opts.empty?
+ reap unless repo_opts.empty?
version
end
@@ -148,10 +148,11 @@ class Chef
with_helper do
json = build_query(action, parameters)
Chef::Log.trace "sending '#{json}' to python helper"
- outpipe.syswrite json + "\n"
- output = inpipe.sysread(4096).chomp
+ outpipe.puts json
+ outpipe.flush
+ output = inpipe.readline.chomp
Chef::Log.trace "got '#{output}' from python helper"
- return output
+ output
end
end
@@ -199,13 +200,13 @@ class Chef
Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
end
ret
- rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e
+ rescue => e
output = drain_fds
- if ( max_retries -= 1 ) > 0
+ restart
+ if ( max_retries -= 1 ) > 0 && !ENV["DNF_HELPER_NO_RETRIES"]
unless output.empty?
Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
end
- restart
retry
else
raise e if output.empty?
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index 87acb3a830..077464b5c2 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -42,7 +42,9 @@ class Chef
end
def current_installed_version
- pkg_info = shell_out!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 70])
+ # pkgng up to version 1.15.99.7 returns 70 for pkg not found,
+ # later versions return 1
+ pkg_info = shell_out!("pkg", "info", new_resource.package_name, env: nil, returns: [0, 1, 70])
pkg_info.stdout[/^Version +: (.+)$/, 1]
end
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 3182686c0e..9975010e5b 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -28,13 +28,13 @@ class Chef
provides :package, platform: "gentoo"
provides :portage_package
- PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}.freeze
+ PACKAGE_NAME_PATTERN = %r{^(?:([^/]+)/)?([^/]+)$}.freeze
def load_current_resource
@current_resource = Chef::Resource::PortagePackage.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
- category, pkg = /^#{PACKAGE_NAME_PATTERN}$/.match(new_resource.package_name)[1, 2]
+ category, pkg = PACKAGE_NAME_PATTERN.match(new_resource.package_name)[1, 2]
globsafe_category = category ? Chef::Util::PathHelper.escape_glob_dir(category) : nil
globsafe_pkg = Chef::Util::PathHelper.escape_glob_dir(pkg)
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index 9ac1e7d31b..1a396a364b 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -72,7 +72,6 @@ class Chef
# A rubygems specification object containing the list of gemspecs for all
# available gems in the gem installation.
# Implemented by subclasses
- # For rubygems >= 1.8.0
#
# @return [Gem::Specification]
#
@@ -107,10 +106,8 @@ class Chef
# This isn't sorting before returning because the only code that
# uses this method calls `max_by` so it doesn't need to be sorted.
stubs
- elsif rubygems_version >= Gem::Version.new("1.8.0")
+ else # >= rubygems 1.8 behavior
gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
- else
- gem_source_index.search(gem_dep)
end
end
@@ -192,7 +189,8 @@ class Chef
begin
rs = dependency_installer.resolve_dependencies gem_dependency.name, gem_dependency.requirement
rs.specs.find { |s| s.name == gem_dependency.name }
- rescue Gem::UnsatisfiableDependencyError
+ # ruby-3.0.0 versions of rubygems-3.x throws NoMethodError when the dep is not found
+ rescue Gem::UnsatisfiableDependencyError, NoMethodError
nil
end
end
@@ -423,11 +421,11 @@ class Chef
end
def is_omnibus?
- if %r{/(opscode|chef|chefdk)/embedded/bin}.match?(RbConfig::CONFIG["bindir"])
+ if %r{/(#{ChefUtils::Dist::Org::LEGACY_CONF_DIR}|#{ChefUtils::Dist::Infra::SHORT}|#{ChefUtils::Dist::Workstation::DIR_SUFFIX})/embedded/bin}.match?(RbConfig::CONFIG["bindir"])
logger.trace("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG["bindir"]}")
# Omnibus installs to a static path because of linking on unix, find it.
true
- elsif RbConfig::CONFIG["bindir"].sub(/^\w:/, "") == "/opscode/chef/embedded/bin"
+ elsif RbConfig::CONFIG["bindir"].sub(/^\w:/, "") == "/#{ChefUtils::Dist::Org::LEGACY_CONF_DIR}/#{ChefUtils::Dist::Infra::SHORT}/embedded/bin"
logger.trace("#{new_resource} detected omnibus installation in #{RbConfig::CONFIG["bindir"]}")
# windows, with the drive letter removed
true
@@ -481,9 +479,7 @@ class Chef
end
def all_installed_versions
- @all_installed_versions ||= begin
- @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, ">= 0"))
- end
+ @all_installed_versions ||= @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, ">= 0"))
end
##
@@ -523,13 +519,11 @@ class Chef
end
def candidate_version
- @candidate_version ||= begin
- if source_is_remote?
- @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
- else
- @gem_env.candidate_version_from_file(gem_dependency, new_resource.source).to_s
- end
- end
+ @candidate_version ||= if source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, new_resource.source).to_s
+ end
end
def version_requirement_satisfied?(current_version, new_version)
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index c722d8222c..4350eb6d12 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -70,8 +70,7 @@ class Chef
end
def package_provider
- @package_provider ||= begin
- case installer_type
+ @package_provider ||= case installer_type
when :msi
logger.trace("#{new_resource} is MSI")
require_relative "windows/msi"
@@ -80,8 +79,7 @@ class Chef
logger.trace("#{new_resource} is EXE with type '#{installer_type}'")
require_relative "windows/exe"
Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
- end
- end
+ end
end
def installer_type
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 76b9b15172..121083ea46 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -237,11 +237,8 @@ class Chef
@installed_version[index]
end
- # cache flushing is accomplished by simply restarting the python helper. this produces a roughly
- # 15% hit to the runtime of installing/removing/upgrading packages. correctly using multipackage
- # array installs (and the multipackage cookbook) can produce 600% improvements in runtime.
def flushcache
- python_helper.restart
+ python_helper.close_rpmdb
end
def yum_binary
diff --git a/lib/chef/provider/package/yum/python_helper.rb b/lib/chef/provider/package/yum/python_helper.rb
index 7758383b95..db929ea88b 100644
--- a/lib/chef/provider/package/yum/python_helper.rb
+++ b/lib/chef/provider/package/yum/python_helper.rb
@@ -51,7 +51,6 @@ class Chef
end
def start
- ENV["PYTHONUNBUFFERED"] = "1"
@inpipe, inpipe_write = IO.pipe
outpipe_read, @outpipe = IO.pipe
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3("#{yum_command} #{outpipe_read.fileno} #{inpipe_write.fileno}", outpipe_read.fileno => outpipe_read, inpipe_write.fileno => inpipe_write, close_others: false)
@@ -74,6 +73,7 @@ class Chef
stderr.close unless stderr.nil?
inpipe.close unless inpipe.nil?
outpipe.close unless outpipe.nil?
+ @stdin = @stdout = @stderr = @inpipe = @outpipe = @wait_thr = nil
end
end
@@ -81,6 +81,10 @@ class Chef
start if stdin.nil?
end
+ def close_rpmdb
+ query("close_rpmdb", {})
+ end
+
def compare_versions(version1, version2)
query("versioncompare", { "versions" => [version1, version2] }).to_i
end
@@ -117,12 +121,12 @@ class Chef
parameters = { "provides" => provides, "version" => version, "arch" => arch }
repo_opts = options_params(options || {})
parameters.merge!(repo_opts)
- # XXX: for now we restart before and after every query with an enablerepo/disablerepo to clean the helpers internal state
- restart unless repo_opts.empty?
+ # XXX: for now we close the rpmdb before and after every query with an enablerepo/disablerepo to clean the helpers internal state
+ close_rpmdb unless repo_opts.empty?
query_output = query(action, parameters)
version = parse_response(query_output.lines.last)
Chef::Log.trace "parsed #{version} from python helper"
- restart unless repo_opts.empty?
+ close_rpmdb unless repo_opts.empty?
version
end
@@ -156,10 +160,11 @@ class Chef
with_helper do
json = build_query(action, parameters)
Chef::Log.trace "sending '#{json}' to python helper"
- outpipe.syswrite json + "\n"
- output = inpipe.sysread(4096).chomp
+ outpipe.puts json
+ outpipe.flush
+ output = inpipe.readline.chomp
Chef::Log.trace "got '#{output}' from python helper"
- return output
+ output
end
end
@@ -207,13 +212,13 @@ class Chef
Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
end
ret
- rescue EOFError, Errno::EPIPE, Timeout::Error, Errno::ESRCH => e
+ rescue => e
output = drain_fds
- if ( max_retries -= 1 ) > 0
+ restart
+ if ( max_retries -= 1 ) > 0 && !ENV["YUM_HELPER_NO_RETRIES"]
unless output.empty?
Chef::Log.trace "discarding output on stderr/stdout from python helper: #{output}"
end
- restart
retry
else
raise e if output.empty?
diff --git a/lib/chef/provider/package/yum/simplejson/LICENSE.txt b/lib/chef/provider/package/yum/simplejson/LICENSE.txt
deleted file mode 100644
index e05f49c3fd..0000000000
--- a/lib/chef/provider/package/yum/simplejson/LICENSE.txt
+++ /dev/null
@@ -1,79 +0,0 @@
-simplejson is dual-licensed software. It is available under the terms
-of the MIT license, or the Academic Free License version 2.1. The full
-text of each license agreement is included below. This code is also
-licensed to the Python Software Foundation (PSF) under a Contributor
-Agreement.
-
-MIT License
-===========
-
-Copyright (c) 2006 Bob Ippolito
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-Academic Free License v. 2.1
-============================
-
-Copyright (c) 2006 Bob Ippolito. All rights reserved.
-
-This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work:
-
-Licensed under the Academic Free License version 2.1
-
-1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following:
-
-a) to reproduce the Original Work in copies;
-
-b) to prepare derivative works ("Derivative Works") based upon the Original Work;
-
-c) to distribute copies of the Original Work and Derivative Works to the public;
-
-d) to perform the Original Work publicly; and
-
-e) to display the Original Work publicly.
-
-2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works.
-
-3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work.
-
-4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license.
-
-5) This section intentionally omitted.
-
-6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
-7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer.
-
-8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
-
-9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions.
-
-10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
-11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License.
-
-12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
-13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
-14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
-15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
-This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner.
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.py b/lib/chef/provider/package/yum/simplejson/__init__.py
deleted file mode 100644
index d5b4d39913..0000000000
--- a/lib/chef/provider/package/yum/simplejson/__init__.py
+++ /dev/null
@@ -1,318 +0,0 @@
-r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
-JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
-interchange format.
-
-:mod:`simplejson` exposes an API familiar to users of the standard library
-:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
-version of the :mod:`json` library contained in Python 2.6, but maintains
-compatibility with Python 2.4 and Python 2.5 and (currently) has
-significant performance advantages, even without using the optional C
-extension for speedups.
-
-Encoding basic Python object hierarchies::
-
- >>> import simplejson as json
- >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
- '["foo", {"bar": ["baz", null, 1.0, 2]}]'
- >>> print json.dumps("\"foo\bar")
- "\"foo\bar"
- >>> print json.dumps(u'\u1234')
- "\u1234"
- >>> print json.dumps('\\')
- "\\"
- >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
- {"a": 0, "b": 0, "c": 0}
- >>> from StringIO import StringIO
- >>> io = StringIO()
- >>> json.dump(['streaming API'], io)
- >>> io.getvalue()
- '["streaming API"]'
-
-Compact encoding::
-
- >>> import simplejson as json
- >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
- '[1,2,3,{"4":5,"6":7}]'
-
-Pretty printing::
-
- >>> import simplejson as json
- >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
- >>> print '\n'.join([l.rstrip() for l in s.splitlines()])
- {
- "4": 5,
- "6": 7
- }
-
-Decoding JSON::
-
- >>> import simplejson as json
- >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
- >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
- True
- >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
- True
- >>> from StringIO import StringIO
- >>> io = StringIO('["streaming API"]')
- >>> json.load(io)[0] == 'streaming API'
- True
-
-Specializing JSON object decoding::
-
- >>> import simplejson as json
- >>> def as_complex(dct):
- ... if '__complex__' in dct:
- ... return complex(dct['real'], dct['imag'])
- ... return dct
- ...
- >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
- ... object_hook=as_complex)
- (1+2j)
- >>> import decimal
- >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
- True
-
-Specializing JSON object encoding::
-
- >>> import simplejson as json
- >>> def encode_complex(obj):
- ... if isinstance(obj, complex):
- ... return [obj.real, obj.imag]
- ... raise TypeError(repr(o) + " is not JSON serializable")
- ...
- >>> json.dumps(2 + 1j, default=encode_complex)
- '[2.0, 1.0]'
- >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
- '[2.0, 1.0]'
- >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
- '[2.0, 1.0]'
-
-
-Using simplejson.tool from the shell to validate and pretty-print::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-"""
-__version__ = '2.0.9'
-__all__ = [
- 'dump', 'dumps', 'load', 'loads',
- 'JSONDecoder', 'JSONEncoder',
-]
-
-__author__ = 'Bob Ippolito <bob@redivi.com>'
-
-from decoder import JSONDecoder
-from encoder import JSONEncoder
-
-_default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
-)
-
-def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, **kw):
- """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
- ``.write()``-supporting file-like object).
-
- If ``skipkeys`` is true then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the some chunks written to ``fp``
- may be ``unicode`` instances, subject to normal Python ``str`` to
- ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
- understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
- to cause an error.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
- in strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If ``indent`` is a non-negative integer, then JSON array elements and object
- members will be pretty-printed with that indent level. An indent level
- of 0 will only insert newlines. ``None`` is the most compact representation.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and not kw):
- iterable = _default_encoder.iterencode(obj)
- else:
- if cls is None:
- cls = JSONEncoder
- iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding,
- default=default, **kw).iterencode(obj)
- # could accelerate with writelines in some versions of Python, at
- # a debuggability cost
- for chunk in iterable:
- fp.write(chunk)
-
-
-def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, **kw):
- """Serialize ``obj`` to a JSON formatted ``str``.
-
- If ``skipkeys`` is false then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the return value will be a
- ``unicode`` instance subject to normal Python ``str`` to ``unicode``
- coercion rules instead of being escaped to an ASCII ``str``.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
- strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If ``indent`` is a non-negative integer, then JSON array elements and
- object members will be pretty-printed with that indent level. An indent
- level of 0 will only insert newlines. ``None`` is the most compact
- representation.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and not kw):
- return _default_encoder.encode(obj)
- if cls is None:
- cls = JSONEncoder
- return cls(
- skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding, default=default,
- **kw).encode(obj)
-
-
-_default_decoder = JSONDecoder(encoding=None, object_hook=None)
-
-
-def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, **kw):
- """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
- a JSON document) to a Python object.
-
- If the contents of ``fp`` is encoded with an ASCII based encoding other
- than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
- be specified. Encodings that are not ASCII based (such as UCS-2) are
- not allowed, and should be wrapped with
- ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
- object and passed to ``loads()``
-
- ``object_hook`` is an optional function that will be called with the
- result of any object literal decode (a ``dict``). The return value of
- ``object_hook`` will be used instead of the ``dict``. This feature
- can be used to implement custom decoders (e.g. JSON-RPC class hinting).
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- return loads(fp.read(),
- encoding=encoding, cls=cls, object_hook=object_hook,
- parse_float=parse_float, parse_int=parse_int,
- parse_constant=parse_constant, **kw)
-
-
-def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, **kw):
- """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
- document) to a Python object.
-
- If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
- other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
- must be specified. Encodings that are not ASCII based (such as UCS-2)
- are not allowed and should be decoded to ``unicode`` first.
-
- ``object_hook`` is an optional function that will be called with the
- result of any object literal decode (a ``dict``). The return value of
- ``object_hook`` will be used instead of the ``dict``. This feature
- can be used to implement custom decoders (e.g. JSON-RPC class hinting).
-
- ``parse_float``, if specified, will be called with the string
- of every JSON float to be decoded. By default this is equivalent to
- float(num_str). This can be used to use another datatype or parser
- for JSON floats (e.g. decimal.Decimal).
-
- ``parse_int``, if specified, will be called with the string
- of every JSON int to be decoded. By default this is equivalent to
- int(num_str). This can be used to use another datatype or parser
- for JSON integers (e.g. float).
-
- ``parse_constant``, if specified, will be called with one of the
- following strings: -Infinity, Infinity, NaN, null, true, false.
- This can be used to raise an exception if invalid JSON numbers
- are encountered.
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- if (cls is None and encoding is None and object_hook is None and
- parse_int is None and parse_float is None and
- parse_constant is None and not kw):
- return _default_decoder.decode(s)
- if cls is None:
- cls = JSONDecoder
- if object_hook is not None:
- kw['object_hook'] = object_hook
- if parse_float is not None:
- kw['parse_float'] = parse_float
- if parse_int is not None:
- kw['parse_int'] = parse_int
- if parse_constant is not None:
- kw['parse_constant'] = parse_constant
- return cls(encoding=encoding, **kw).decode(s)
diff --git a/lib/chef/provider/package/yum/simplejson/__init__.pyc b/lib/chef/provider/package/yum/simplejson/__init__.pyc
deleted file mode 100644
index 10679d3b04..0000000000
--- a/lib/chef/provider/package/yum/simplejson/__init__.pyc
+++ /dev/null
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.py b/lib/chef/provider/package/yum/simplejson/decoder.py
deleted file mode 100644
index d921ce0b97..0000000000
--- a/lib/chef/provider/package/yum/simplejson/decoder.py
+++ /dev/null
@@ -1,354 +0,0 @@
-"""Implementation of JSONDecoder
-"""
-import re
-import sys
-import struct
-
-from simplejson.scanner import make_scanner
-try:
- from simplejson._speedups import scanstring as c_scanstring
-except ImportError:
- c_scanstring = None
-
-__all__ = ['JSONDecoder']
-
-FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
-
-def _floatconstants():
- _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
- if sys.byteorder != 'big':
- _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
- nan, inf = struct.unpack('dd', _BYTES)
- return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
-
-
-def linecol(doc, pos):
- lineno = doc.count('\n', 0, pos) + 1
- if lineno == 1:
- colno = pos
- else:
- colno = pos - doc.rindex('\n', 0, pos)
- return lineno, colno
-
-
-def errmsg(msg, doc, pos, end=None):
- # Note that this function is called from _speedups
- lineno, colno = linecol(doc, pos)
- if end is None:
- #fmt = '{0}: line {1} column {2} (char {3})'
- #return fmt.format(msg, lineno, colno, pos)
- fmt = '%s: line %d column %d (char %d)'
- return fmt % (msg, lineno, colno, pos)
- endlineno, endcolno = linecol(doc, end)
- #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
- #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
- fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
- return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
-
-
-_CONSTANTS = {
- '-Infinity': NegInf,
- 'Infinity': PosInf,
- 'NaN': NaN,
-}
-
-STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
-BACKSLASH = {
- '"': u'"', '\\': u'\\', '/': u'/',
- 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
-}
-
-DEFAULT_ENCODING = "utf-8"
-
-def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
- """Scan the string s for a JSON string. End is the index of the
- character in s after the quote that started the JSON string.
- Unescapes all valid JSON string escape sequences and raises ValueError
- on attempt to decode an invalid string. If strict is False then literal
- control characters are allowed in the string.
-
- Returns a tuple of the decoded string and the index of the character in s
- after the end quote."""
- if encoding is None:
- encoding = DEFAULT_ENCODING
- chunks = []
- _append = chunks.append
- begin = end - 1
- while 1:
- chunk = _m(s, end)
- if chunk is None:
- raise ValueError(
- errmsg("Unterminated string starting at", s, begin))
- end = chunk.end()
- content, terminator = chunk.groups()
- # Content is contains zero or more unescaped string characters
- if content:
- if not isinstance(content, unicode):
- content = unicode(content, encoding)
- _append(content)
- # Terminator is the end of string, a literal control character,
- # or a backslash denoting that an escape sequence follows
- if terminator == '"':
- break
- elif terminator != '\\':
- if strict:
- msg = "Invalid control character %r at" % (terminator,)
- #msg = "Invalid control character {0!r} at".format(terminator)
- raise ValueError(errmsg(msg, s, end))
- else:
- _append(terminator)
- continue
- try:
- esc = s[end]
- except IndexError:
- raise ValueError(
- errmsg("Unterminated string starting at", s, begin))
- # If not a unicode escape sequence, must be in the lookup table
- if esc != 'u':
- try:
- char = _b[esc]
- except KeyError:
- msg = "Invalid \\escape: " + repr(esc)
- raise ValueError(errmsg(msg, s, end))
- end += 1
- else:
- # Unicode escape sequence
- esc = s[end + 1:end + 5]
- next_end = end + 5
- if len(esc) != 4:
- msg = "Invalid \\uXXXX escape"
- raise ValueError(errmsg(msg, s, end))
- uni = int(esc, 16)
- # Check for surrogate pair on UCS-4 systems
- if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
- msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
- if not s[end + 5:end + 7] == '\\u':
- raise ValueError(errmsg(msg, s, end))
- esc2 = s[end + 7:end + 11]
- if len(esc2) != 4:
- raise ValueError(errmsg(msg, s, end))
- uni2 = int(esc2, 16)
- uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
- next_end += 6
- char = unichr(uni)
- end = next_end
- # Append the unescaped character
- _append(char)
- return u''.join(chunks), end
-
-
-# Use speedup if available
-scanstring = c_scanstring or py_scanstring
-
-WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
-WHITESPACE_STR = ' \t\n\r'
-
-def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- pairs = {}
- # Use a slice to prevent IndexError from being raised, the following
- # check will raise a more specific ValueError if the string is empty
- nextchar = s[end:end + 1]
- # Normally we expect nextchar == '"'
- if nextchar != '"':
- if nextchar in _ws:
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- # Trivial empty object
- if nextchar == '}':
- return pairs, end + 1
- elif nextchar != '"':
- raise ValueError(errmsg("Expecting property name", s, end))
- end += 1
- while True:
- key, end = scanstring(s, end, encoding, strict)
-
- # To skip some function call overhead we optimize the fast paths where
- # the JSON key separator is ": " or just ":".
- if s[end:end + 1] != ':':
- end = _w(s, end).end()
- if s[end:end + 1] != ':':
- raise ValueError(errmsg("Expecting : delimiter", s, end))
-
- end += 1
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
- pairs[key] = value
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
- end += 1
-
- if nextchar == '}':
- break
- elif nextchar != ',':
- raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end += 1
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
-
- end += 1
- if nextchar != '"':
- raise ValueError(errmsg("Expecting property name", s, end - 1))
-
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end
-
-def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- values = []
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- # Look-ahead for trivial empty array
- if nextchar == ']':
- return values, end + 1
- _append = values.append
- while True:
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise ValueError(errmsg("Expecting object", s, end))
- _append(value)
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar == ']':
- break
- elif nextchar != ',':
- raise ValueError(errmsg("Expecting , delimiter", s, end))
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- return values, end
-
-class JSONDecoder(object):
- """Simple JSON <http://json.org> decoder
-
- Performs the following translations in decoding by default:
-
- +---------------+-------------------+
- | JSON | Python |
- +===============+===================+
- | object | dict |
- +---------------+-------------------+
- | array | list |
- +---------------+-------------------+
- | string | unicode |
- +---------------+-------------------+
- | number (int) | int, long |
- +---------------+-------------------+
- | number (real) | float |
- +---------------+-------------------+
- | true | True |
- +---------------+-------------------+
- | false | False |
- +---------------+-------------------+
- | null | None |
- +---------------+-------------------+
-
- It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
- their corresponding ``float`` values, which is outside the JSON spec.
-
- """
-
- def __init__(self, encoding=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, strict=True):
- """``encoding`` determines the encoding used to interpret any ``str``
- objects decoded by this instance (utf-8 by default). It has no
- effect when decoding ``unicode`` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as ``unicode``.
-
- ``object_hook``, if specified, will be called with the result
- of every JSON object decoded and its return value will be used in
- place of the given ``dict``. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- ``parse_float``, if specified, will be called with the string
- of every JSON float to be decoded. By default this is equivalent to
- float(num_str). This can be used to use another datatype or parser
- for JSON floats (e.g. decimal.Decimal).
-
- ``parse_int``, if specified, will be called with the string
- of every JSON int to be decoded. By default this is equivalent to
- int(num_str). This can be used to use another datatype or parser
- for JSON integers (e.g. float).
-
- ``parse_constant``, if specified, will be called with one of the
- following strings: -Infinity, Infinity, NaN.
- This can be used to raise an exception if invalid JSON numbers
- are encountered.
-
- """
- self.encoding = encoding
- self.object_hook = object_hook
- self.parse_float = parse_float or float
- self.parse_int = parse_int or int
- self.parse_constant = parse_constant or _CONSTANTS.__getitem__
- self.strict = strict
- self.parse_object = JSONObject
- self.parse_array = JSONArray
- self.parse_string = scanstring
- self.scan_once = make_scanner(self)
-
- def decode(self, s, _w=WHITESPACE.match):
- """Return the Python representation of ``s`` (a ``str`` or ``unicode``
- instance containing a JSON document)
-
- """
- obj, end = self.raw_decode(s, idx=_w(s, 0).end())
- end = _w(s, end).end()
- if end != len(s):
- raise ValueError(errmsg("Extra data", s, end, len(s)))
- return obj
-
- def raw_decode(self, s, idx=0):
- """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
- with a JSON document) and return a 2-tuple of the Python
- representation and the index in ``s`` where the document ended.
-
- This can be used to decode a JSON document from a string that may
- have extraneous data at the end.
-
- """
- try:
- obj, end = self.scan_once(s, idx)
- except StopIteration:
- raise ValueError("No JSON object could be decoded")
- return obj, end
diff --git a/lib/chef/provider/package/yum/simplejson/decoder.pyc b/lib/chef/provider/package/yum/simplejson/decoder.pyc
deleted file mode 100644
index d402901870..0000000000
--- a/lib/chef/provider/package/yum/simplejson/decoder.pyc
+++ /dev/null
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.py b/lib/chef/provider/package/yum/simplejson/encoder.py
deleted file mode 100644
index cf58290366..0000000000
--- a/lib/chef/provider/package/yum/simplejson/encoder.py
+++ /dev/null
@@ -1,440 +0,0 @@
-"""Implementation of JSONEncoder
-"""
-import re
-
-try:
- from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
-except ImportError:
- c_encode_basestring_ascii = None
-try:
- from simplejson._speedups import make_encoder as c_make_encoder
-except ImportError:
- c_make_encoder = None
-
-ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
-ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
-HAS_UTF8 = re.compile(r'[\x80-\xff]')
-ESCAPE_DCT = {
- '\\': '\\\\',
- '"': '\\"',
- '\b': '\\b',
- '\f': '\\f',
- '\n': '\\n',
- '\r': '\\r',
- '\t': '\\t',
-}
-for i in range(0x20):
- #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
- ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
-
-# Assume this produces an infinity on all machines (probably not guaranteed)
-INFINITY = float('1e66666')
-FLOAT_REPR = repr
-
-def encode_basestring(s):
- """Return a JSON representation of a Python string
-
- """
- def replace(match):
- return ESCAPE_DCT[match.group(0)]
- return '"' + ESCAPE.sub(replace, s) + '"'
-
-
-def py_encode_basestring_ascii(s):
- """Return an ASCII-only JSON representation of a Python string
-
- """
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- s = match.group(0)
- try:
- return ESCAPE_DCT[s]
- except KeyError:
- n = ord(s)
- if n < 0x10000:
- #return '\\u{0:04x}'.format(n)
- return '\\u%04x' % (n,)
- else:
- # surrogate pair
- n -= 0x10000
- s1 = 0xd800 | ((n >> 10) & 0x3ff)
- s2 = 0xdc00 | (n & 0x3ff)
- #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
- return '\\u%04x\\u%04x' % (s1, s2)
- return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
-
-
-encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
-
-class JSONEncoder(object):
- """Extensible JSON <http://json.org> encoder for Python data structures.
-
- Supports the following objects and types by default:
-
- +-------------------+---------------+
- | Python | JSON |
- +===================+===============+
- | dict | object |
- +-------------------+---------------+
- | list, tuple | array |
- +-------------------+---------------+
- | str, unicode | string |
- +-------------------+---------------+
- | int, long, float | number |
- +-------------------+---------------+
- | True | true |
- +-------------------+---------------+
- | False | false |
- +-------------------+---------------+
- | None | null |
- +-------------------+---------------+
-
- To extend this to recognize other objects, subclass and implement a
- ``.default()`` method with another method that returns a serializable
- object for ``o`` if possible, otherwise it should call the superclass
- implementation (to raise ``TypeError``).
-
- """
- item_separator = ', '
- key_separator = ': '
- def __init__(self, skipkeys=False, ensure_ascii=True,
- check_circular=True, allow_nan=True, sort_keys=False,
- indent=None, separators=None, encoding='utf-8', default=None):
- """Constructor for JSONEncoder, with sensible defaults.
-
- If skipkeys is false, then it is a TypeError to attempt
- encoding of keys that are not str, int, long, float or None. If
- skipkeys is True, such items are simply skipped.
-
- If ensure_ascii is true, the output is guaranteed to be str
- objects with all incoming unicode characters escaped. If
- ensure_ascii is false, the output will be unicode object.
-
- If check_circular is true, then lists, dicts, and custom encoded
- objects will be checked for circular references during encoding to
- prevent an infinite recursion (which would cause an OverflowError).
- Otherwise, no such check takes place.
-
- If allow_nan is true, then NaN, Infinity, and -Infinity will be
- encoded as such. This behavior is not JSON specification compliant,
- but is consistent with most JavaScript based encoders and decoders.
- Otherwise, it will be a ValueError to encode such floats.
-
- If sort_keys is true, then the output of dictionaries will be
- sorted by key; this is useful for regression tests to ensure
- that JSON serializations can be compared on a day-to-day basis.
-
- If indent is a non-negative integer, then JSON array
- elements and object members will be pretty-printed with that
- indent level. An indent level of 0 will only insert newlines.
- None is the most compact representation.
-
- If specified, separators should be a (item_separator, key_separator)
- tuple. The default is (', ', ': '). To get the most compact JSON
- representation you should specify (',', ':') to eliminate whitespace.
-
- If specified, default is a function that gets called for objects
- that can't otherwise be serialized. It should return a JSON encodable
- version of the object or raise a ``TypeError``.
-
- If encoding is not None, then all input strings will be
- transformed into unicode using that encoding prior to JSON-encoding.
- The default is UTF-8.
-
- """
-
- self.skipkeys = skipkeys
- self.ensure_ascii = ensure_ascii
- self.check_circular = check_circular
- self.allow_nan = allow_nan
- self.sort_keys = sort_keys
- self.indent = indent
- if separators is not None:
- self.item_separator, self.key_separator = separators
- if default is not None:
- self.default = default
- self.encoding = encoding
-
- def default(self, o):
- """Implement this method in a subclass such that it returns
- a serializable object for ``o``, or calls the base implementation
- (to raise a ``TypeError``).
-
- For example, to support arbitrary iterators, you could
- implement default like this::
-
- def default(self, o):
- try:
- iterable = iter(o)
- except TypeError:
- pass
- else:
- return list(iterable)
- return JSONEncoder.default(self, o)
-
- """
- raise TypeError(repr(o) + " is not JSON serializable")
-
- def encode(self, o):
- """Return a JSON string representation of a Python data structure.
-
- >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
- '{"foo": ["bar", "baz"]}'
-
- """
- # This is for extremely simple cases and benchmarks.
- if isinstance(o, basestring):
- if isinstance(o, str):
- _encoding = self.encoding
- if (_encoding is not None
- and not (_encoding == 'utf-8')):
- o = o.decode(_encoding)
- if self.ensure_ascii:
- return encode_basestring_ascii(o)
- else:
- return encode_basestring(o)
- # This doesn't pass the iterator directly to ''.join() because the
- # exceptions aren't as detailed. The list call should be roughly
- # equivalent to the PySequence_Fast that ''.join() would do.
- chunks = self.iterencode(o, _one_shot=True)
- if not isinstance(chunks, (list, tuple)):
- chunks = list(chunks)
- return ''.join(chunks)
-
- def iterencode(self, o, _one_shot=False):
- """Encode the given object and yield each string
- representation as available.
-
- For example::
-
- for chunk in JSONEncoder().iterencode(bigobject):
- mysocket.write(chunk)
-
- """
- if self.check_circular:
- markers = {}
- else:
- markers = None
- if self.ensure_ascii:
- _encoder = encode_basestring_ascii
- else:
- _encoder = encode_basestring
- if self.encoding != 'utf-8':
- def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
- if isinstance(o, str):
- o = o.decode(_encoding)
- return _orig_encoder(o)
-
- def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
- # Check for specials. Note that this type of test is processor- and/or
- # platform-specific, so do tests which don't depend on the internals.
-
- if o != o:
- text = 'NaN'
- elif o == _inf:
- text = 'Infinity'
- elif o == _neginf:
- text = '-Infinity'
- else:
- return _repr(o)
-
- if not allow_nan:
- raise ValueError(
- "Out of range float values are not JSON compliant: " +
- repr(o))
-
- return text
-
-
- if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
- _iterencode = c_make_encoder(
- markers, self.default, _encoder, self.indent,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, self.allow_nan)
- else:
- _iterencode = _make_iterencode(
- markers, self.default, _encoder, self.indent, floatstr,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, _one_shot)
- return _iterencode(o, 0)
-
-def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
- ## HACK: hand-optimized bytecode; turn globals into locals
- False=False,
- True=True,
- ValueError=ValueError,
- basestring=basestring,
- dict=dict,
- float=float,
- id=id,
- int=int,
- isinstance=isinstance,
- list=list,
- long=long,
- str=str,
- tuple=tuple,
- ):
-
- def _iterencode_list(lst, _current_indent_level):
- if not lst:
- yield '[]'
- return
- if markers is not None:
- markerid = id(lst)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = lst
- buf = '['
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
- separator = _item_separator + newline_indent
- buf += newline_indent
- else:
- newline_indent = None
- separator = _item_separator
- first = True
- for value in lst:
- if first:
- first = False
- else:
- buf = separator
- if isinstance(value, basestring):
- yield buf + _encoder(value)
- elif value is None:
- yield buf + 'null'
- elif value is True:
- yield buf + 'true'
- elif value is False:
- yield buf + 'false'
- elif isinstance(value, (int, long)):
- yield buf + str(value)
- elif isinstance(value, float):
- yield buf + _floatstr(value)
- else:
- yield buf
- if isinstance(value, (list, tuple)):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (' ' * (_indent * _current_indent_level))
- yield ']'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode_dict(dct, _current_indent_level):
- if not dct:
- yield '{}'
- return
- if markers is not None:
- markerid = id(dct)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = dct
- yield '{'
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
- item_separator = _item_separator + newline_indent
- yield newline_indent
- else:
- newline_indent = None
- item_separator = _item_separator
- first = True
- if _sort_keys:
- items = dct.items()
- items.sort(key=lambda kv: kv[0])
- else:
- items = dct.iteritems()
- for key, value in items:
- if isinstance(key, basestring):
- pass
- # JavaScript is weakly typed for these, so it makes sense to
- # also allow them. Many encoders seem to do something like this.
- elif isinstance(key, float):
- key = _floatstr(key)
- elif key is True:
- key = 'true'
- elif key is False:
- key = 'false'
- elif key is None:
- key = 'null'
- elif isinstance(key, (int, long)):
- key = str(key)
- elif _skipkeys:
- continue
- else:
- raise TypeError("key " + repr(key) + " is not a string")
- if first:
- first = False
- else:
- yield item_separator
- yield _encoder(key)
- yield _key_separator
- if isinstance(value, basestring):
- yield _encoder(value)
- elif value is None:
- yield 'null'
- elif value is True:
- yield 'true'
- elif value is False:
- yield 'false'
- elif isinstance(value, (int, long)):
- yield str(value)
- elif isinstance(value, float):
- yield _floatstr(value)
- else:
- if isinstance(value, (list, tuple)):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (' ' * (_indent * _current_indent_level))
- yield '}'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode(o, _current_indent_level):
- if isinstance(o, basestring):
- yield _encoder(o)
- elif o is None:
- yield 'null'
- elif o is True:
- yield 'true'
- elif o is False:
- yield 'false'
- elif isinstance(o, (int, long)):
- yield str(o)
- elif isinstance(o, float):
- yield _floatstr(o)
- elif isinstance(o, (list, tuple)):
- for chunk in _iterencode_list(o, _current_indent_level):
- yield chunk
- elif isinstance(o, dict):
- for chunk in _iterencode_dict(o, _current_indent_level):
- yield chunk
- else:
- if markers is not None:
- markerid = id(o)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = o
- o = _default(o)
- for chunk in _iterencode(o, _current_indent_level):
- yield chunk
- if markers is not None:
- del markers[markerid]
-
- return _iterencode
diff --git a/lib/chef/provider/package/yum/simplejson/encoder.pyc b/lib/chef/provider/package/yum/simplejson/encoder.pyc
deleted file mode 100644
index 207bce5cfb..0000000000
--- a/lib/chef/provider/package/yum/simplejson/encoder.pyc
+++ /dev/null
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.py b/lib/chef/provider/package/yum/simplejson/scanner.py
deleted file mode 100644
index adbc6ec979..0000000000
--- a/lib/chef/provider/package/yum/simplejson/scanner.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""JSON token scanner
-"""
-import re
-try:
- from simplejson._speedups import make_scanner as c_make_scanner
-except ImportError:
- c_make_scanner = None
-
-__all__ = ['make_scanner']
-
-NUMBER_RE = re.compile(
- r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
- (re.VERBOSE | re.MULTILINE | re.DOTALL))
-
-def py_make_scanner(context):
- parse_object = context.parse_object
- parse_array = context.parse_array
- parse_string = context.parse_string
- match_number = NUMBER_RE.match
- encoding = context.encoding
- strict = context.strict
- parse_float = context.parse_float
- parse_int = context.parse_int
- parse_constant = context.parse_constant
- object_hook = context.object_hook
-
- def _scan_once(string, idx):
- try:
- nextchar = string[idx]
- except IndexError:
- raise StopIteration
-
- if nextchar == '"':
- return parse_string(string, idx + 1, encoding, strict)
- elif nextchar == '{':
- return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
- elif nextchar == '[':
- return parse_array((string, idx + 1), _scan_once)
- elif nextchar == 'n' and string[idx:idx + 4] == 'null':
- return None, idx + 4
- elif nextchar == 't' and string[idx:idx + 4] == 'true':
- return True, idx + 4
- elif nextchar == 'f' and string[idx:idx + 5] == 'false':
- return False, idx + 5
-
- m = match_number(string, idx)
- if m is not None:
- integer, frac, exp = m.groups()
- if frac or exp:
- res = parse_float(integer + (frac or '') + (exp or ''))
- else:
- res = parse_int(integer)
- return res, m.end()
- elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
- return parse_constant('NaN'), idx + 3
- elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
- return parse_constant('Infinity'), idx + 8
- elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
- return parse_constant('-Infinity'), idx + 9
- else:
- raise StopIteration
-
- return _scan_once
-
-make_scanner = c_make_scanner or py_make_scanner
diff --git a/lib/chef/provider/package/yum/simplejson/scanner.pyc b/lib/chef/provider/package/yum/simplejson/scanner.pyc
deleted file mode 100644
index 12df070e44..0000000000
--- a/lib/chef/provider/package/yum/simplejson/scanner.pyc
+++ /dev/null
Binary files differ
diff --git a/lib/chef/provider/package/yum/simplejson/tool.py b/lib/chef/provider/package/yum/simplejson/tool.py
deleted file mode 100644
index 90443317b2..0000000000
--- a/lib/chef/provider/package/yum/simplejson/tool.py
+++ /dev/null
@@ -1,37 +0,0 @@
-r"""Command-line tool to validate and pretty-print JSON
-
-Usage::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-
-"""
-import sys
-import simplejson
-
-def main():
- if len(sys.argv) == 1:
- infile = sys.stdin
- outfile = sys.stdout
- elif len(sys.argv) == 2:
- infile = open(sys.argv[1], 'rb')
- outfile = sys.stdout
- elif len(sys.argv) == 3:
- infile = open(sys.argv[1], 'rb')
- outfile = open(sys.argv[2], 'wb')
- else:
- raise SystemExit(sys.argv[0] + " [infile [outfile]]")
- try:
- obj = simplejson.load(infile)
- except ValueError, e:
- raise SystemExit(e)
- simplejson.dump(obj, outfile, sort_keys=True, indent=4)
- outfile.write('\n')
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/chef/provider/package/yum/yum_helper.py b/lib/chef/provider/package/yum/yum_helper.py
index 47cbe2efe6..4dfe0cb24b 100644
--- a/lib/chef/provider/package/yum/yum_helper.py
+++ b/lib/chef/provider/package/yum/yum_helper.py
@@ -2,45 +2,26 @@
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
#
-# NOTE: this actually needs to run under python2.4 and centos 5.x through python3 and centos 7.x
-# please manually test changes on centos5 boxes or you will almost certainly break things.
+# NOTE: this actually needs to run under python2.7 and centos 6.x through python3 and centos 7.x
+# please manually test changes on centos6 boxes or you will almost certainly break things.
#
import sys
import yum
import signal
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'simplejson'))
-try: import json
-except ImportError: import simplejson as json
+import fcntl
+import json
import re
from rpmUtils.miscutils import stringToVersion,compareEVR
from rpmUtils.arch import getBaseArch, getArchList
-
-
-try: from yum.misc import string_to_prco_tuple
-except ImportError:
- # RHEL5 compat
- def string_to_prco_tuple(prcoString):
- prco_split = prcoString.split()
- n, f, v = prco_split
- (prco_e, prco_v, prco_r) = stringToVersion(v)
- return (n, f, (prco_e, prco_v, prco_r))
+from yum.misc import string_to_prco_tuple
# hack to work around https://github.com/chef/chef/issues/7126
# see https://bugzilla.redhat.com/show_bug.cgi?id=1396248
if not hasattr(yum.packages.FakeRepository, 'compare_providers_priority'):
yum.packages.FakeRepository.compare_providers_priority = 99
-base = None
-
-def get_base():
- global base
- if base is None:
- base = yum.YumBase()
- setup_exit_handler()
- return base
-
def versioncompare(versions):
arch_list = getArchList()
candidate_arch1 = versions[0].split(".")[-1]
@@ -51,9 +32,9 @@ def versioncompare(versions):
# then we'll chop the arch component (assuming it *is* a valid one) from the first version string
# so we're only comparing the evr portions.
if (candidate_arch2 not in arch_list) and (candidate_arch1 in arch_list):
- final_version1 = versions[0].replace("." + candidate_arch1,"")
+ final_version1 = versions[0].replace("." + candidate_arch1,"")
else:
- final_version1 = versions[0]
+ final_version1 = versions[0]
final_version2 = versions[1]
@@ -64,37 +45,24 @@ def versioncompare(versions):
outpipe.write("%(e)s\n" % { 'e': evr_comparison })
outpipe.flush()
-def install_only_packages(name):
- base = get_base()
+def install_only_packages(base, name):
if name in base.conf.installonlypkgs:
- outpipe.write('True')
+ outpipe.write('True\n')
else:
- outpipe.write('False')
+ outpipe.write('False\n')
outpipe.flush()
-# python2.4 / centos5 compat
-try:
- any
-except NameError:
- def any(s):
- for v in s:
- if v:
- return True
- return False
-
-def query(command):
- base = get_base()
-
+def query(base, command):
enabled_repos = base.repos.listEnabled()
# Handle any repocontrols passed in with our options
if 'repos' in command:
- for repo in command['repos']:
- if 'enable' in repo:
- base.repos.enableRepo(repo['enable'])
+ for repo in command['repos']:
+ if 'enable' in repo:
+ base.repos.enableRepo(repo['enable'])
if 'disable' in repo:
- base.repos.disableRepo(repo['disable'])
+ base.repos.disableRepo(repo['disable'])
args = { 'name': command['provides'] }
do_nevra = False
@@ -136,7 +104,7 @@ def query(command):
# returnPackages and searchProvides and then apply the Nevra filters to those results.
pkgs = obj.searchNevra(**args)
if (command['action'] == "whatinstalled") and (not pkgs):
- pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
+ pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
else:
pats = [command['provides']]
pkgs = obj.returnPackages(patterns=pats)
@@ -157,13 +125,13 @@ def query(command):
# Reset any repos we were passed in enablerepo/disablerepo to the original state in enabled_repos
if 'repos' in command:
- for repo in command['repos']:
- if 'enable' in repo:
- if base.repos.getRepo(repo['enable']) not in enabled_repos:
- base.repos.disableRepo(repo['enable'])
+ for repo in command['repos']:
+ if 'enable' in repo:
+ if base.repos.getRepo(repo['enable']) not in enabled_repos:
+ base.repos.disableRepo(repo['enable'])
if 'disable' in repo:
- if base.repos.getRepo(repo['disable']) in enabled_repos:
- base.repos.enableRepo(repo['disable'])
+ if base.repos.getRepo(repo['disable']) in enabled_repos:
+ base.repos.enableRepo(repo['disable'])
# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
# to keep process tables clean. additional error handling should probably be added to the retry loop
@@ -179,36 +147,56 @@ def setup_exit_handler():
signal.signal(signal.SIGPIPE, exit_handler)
signal.signal(signal.SIGQUIT, exit_handler)
+def set_blocking(fd):
+ old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, old_flags & ~os.O_NONBLOCK)
+
+base = None
+
if len(sys.argv) < 3:
- inpipe = sys.stdin
- outpipe = sys.stdout
+ inpipe = sys.stdin
+ outpipe = sys.stdout
else:
- inpipe = os.fdopen(int(sys.argv[1]), "r")
- outpipe = os.fdopen(int(sys.argv[2]), "w")
+ set_blocking(int(sys.argv[1]))
+ set_blocking(int(sys.argv[2]))
+ inpipe = os.fdopen(int(sys.argv[1]), "r")
+ outpipe = os.fdopen(int(sys.argv[2]), "w")
try:
+ setup_exit_handler()
while 1:
- # kill self if we get orphaned (tragic)
+ # stop the process if the parent proc goes away
ppid = os.getppid()
if ppid == 1:
raise RuntimeError("orphaned")
- setup_exit_handler()
line = inpipe.readline()
+ # only way to detect EOF in python
+ if line == "":
+ break
+
try:
command = json.loads(line)
except ValueError, e:
raise RuntimeError("bad json parse")
+ if base is None:
+ base = yum.YumBase()
+
if command['action'] == "whatinstalled":
- query(command)
+ query(base, command)
elif command['action'] == "whatavailable":
- query(command)
+ query(base, command)
elif command['action'] == "versioncompare":
versioncompare(command['versions'])
elif command['action'] == "installonlypkgs":
- install_only_packages(command['package'])
+ install_only_packages(base, command['package'])
+ elif command['action'] == "close_rpmdb":
+ base.closeRpmDB()
+ base = None
+ outpipe.write('nil nil nil\n')
+ outpipe.flush()
else:
raise RuntimeError("bad command")
finally:
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 316a2a1081..5d723feb29 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -78,7 +78,7 @@ class Chef
def define_resource_requirements
requirements.assert(:create, :create_if_missing, :delete, :delete_key) do |a|
a.assertion { registry.hive_exists?(new_resource.key) }
- a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{new_resource.key.split('\\').shift} does not exist")
+ a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{new_resource.key.split("\\").shift} does not exist")
end
requirements.assert(:create) do |a|
diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb
index 614d56aa22..aa4acc1f83 100644
--- a/lib/chef/provider/route.rb
+++ b/lib/chef/provider/route.rb
@@ -131,7 +131,7 @@ class Chef
action :add do
# check to see if load_current_resource found the route
if is_running
- logger.trace("#{new_resource} route already active - nothing to do")
+ logger.debug("#{new_resource} route already active - nothing to do")
else
command = generate_command(:add)
converge_by("run #{command.join(" ")} to add route") do
@@ -152,7 +152,7 @@ class Chef
logger.info("#{new_resource} removed")
end
else
- logger.trace("#{new_resource} route does not exist - nothing to do")
+ logger.debug("#{new_resource} route does not exist - nothing to do")
end
# for now we always write the file (ugly but its what it is)
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 7a2d2fc86c..a1a43ef54e 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -82,7 +82,7 @@ class Chef
action :enable do
if current_resource.enabled
- logger.trace("#{new_resource} already enabled - nothing to do")
+ logger.debug("#{new_resource} already enabled - nothing to do")
else
converge_by("enable service #{new_resource}") do
enable_service
@@ -100,7 +100,7 @@ class Chef
logger.info("#{new_resource} disabled")
end
else
- logger.trace("#{new_resource} already disabled - nothing to do")
+ logger.debug("#{new_resource} already disabled - nothing to do")
end
load_new_resource_state
new_resource.enabled(false)
@@ -108,7 +108,7 @@ class Chef
action :mask do
if current_resource.masked
- logger.trace("#{new_resource} already masked - nothing to do")
+ logger.debug("#{new_resource} already masked - nothing to do")
else
converge_by("mask service #{new_resource}") do
mask_service
@@ -126,7 +126,7 @@ class Chef
logger.info("#{new_resource} unmasked")
end
else
- logger.trace("#{new_resource} already unmasked - nothing to do")
+ logger.debug("#{new_resource} already unmasked - nothing to do")
end
load_new_resource_state
new_resource.masked(false)
@@ -139,7 +139,7 @@ class Chef
logger.info("#{new_resource} started")
end
else
- logger.trace("#{new_resource} already running - nothing to do")
+ logger.debug("#{new_resource} already running - nothing to do")
end
load_new_resource_state
new_resource.running(true)
@@ -152,7 +152,7 @@ class Chef
logger.info("#{new_resource} stopped")
end
else
- logger.trace("#{new_resource} already stopped - nothing to do")
+ logger.debug("#{new_resource} already stopped - nothing to do")
end
load_new_resource_state
new_resource.running(false)
diff --git a/lib/chef/provider/service/aixinit.rb b/lib/chef/provider/service/aixinit.rb
index e845629fe7..0241839f20 100644
--- a/lib/chef/provider/service/aixinit.rb
+++ b/lib/chef/provider/service/aixinit.rb
@@ -45,7 +45,7 @@ class Chef
priority_ok = @current_resource.priority == @new_resource.priority
end
if @current_resource.enabled && priority_ok
- logger.trace("#{@new_resource} already enabled - nothing to do")
+ logger.debug("#{@new_resource} already enabled - nothing to do")
else
converge_by("enable service #{@new_resource}") do
enable_service
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 17e0b19b9c..7e38b7072b 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -138,7 +138,7 @@ class Chef
priority_ok = @current_resource.priority == new_resource.priority
end
if current_resource.enabled && priority_ok
- logger.trace("#{new_resource} already enabled - nothing to do")
+ logger.debug("#{new_resource} already enabled - nothing to do")
else
converge_by("enable service #{new_resource}") do
enable_service
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 01f7adef9f..9c1cdba39f 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -130,27 +130,21 @@ class Chef
# The variable name used in /etc/rc.conf for enabling this service
def service_enable_variable_name
@service_enable_variable_name ||=
- begin
- # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf
- # corresponding to this service
- # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need
- # to set mysql_enable="YES" in /etc/rc.conf$
- if init_command
- ::File.open(init_command) do |rcscript|
- rcscript.each_line do |line|
- if line =~ /^name="?(\w+)"?/
- return $1 + "_enable"
- end
+ if init_command
+ ::File.open(init_command) do |rcscript|
+ rcscript.each_line do |line|
+ if line =~ /^name="?(\w+)"?/
+ return $1 + "_enable"
end
end
- # some scripts support multiple instances through symlinks such as openvpn.
- # We should get the service name from rcvar.
- logger.trace("name=\"service\" not found at #{init_command}. falling back to rcvar")
- shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
- else
- # for why-run mode when the rcd_script is not there yet
- new_resource.service_name
end
+ # some scripts support multiple instances through symlinks such as openvpn.
+ # We should get the service name from rcvar.
+ logger.trace("name=\"service\" not found at #{init_command}. falling back to rcvar")
+ shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ else
+ # for why-run mode when the rcd_script is not there yet
+ new_resource.service_name
end
end
@@ -161,9 +155,9 @@ class Chef
case line
when /^#{Regexp.escape(var_name)}="(\w+)"/
enabled_state_found!
- if $1 =~ /^yes$/i
+ if $1.casecmp?("yes")
current_resource.enabled true
- elsif $1 =~ /^(no|none)$/i
+ elsif $1.casecmp?("no") || $1.casecmp?("none")
current_resource.enabled false
end
end
@@ -171,7 +165,7 @@ class Chef
end
if current_resource.enabled.nil?
- logger.trace("#{new_resource.name} enable/disable state unknown")
+ logger.debug("#{new_resource.name} enable/disable state unknown")
current_resource.enabled false
end
end
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 2152789a6e..fd8610a0f9 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -105,7 +105,7 @@ class Chef
def start_service
if @current_resource.running
- logger.trace("#{@new_resource} already running, not starting")
+ logger.debug("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
@@ -117,7 +117,7 @@ class Chef
def stop_service
unless @current_resource.running
- logger.trace("#{@new_resource} not running, not stopping")
+ logger.debug("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
@@ -153,7 +153,7 @@ class Chef
#
def enable_service
if @current_resource.enabled
- logger.trace("#{@new_resource} already enabled, not enabling")
+ logger.debug("#{@new_resource} already enabled, not enabling")
else
load_service
end
@@ -161,7 +161,7 @@ class Chef
def disable_service
unless @current_resource.enabled
- logger.trace("#{@new_resource} not enabled, not disabling")
+ logger.debug("#{@new_resource} not enabled, not disabling")
else
unload_service
end
@@ -169,12 +169,12 @@ class Chef
def load_service
session = @session_type ? "-S #{@session_type} " : ""
- cmd = "launchctl load -w " + session + @plist
+ cmd = "/bin/launchctl load -w " + session + @plist
shell_out_as_user(cmd)
end
def unload_service
- cmd = "launchctl unload -w " + @plist
+ cmd = "/bin/launchctl unload -w " + @plist
shell_out_as_user(cmd)
end
@@ -190,7 +190,7 @@ class Chef
def set_service_status
return if @plist.nil? || @service_label.to_s.empty?
- cmd = "launchctl list #{@service_label}"
+ cmd = "/bin/launchctl list #{@service_label}"
res = shell_out_as_user(cmd)
if res.exitstatus == 0
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 11c8d285bc..8f785875cc 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -76,6 +76,30 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
end
end
+ def systemd_service_status
+ @systemd_service_status ||= begin
+ # Collect all the status information for a service and returns it at once
+ options, args = get_systemctl_options_args
+ s = shell_out!(systemctl_path, args, "show", "-p", "UnitFileState", "-p", "ActiveState", new_resource.service_name, **options)
+ # e.g. /bin/systemctl --system show -p UnitFileState -p ActiveState sshd.service
+ # Returns something like:
+ # ActiveState=active
+ # UnitFileState=enabled
+ status = {}
+ s.stdout.each_line do |line|
+ k, v = line.strip.split("=")
+ status[k] = v
+ end
+
+ # Assert requisite keys exist
+ unless status.key?("UnitFileState") && status.key?("ActiveState")
+ raise Chef::Exceptions::Service, "'#{systemctl_path} show' not reporting status for #{new_resource.service_name}!"
+ end
+
+ status
+ end
+ end
+
def get_systemctl_options_args
if new_resource.user
raise NotImplementedError, "#{new_resource} does not support the user property on a target_mode host (yet)" if Chef::Config.target_mode?
@@ -98,7 +122,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def start_service
if current_resource.running
- logger.trace("#{new_resource} already running, not starting")
+ logger.debug("#{new_resource} already running, not starting")
else
if new_resource.start_command
super
@@ -111,7 +135,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def stop_service
unless current_resource.running
- logger.trace("#{new_resource} not running, not stopping")
+ logger.debug("#{new_resource} not running, not stopping")
else
if new_resource.stop_command
super
@@ -146,7 +170,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def enable_service
if current_resource.masked || current_resource.indirect
- logger.trace("#{new_resource} cannot be enabled: it is masked or indirect")
+ logger.debug("#{new_resource} cannot be enabled: it is masked or indirect")
return
end
options, args = get_systemctl_options_args
@@ -155,7 +179,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
def disable_service
if current_resource.masked || current_resource.indirect
- logger.trace("#{new_resource} cannot be disabled: it is masked or indirect")
+ logger.debug("#{new_resource} cannot be disabled: it is masked or indirect")
return
end
options, args = get_systemctl_options_args
@@ -173,25 +197,30 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
end
def is_active?
- options, args = get_systemctl_options_args
- shell_out(systemctl_path, args, "is-active", new_resource.service_name, "--quiet", **options).exitstatus == 0
+ # Note: "activating" is not active (as with type=notify or a oneshot)
+ systemd_service_status["ActiveState"] == "active"
end
def is_enabled?
- options, args = get_systemctl_options_args
- shell_out(systemctl_path, args, "is-enabled", new_resource.service_name, "--quiet", **options).exitstatus == 0
+ # if the service is in sysv compat mode, shellout to determine if enabled
+ if systemd_service_status["UnitFileState"] == "bad"
+ options, args = get_systemctl_options_args
+ return shell_out(systemctl_path, args, "is-enabled", new_resource.service_name, "--quiet", **options).exitstatus == 0
+ end
+ # See https://github.com/systemd/systemd/blob/master/src/systemctl/systemctl-is-enabled.c
+ # Note: enabled-runtime is excluded because this is volatile, and the state of enabled-runtime
+ # specifically means that the service is not enabled
+ %w{enabled static generated alias indirect}.include?(systemd_service_status["UnitFileState"])
end
def is_indirect?
- options, args = get_systemctl_options_args
- s = shell_out(systemctl_path, args, "is-enabled", new_resource.service_name, **options)
- s.stdout.include?("indirect")
+ systemd_service_status["UnitFileState"] == "indirect"
end
def is_masked?
- options, args = get_systemctl_options_args
- s = shell_out(systemctl_path, args, "is-enabled", new_resource.service_name, **options)
- s.exitstatus != 0 && s.stdout.include?("masked")
+ # Note: masked-runtime is excluded, because runtime is volatile, and
+ # because masked-runtime is not masked.
+ systemd_service_status["UnitFileState"] == "masked"
end
private
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 2b9e304160..7c2b82dc3a 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -28,10 +28,6 @@ class Chef
# to maintain a local state of service across restart's internal calls
attr_accessor :upstart_service_running
- provides :service, platform_family: "debian", override: true do
- upstart?
- end
-
UPSTART_STATE_FORMAT = %r{\S+ \(?(start|stop)?\)? ?[/ ](\w+)}.freeze
# Returns true if the configs for the service name has upstart variable
@@ -65,15 +61,8 @@ class Chef
end
end
- platform, version = Chef::Platform.find_platform_and_version(run_context.node)
- if platform == "ubuntu" && (8.04..9.04).cover?(version.to_f)
- @upstart_job_dir = "/etc/event.d"
- @upstart_conf_suffix = ""
- else
- @upstart_job_dir = "/etc/init"
- @upstart_conf_suffix = ".conf"
- end
-
+ @upstart_job_dir = "/etc/init"
+ @upstart_conf_suffix = ".conf"
@command_success = true # new_resource.status_command= false, means upstart used
@config_file_found = true
@upstart_command_success = true
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 98aad4fe29..ba2ecf224c 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -85,7 +85,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
state = current_state
if state == RUNNING
- logger.trace "#{@new_resource} already started - nothing to do"
+ logger.debug "#{@new_resource} already started - nothing to do"
elsif state == START_PENDING
logger.trace "#{@new_resource} already sent start signal - waiting for start"
wait_for_state(RUNNING)
@@ -114,7 +114,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be started from state [#{state}]"
end
else
- logger.trace "#{@new_resource} does not exist - nothing to do"
+ logger.debug "#{@new_resource} does not exist - nothing to do"
end
end
@@ -133,7 +133,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
elsif state == STOPPED
- logger.trace "#{@new_resource} already stopped - nothing to do"
+ logger.debug "#{@new_resource} already stopped - nothing to do"
elsif state == STOP_PENDING
logger.trace "#{@new_resource} already sent stop signal - waiting for stop"
wait_for_state(STOPPED)
@@ -141,7 +141,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
raise Chef::Exceptions::Service, "Service #{@new_resource} can't be stopped from state [#{state}]"
end
else
- logger.trace "#{@new_resource} does not exist - nothing to do"
+ logger.debug "#{@new_resource} does not exist - nothing to do"
end
end
@@ -156,7 +156,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
@new_resource.updated_by_last_action(true)
else
- logger.trace "#{@new_resource} does not exist - nothing to do"
+ logger.debug "#{@new_resource} does not exist - nothing to do"
end
end
@@ -164,7 +164,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:automatic)
else
- logger.trace "#{@new_resource} does not exist - nothing to do"
+ logger.debug "#{@new_resource} does not exist - nothing to do"
end
end
@@ -172,13 +172,13 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
if Win32::Service.exists?(@new_resource.service_name)
set_startup_type(:disabled)
else
- logger.trace "#{@new_resource} does not exist - nothing to do"
+ logger.debug "#{@new_resource} does not exist - nothing to do"
end
end
action :create do
if Win32::Service.exists?(new_resource.service_name)
- logger.trace "#{new_resource} already exists - nothing to do"
+ logger.debug "#{new_resource} already exists - nothing to do"
return
end
@@ -191,7 +191,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
action :delete do
unless Win32::Service.exists?(new_resource.service_name)
- logger.trace "#{new_resource} does not exist - nothing to do"
+ logger.debug "#{new_resource} does not exist - nothing to do"
return
end
@@ -222,7 +222,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
logger.info("#{@new_resource} enabled")
end
else
- logger.trace("#{@new_resource} already enabled - nothing to do")
+ logger.debug("#{@new_resource} already enabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(true)
@@ -235,7 +235,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
logger.info("#{@new_resource} disabled")
end
else
- logger.trace("#{@new_resource} already disabled - nothing to do")
+ logger.debug("#{@new_resource} already disabled - nothing to do")
end
load_new_resource_state
@new_resource.enabled(false)
@@ -248,7 +248,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
set_startup_type(startup_type)
end
else
- logger.trace("#{@new_resource} startup_type already #{startup_type} - nothing to do")
+ logger.debug("#{@new_resource} startup_type already #{startup_type} - nothing to do")
end
converge_delayed_start
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index 18fc9d3a3c..7f49e2139f 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -61,7 +61,7 @@ class Chef
shell_out!(checkout_command, run_options)
end
else
- logger.trace "#{new_resource} checkout destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.debug "#{new_resource} checkout destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
@@ -69,7 +69,7 @@ class Chef
if target_dir_non_existent_or_empty?
action_force_export
else
- logger.trace "#{new_resource} export destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ logger.debug "#{new_resource} export destination #{new_resource.destination} already exists or is a non-empty directory - nothing to do"
end
end
@@ -121,16 +121,14 @@ class Chef
# up the revision id by asking the server
# If the specified revision is an integer, trust it.
def revision_int
- @revision_int ||= begin
- if /^\d+$/.match?(new_resource.revision)
- new_resource.revision
- else
- command = scm(:info, new_resource.repository, new_resource.svn_info_args, authentication, "-r#{new_resource.revision}")
- svn_info = shell_out!(command, run_options(cwd: cwd, returns: [0, 1])).stdout
-
- extract_revision_info(svn_info)
- end
- end
+ @revision_int ||= if /^\d+$/.match?(new_resource.revision)
+ new_resource.revision
+ else
+ command = scm(:info, new_resource.repository, new_resource.svn_info_args, authentication, "-r#{new_resource.revision}")
+ svn_info = shell_out!(command, run_options(cwd: cwd, returns: [0, 1])).stdout
+
+ extract_revision_info(svn_info)
+ end
end
alias :revision_slug :revision_int
diff --git a/lib/chef/provider/support/zypper_repo.erb b/lib/chef/provider/support/zypper_repo.erb
index 6d508fa77f..23e871e406 100644
--- a/lib/chef/provider/support/zypper_repo.erb
+++ b/lib/chef/provider/support/zypper_repo.erb
@@ -1,15 +1,17 @@
-# This file was generated by Chef
+# This file was generated by Chef Infra
# Do NOT modify this file by hand.
[<%= @config.repo_name %>]
<% %w{ type enabled autorefresh gpgcheck gpgkey baseurl mirrorlist path priority keeppackages mode refresh_cache }.each do |prop| -%>
-<% next if @config.send(prop.to_sym).nil? -%>
+<% next if @config.send(prop.to_sym).nil? || (@config.send(prop.to_sym).is_a?(Array) && @config.send(prop.to_sym).empty?) -%>
<%= prop %>=<%=
case @config.send(prop.to_sym)
when TrueClass
'1'
when FalseClass
'0'
+ when Array
+ @config.send(prop.to_sym).join("\n ")
else
@config.send(prop.to_sym)
end %>
diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb
index 97043e48c7..26a20814f5 100644
--- a/lib/chef/provider/systemd_unit.rb
+++ b/lib/chef/provider/systemd_unit.rb
@@ -55,6 +55,26 @@ class Chef
end
end
+ def systemd_unit_status
+ @systemd_unit_status ||= begin
+ # Collect all the status information for a unit and return it at once
+ # This may fail if we are managing a template unit (e.g. with '@'), in which case
+ # we just ignore the error because unit status is irrelevant in that case
+ s = shell_out(*systemctl_cmd, "show", "-p", "UnitFileState", "-p", "ActiveState", new_resource.unit_name, **systemctl_opts)
+ # e.g. /bin/systemctl --system show -p UnitFileState -p ActiveState syslog.socket
+ # Returns something like:
+ # ActiveState=inactive
+ # UnitFileState=static
+ status = {}
+ s.stdout.each_line do |line|
+ k, v = line.strip.split("=")
+ status[k] = v
+ end
+
+ status
+ end
+ end
+
action :create do
if current_resource.content != new_resource.to_ini
converge_by("creating unit: #{new_resource.unit_name}") do
@@ -87,10 +107,10 @@ class Chef
action :enable do
if current_resource.static
- logger.trace("#{new_resource.unit_name} is a static unit, enabling is a NOP.")
+ logger.debug("#{new_resource.unit_name} is a static unit, enabling is a NOP.")
end
if current_resource.indirect
- logger.trace("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
+ logger.debug("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
end
unless current_resource.enabled || current_resource.static || current_resource.indirect
@@ -103,11 +123,11 @@ class Chef
action :disable do
if current_resource.static
- logger.trace("#{new_resource.unit_name} is a static unit, disabling is a NOP.")
+ logger.debug("#{new_resource.unit_name} is a static unit, disabling is a NOP.")
end
if current_resource.indirect
- logger.trace("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
+ logger.debug("#{new_resource.unit_name} is an indirect unit, enabling is a NOP.")
end
if current_resource.enabled && !current_resource.static && !current_resource.indirect
@@ -175,7 +195,7 @@ class Chef
logger.info("#{new_resource} reloaded")
end
else
- logger.trace("#{new_resource.unit_name} is not active, skipping reload.")
+ logger.debug("#{new_resource.unit_name} is not active, skipping reload.")
end
end
@@ -201,23 +221,29 @@ class Chef
end
def active?
- systemctl_execute("is-active", new_resource.unit_name).exitstatus == 0
+ # Note: "activating" is not active (as with type=notify or a oneshot)
+ systemd_unit_status["ActiveState"] == "active"
end
def enabled?
- systemctl_execute("is-enabled", new_resource.unit_name).exitstatus == 0
+ # See https://github.com/systemd/systemd/blob/master/src/systemctl/systemctl-is-enabled.c
+ # Note: enabled-runtime is excluded because this is volatile, and the state of enabled-runtime
+ # specifically means that the service is not enabled
+ %w{enabled static generated alias indirect}.include?(systemd_unit_status["UnitFileState"])
end
def masked?
- systemctl_execute("status", new_resource.unit_name).stdout.include?("masked")
+ # Note: masked-runtime is excluded, because runtime is volatile, and
+ # because masked-runtime is not masked.
+ systemd_unit_status["UnitFileState"] == "masked"
end
def static?
- systemctl_execute("is-enabled", new_resource.unit_name).stdout.include?("static")
+ systemd_unit_status["UnitFileState"] == "static"
end
def indirect?
- systemctl_execute("is-enabled", new_resource.unit_name).stdout.include?("indirect")
+ systemd_unit_status["UnitFileState"] == "indirect"
end
private
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index a0977b5523..00a82fea79 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -29,9 +29,7 @@ class Chef
include Chef::Mixin::Template
def template_location
- @template_file_cache_location ||= begin
- template_finder.find(new_resource.source, local: new_resource.local, cookbook: new_resource.cookbook)
- end
+ @template_file_cache_location ||= template_finder.find(new_resource.source, local: new_resource.local, cookbook: new_resource.cookbook)
end
private
@@ -65,7 +63,7 @@ class Chef
context[:template_finder] = template_finder
# helper variables
- context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:coookbook_name)
+ context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:cookbook_name)
context[:recipe_name] = new_resource.recipe_name unless context.keys.include?(:recipe_name)
context[:recipe_line_string] = new_resource.source_line unless context.keys.include?(:recipe_line_string)
context[:recipe_path] = new_resource.source_line_file unless context.keys.include?(:recipe_path)
@@ -84,9 +82,7 @@ class Chef
end
def template_finder
- @template_finder ||= begin
- TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node)
- end
+ @template_finder ||= TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node)
end
end
end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index 803d87d820..61de2127bb 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -172,7 +172,7 @@ class Chef
logger.info("#{new_resource} locked")
end
else
- logger.trace("#{new_resource} already locked - nothing to do")
+ logger.debug("#{new_resource} already locked - nothing to do")
end
end
@@ -183,7 +183,7 @@ class Chef
logger.info("#{new_resource} unlocked")
end
else
- logger.trace("#{new_resource} already unlocked - nothing to do")
+ logger.debug("#{new_resource} already unlocked - nothing to do")
end
end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 7b266b8d62..cd10403e4a 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -438,7 +438,7 @@ in 'password', with the associated 'salt' and 'iterations'.")
#
def locked?
if authentication_authority
- !!(authentication_authority =~ /DisabledUser/ )
+ !!(authentication_authority.include?("DisabledUser"))
else
false
end
diff --git a/lib/chef/provider/user/mac.rb b/lib/chef/provider/user/mac.rb
index 5604244f7f..94b0ce0b21 100644
--- a/lib/chef/provider/user/mac.rb
+++ b/lib/chef/provider/user/mac.rb
@@ -29,13 +29,12 @@ class Chef
class User
# A macOS user provider that is compatible with default TCC restrictions
# in macOS 10.14. See resource/user/mac_user.rb for complete description
- # of the mac_user resource and how it differs from the dscl resource used
- # on previous platforms.
+ # of the mac_user resource
class MacUser < Chef::Provider::User
include Chef::Mixin::Which
provides :mac_user
- provides :user, os: "darwin", platform_version: ">= 10.14"
+ provides :user, os: "darwin"
attr_reader :user_plist, :admin_group_plist
@@ -394,23 +393,21 @@ class Chef
# associated group resource. If a group exists we'll modify it, otherwise
# create it.
def user_group_info
- @user_group_info ||= begin
- if new_resource.gid.is_a?(String)
- begin
- g = Etc.getgrnam(new_resource.gid)
- [g.name, g.gid.to_s, :modify]
- rescue
- [new_resource.gid, nil, :create]
- end
- else
- begin
- g = Etc.getgrgid(new_resource.gid)
- [g.name, g.gid.to_s, :modify]
- rescue
- [g.username, nil, :create]
- end
- end
- end
+ @user_group_info ||= if new_resource.gid.is_a?(String)
+ begin
+ g = Etc.getgrnam(new_resource.gid)
+ [g.name, g.gid.to_s, :modify]
+ rescue
+ [new_resource.gid, nil, :create]
+ end
+ else
+ begin
+ g = Etc.getgrgid(new_resource.gid)
+ [g.name, g.gid.to_s, :modify]
+ rescue
+ [g.username, nil, :create]
+ end
+ end
end
def secure_token_enabled?
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index 9e21e5a816..924098eea0 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -97,7 +97,7 @@ class Chef
command = "pw usermod #{new_resource.username} -H 0"
shell_out!(command, input: new_resource.password.to_s)
else
- logger.trace("#{new_resource} no change needed to password")
+ logger.debug("#{new_resource} no change needed to password")
end
end
end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index 32b2c35264..8cf4ea475b 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -62,7 +62,7 @@ class Chef
# <false>:: If the users are identical
def compare_user
@change_desc = []
- unless @net_user.validate_credentials(new_resource.password)
+ if new_resource.password && !@net_user.validate_credentials(new_resource.password)
@change_desc << "update password"
end
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index a93319a35a..ee986c6d59 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -83,7 +83,7 @@ class Chef
username = new_resource.user
if new_resource.domain
- username = new_resource.domain + '\\' + new_resource.user
+ username = new_resource.domain + "\\" + new_resource.user
end
# Create an ACE that allows the alternate user read access to the script
diff --git a/lib/chef/provider/yum_repository.rb b/lib/chef/provider/yum_repository.rb
index 956d5ae64c..b0dfe20f1b 100644
--- a/lib/chef/provider/yum_repository.rb
+++ b/lib/chef/provider/yum_repository.rb
@@ -33,7 +33,7 @@ class Chef
def load_current_resource; end
action :create do
- declare_resource(:template, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
+ declare_resource(:template, ::File.join(new_resource.reposdir, "#{new_resource.repositoryid}.repo")) do
if template_available?(new_resource.source)
source new_resource.source
else
@@ -81,7 +81,7 @@ class Chef
only_if "yum repolist all | grep -P '^#{new_resource.repositoryid}([ \t]|$)'"
end
- declare_resource(:file, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do
+ declare_resource(:file, ::File.join(new_resource.reposdir, "#{new_resource.repositoryid}.repo")) do
action :delete
notifies :create, "ruby_block[package-cache-reload-#{new_resource.repositoryid}]", :immediately
end
diff --git a/lib/chef/provider/zypper_repository.rb b/lib/chef/provider/zypper_repository.rb
index 53dae74948..c1c863e20e 100644
--- a/lib/chef/provider/zypper_repository.rb
+++ b/lib/chef/provider/zypper_repository.rb
@@ -31,12 +31,12 @@ class Chef
action :create do
if new_resource.gpgautoimportkeys
- install_gpg_key(new_resource.gpgkey)
+ install_gpg_keys(new_resource.gpgkey)
else
- logger.trace("'gpgautoimportkeys' property is set to false. Skipping key import.")
+ logger.debug("'gpgautoimportkeys' property is set to false. Skipping key import.")
end
- declare_resource(:template, "/etc/zypp/repos.d/#{escaped_repo_name}.repo") do
+ template "/etc/zypp/repos.d/#{escaped_repo_name}.repo" do
if template_available?(new_resource.source)
source new_resource.source
else
@@ -51,13 +51,13 @@ class Chef
end
action :delete do
- declare_resource(:execute, "zypper --quiet --non-interactive removerepo #{escaped_repo_name}") do
+ execute "zypper --quiet --non-interactive removerepo #{escaped_repo_name}" do
only_if "zypper --quiet lr #{escaped_repo_name}"
end
end
action :refresh do
- declare_resource(:execute, "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}") do
+ execute "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}" do
only_if "zypper --quiet lr #{escaped_repo_name}"
end
end
@@ -68,15 +68,7 @@ class Chef
# zypper repos are allowed to have spaces in the names
# @return [String] escaped repo string
def escaped_repo_name
- Shellwords.escape(new_resource.repo_name)
- end
-
- # return the specified cookbook name or the cookbook containing the
- # resource.
- #
- # @return [String] name of the cookbook
- def cookbook_name
- new_resource.cookbook || new_resource.cookbook_name
+ @escaped_repo_name ||= Shellwords.escape(new_resource.repo_name)
end
# determine if a template file is available in the current run
@@ -84,7 +76,7 @@ class Chef
#
# @return [Boolean] template file exists or doesn't
def template_available?(path)
- !path.nil? && run_context.has_template_in_cookbook?(cookbook_name, path)
+ !path.nil? && run_context.has_template_in_cookbook?(new_resource.cookbook, path)
end
# determine if a cookbook file is available in the run
@@ -92,7 +84,7 @@ class Chef
#
# @return [Boolean] cookbook file exists or doesn't
def has_cookbook_file?(fn)
- run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn)
+ run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook, fn)
end
# Given the provided key URI determine what kind of chef resource we need
@@ -158,27 +150,31 @@ class Chef
short_key_id
end
- # install the provided gpg key
- # @param [String] uri the uri of the local or remote gpg key
- def install_gpg_key(uri)
- unless uri
- logger.trace("'gpgkey' property not provided or set to nil. Skipping key import.")
+ # install the provided gpg keys
+ # @param [String] uris the uri of the local or remote gpg key
+ def install_gpg_keys(uris)
+ if uris.empty?
+ logger.debug("'gpgkey' property not provided or set. Skipping gpg key import.")
return
end
- cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1])
+ # fetch each key to the cache dir either from the cookbook or by downloading it
+ # and then import the key
+ uris.each do |uri|
+ cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1])
- declare_resource(key_type(new_resource.gpgkey), cached_keyfile) do
- source uri
- mode "0644"
- sensitive new_resource.sensitive
- action :create
- end
+ declare_resource(key_type(uri), cached_keyfile) do
+ source uri
+ mode "0644"
+ sensitive new_resource.sensitive
+ action :create
+ end
- declare_resource(:execute, "import gpg key from #{new_resource.gpgkey}") do
- command "/bin/rpm --import #{cached_keyfile}"
- not_if { key_installed?(cached_keyfile) }
- action :run
+ execute "import gpg key from #{uri}" do
+ command "/bin/rpm --import #{cached_keyfile}"
+ not_if { key_installed?(cached_keyfile) }
+ action :run
+ end
end
end
end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 7652d60896..331f224855 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -94,7 +94,6 @@ require_relative "provider/service/aixinit"
require_relative "provider/service/aix"
require_relative "provider/user/aix"
-require_relative "provider/user/dscl"
require_relative "provider/user/linux"
require_relative "provider/user/mac"
require_relative "provider/user/pw"
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index e572f0667d..2442b9a050 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -42,6 +42,7 @@ require_relative "mixin/deprecation"
require_relative "mixin/properties"
require_relative "mixin/provides"
require_relative "dsl/universal"
+require_relative "constants"
class Chef
class Resource
@@ -1062,6 +1063,7 @@ class Chef
# action for the resource.
#
# @param name [Symbol] The action name to define.
+ # @param description [String] optional description for the action
# @param recipe_block The recipe to run when the action is taken. This block
# takes no parameters, and will be evaluated in a new context containing:
#
@@ -1071,14 +1073,37 @@ class Chef
#
# @return The Action class implementing the action
#
- def self.action(action, &recipe_block)
+ def self.action(action, description: nil, &recipe_block)
action = action.to_sym
declare_action_class
action_class.action(action, &recipe_block)
self.allowed_actions += [ action ]
+ # Accept any non-nil description, which will correctly override
+ # any specific inherited description.
+ action_descriptions[action] = description unless description.nil?
default_action action if Array(default_action) == [:nothing]
end
+ # Retrieve the description for a resource's action, if
+ # any description has been included in the definition.
+ #
+ # @param action [Symbol,String] the action name
+ # @return the description of the action provided, or nil if no description
+ # was defined
+ def self.action_description(action)
+ action_descriptions[action.to_sym]
+ end
+
+ # @api private
+ #
+ # @return existing action description hash, or newly-initialized
+ # hash containing action descriptions inherited from parent Resource,
+ # if any.
+ def self.action_descriptions
+ @action_descriptions ||=
+ superclass.respond_to?(:action_descriptions) ? superclass.action_descriptions.dup : { nothing: nil }
+ end
+
# Define a method to load up this resource's properties with the current
# actual values.
#
@@ -1196,9 +1221,9 @@ class Chef
#
# FORBIDDEN_IVARS do not show up when the resource is converted to JSON (ie. hidden from data_collector and sending to the chef server via #to_json/to_h/as_json/inspect)
- FORBIDDEN_IVARS = %i{@run_context @logger @not_if @only_if @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner}.freeze
+ FORBIDDEN_IVARS = %i{@run_context @logger @not_if @only_if @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner @action_descriptions}.freeze
# HIDDEN_IVARS do not show up when the resource is displayed to the user as text (ie. in the error inspector output via #to_text)
- HIDDEN_IVARS = %i{@allowed_actions @resource_name @source_line @run_context @logger @name @not_if @only_if @elapsed_time @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner}.freeze
+ HIDDEN_IVARS = %i{@allowed_actions @resource_name @source_line @run_context @logger @name @not_if @only_if @elapsed_time @enclosing_provider @description @introduced @examples @validation_message @deprecated @default_description @skip_docs @executed_by_runner @action_descriptions}.freeze
include Chef::Mixin::ConvertToClassName
extend Chef::Mixin::ConvertToClassName
@@ -1338,8 +1363,9 @@ class Chef
#
# @param arg [String] version constraint to match against (e.g. "> 14")
#
- def self.chef_version_for_provides(constraint)
- @chef_version_for_provides = constraint
+ def self.chef_version_for_provides(constraint = NOT_PASSED)
+ @chef_version_for_provides = constraint unless constraint == NOT_PASSED
+ @chef_version_for_provides ||= nil
end
# Mark this resource as providing particular DSL.
@@ -1352,14 +1378,11 @@ class Chef
def self.provides(name, **options, &block)
name = name.to_sym
- # quell warnings
- @chef_version_for_provides = nil unless defined?(@chef_version_for_provides)
-
# deliberately do not go through the accessor here
@resource_name = name if resource_name.nil?
- if @chef_version_for_provides && !options.include?(:chef_version)
- options[:chef_version] = @chef_version_for_provides
+ if chef_version_for_provides && !options.include?(:chef_version)
+ options[:chef_version] = chef_version_for_provides
end
result = Chef.resource_handler_map.set(name, self, **options, &block)
diff --git a/lib/chef/resource/alternatives.rb b/lib/chef/resource/alternatives.rb
index fe5af6b7b6..59939543ab 100644
--- a/lib/chef/resource/alternatives.rb
+++ b/lib/chef/resource/alternatives.rb
@@ -26,7 +26,7 @@ class Chef
provides(:alternatives) { true }
- description "The alternatives resource allows for configuration of command alternatives in Linux using the alternatives or update-alternatives packages."
+ description "Use the **alternatives** resource to configure command alternatives in Linux using the alternatives or update-alternatives packages."
introduced "16.0"
examples <<~DOC
**Install an alternative**:
@@ -122,7 +122,7 @@ class Chef
end
end
- action :install do
+ action :install, description: "Install an alternative on the system including symlinks." do
if path_priority != new_resource.priority
converge_by("adding alternative #{new_resource.link} #{new_resource.link_name} #{new_resource.path} #{new_resource.priority}") do
output = shell_out(alternatives_cmd, "--install", new_resource.link, new_resource.link_name, new_resource.path, new_resource.priority)
@@ -133,7 +133,7 @@ class Chef
end
end
- action :set do
+ action :set, description: "Set the symlink for an alternative." do
if current_path != new_resource.path
converge_by("setting alternative #{new_resource.link_name} #{new_resource.path}") do
output = shell_out(alternatives_cmd, "--set", new_resource.link_name, new_resource.path)
@@ -144,7 +144,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove an alternative and all associated links." do
if path_exists?
converge_by("removing alternative #{new_resource.link_name} #{new_resource.path}") do
shell_out(alternatives_cmd, "--remove", new_resource.link_name, new_resource.path)
@@ -152,13 +152,13 @@ class Chef
end
end
- action :auto do
+ action :auto, description: "Set an alternative up in automatic mode with the highest priority automatically selected." do
converge_by("setting auto alternative #{new_resource.link_name}") do
shell_out(alternatives_cmd, "--auto", new_resource.link_name)
end
end
- action :refresh do
+ action :refresh, description: "Refresh alternatives." do
converge_by("refreshing alternative #{new_resource.link_name}") do
shell_out(alternatives_cmd, "--refresh", new_resource.link_name)
end
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index 0a31f89af3..331d411373 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -72,7 +72,7 @@ class Chef
property :response_file_variables, Hash,
description: "A Hash of response file variables in the form of {'VARIABLE' => 'VALUE'}.",
- default: lazy { {} }, desired_state: false
+ default: {}, desired_state: false
end
end
diff --git a/lib/chef/resource/apt_preference.rb b/lib/chef/resource/apt_preference.rb
index fd987466ea..09e56b1edf 100644
--- a/lib/chef/resource/apt_preference.rb
+++ b/lib/chef/resource/apt_preference.rb
@@ -91,7 +91,7 @@ class Chef
end
end
- action :add do
+ action :add, description: "Creates a preferences file under `/etc/apt/preferences.d`." do
return unless debian?
preference = build_pref(
@@ -130,7 +130,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Removes the preferences file, thus unpinning the package." do
return unless debian?
sanitized_prefname = safe_name(new_resource.package_name)
diff --git a/lib/chef/resource/apt_repository.rb b/lib/chef/resource/apt_repository.rb
index da8ca78413..97dc26b89e 100644
--- a/lib/chef/resource/apt_repository.rb
+++ b/lib/chef/resource/apt_repository.rb
@@ -36,7 +36,7 @@ class Chef
examples <<~DOC
**Add repository with basic settings**:
- ```ruby
+ ```ruby
apt_repository 'nginx' do
uri 'http://nginx.org/packages/ubuntu/'
components ['nginx']
@@ -128,7 +128,7 @@ class Chef
property :components, Array,
description: "Package groupings, such as 'main' and 'stable'.",
- default: lazy { [] }, default_description: "`main` if using a PPA repository."
+ default: [], default_description: "`main` if using a PPA repository."
property :arch, [String, nil, FalseClass],
description: "Constrain packages to a particular CPU architecture such as `i386` or `amd64`."
@@ -147,7 +147,7 @@ class Chef
property :key, [String, Array, nil, FalseClass],
description: "If a keyserver is provided, this is assumed to be the fingerprint; otherwise it can be either the URI of GPG key for the repo, or a cookbook_file.",
- default: lazy { [] }, coerce: proc { |x| x ? Array(x) : x }
+ default: [], coerce: proc { |x| x ? Array(x) : x }
property :key_proxy, [String, nil, FalseClass],
description: "If set, a specified proxy is passed to GPG via `http-proxy=`."
@@ -409,7 +409,7 @@ class Chef
end
end
- action :add do
+ action :add, description: "Creates a repository file at `/etc/apt/sources.list.d/` and builds the repository listing." do
return unless debian?
execute "apt-cache gencaches" do
@@ -459,7 +459,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Removes the repository listing." do
return unless debian?
cleanup_legacy_file!
@@ -477,7 +477,7 @@ class Chef
end
end
else
- logger.trace("/etc/apt/sources.list.d/#{new_resource.repo_name}.list does not exist. Nothing to do")
+ logger.debug("/etc/apt/sources.list.d/#{new_resource.repo_name}.list does not exist. Nothing to do")
end
end
diff --git a/lib/chef/resource/apt_update.rb b/lib/chef/resource/apt_update.rb
index e5f75143bb..cfecef02cf 100644
--- a/lib/chef/resource/apt_update.rb
+++ b/lib/chef/resource/apt_update.rb
@@ -17,6 +17,7 @@
#
require_relative "../resource"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
@@ -32,8 +33,8 @@ class Chef
```ruby
apt_update 'all platforms' do
- frequency 86400
- action :periodic
+ frequency 86400
+ action :periodic
end
```
@@ -85,7 +86,7 @@ class Chef
end
end
- action :periodic do
+ action :periodic, description: "Update the Apt repository at the interval specified by the `frequency` property." do
return unless debian?
unless apt_up_to_date?
@@ -95,7 +96,7 @@ class Chef
end
end
- action :update do
+ action :update, description: "Update the Apt repository at the start of a #{ChefUtils::Dist::Infra::PRODUCT} run." do
return unless debian?
converge_by "force update new lists of packages" do
diff --git a/lib/chef/resource/archive_file.rb b/lib/chef/resource/archive_file.rb
index 4d77ee979b..212835114b 100644
--- a/lib/chef/resource/archive_file.rb
+++ b/lib/chef/resource/archive_file.rb
@@ -67,7 +67,7 @@ class Chef
property :mode, [String, Integer],
description: "The mode of the extracted files. Integer values are deprecated as octal values (ex. 0755) would not be interpreted correctly.",
- default: "755"
+ default: "755", default_description: "'755'"
property :destination, String,
description: "The file path to extract the archive file to.",
@@ -85,8 +85,7 @@ class Chef
alias_method :extract_options, :options
alias_method :extract_to, :destination
- action :extract do
- description "Extract and archive file."
+ action :extract, description: "Extract and archive file." do
require_libarchive
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index 50e2d06391..360c064ff0 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -92,7 +92,7 @@ class Chef
super(caller.first, *args)
end
- action :break do
+ action :break, description: "Add a breakpoint for use with #{ChefUtils::Dist::Infra::SHELL}" do
if defined?(Shell) && Shell.running?
with_run_context :parent do
run_context.resource_collection.iterator.pause
diff --git a/lib/chef/resource/build_essential.rb b/lib/chef/resource/build_essential.rb
index 3039f709c8..8b4c142e0f 100644
--- a/lib/chef/resource/build_essential.rb
+++ b/lib/chef/resource/build_essential.rb
@@ -57,10 +57,7 @@ class Chef
introduced: "15.5",
default: false, desired_state: false # FIXME: make this default to true
- action :install do
-
- description "Install build essential packages"
-
+ action :install, description: "Install build essential packages." do
case
when debian?
package %w{ autoconf binutils-doc bison build-essential flex gettext ncurses-dev }
@@ -122,8 +119,7 @@ class Chef
end
end
- action :upgrade do
- description "Upgrade build essential (Xcode Command Line) tools on macOS"
+ action :upgrade, description: "Upgrade the Xcode CLI Tools on macOS hosts. **New in Chef Infra Client 16**" do
if macos?
pkg_label = xcode_cli_package_label
diff --git a/lib/chef/resource/chef_client_config.rb b/lib/chef/resource/chef_client_config.rb
index deee413ba4..770aedf442 100644
--- a/lib/chef/resource/chef_client_config.rb
+++ b/lib/chef/resource/chef_client_config.rb
@@ -110,7 +110,7 @@ class Chef
property :config_directory, String,
description: "The directory to store the client.rb in.",
default: ChefConfig::Config.etc_chef_dir,
- default_description: "`/etc/chef/` on *nix-like systems and `C:\chef\` on Windows"
+ default_description: "`/etc/chef/` on *nix-like systems and `C:\\chef\\` on Windows"
property :user, String,
description: "The user that should own the client.rb file and the configuration directory if it needs to be created. Note: The configuration directory will not be created if it already exists, which allows you to further control the setup of that directory outside of this resource."
@@ -139,7 +139,7 @@ class Chef
DESC
property :formatters, Array,
- description: "",
+ description: "Client logging formatters to load.",
default: []
property :event_loggers, Array,
@@ -227,7 +227,7 @@ class Chef
property :additional_config, String,
description: "Additional text to add at the bottom of the client.rb config. This can be used to run custom Ruby or to add less common config options"
- action :create do
+ action :create, description: "Create a client.rb config file for configuring #{ChefUtils::Dist::Infra::PRODUCT}." do
unless ::Dir.exist?(new_resource.config_directory)
directory new_resource.config_directory do
user new_resource.user unless new_resource.user.nil?
@@ -284,7 +284,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a client.rb config file for configuring #{ChefUtils::Dist::Infra::PRODUCT}." do
file ::File.join(new_resource.config_directory, "client.rb") do
action :delete
end
diff --git a/lib/chef/resource/chef_client_cron.rb b/lib/chef/resource/chef_client_cron.rb
index 003b28d7b3..cab2adacf2 100644
--- a/lib/chef/resource/chef_client_cron.rb
+++ b/lib/chef/resource/chef_client_cron.rb
@@ -131,11 +131,11 @@ class Chef
description: "The path to the #{ChefUtils::Dist::Infra::CLIENT} binary."
property :daemon_options, Array,
- default: lazy { [] },
+ default: [],
description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command."
property :environment, Hash,
- default: lazy { {} },
+ default: {},
description: "A Hash containing additional arbitrary environment variables under which the cron job will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`."
property :nice, [Integer, String],
@@ -144,7 +144,7 @@ class Chef
coerce: proc { |x| Integer(x) },
callbacks: { "should be an Integer between -20 and 19" => proc { |v| v >= -20 && v <= 19 } }
- action :add do
+ action :add, description: "Add a cron job to run #{ChefUtils::Dist::Infra::PRODUCT}." do
# TODO: Replace this with a :create_if_missing action on directory when that exists
unless ::Dir.exist?(new_resource.log_directory)
directory new_resource.log_directory do
@@ -168,7 +168,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a cron job for #{ChefUtils::Dist::Infra::PRODUCT}." do
declare_resource(cron_resource_type, new_resource.job_name) do
action :delete
end
@@ -213,7 +213,7 @@ class Chef
#
def log_command
if new_resource.append_log_file
- "-L #{::File.join(new_resource.log_directory, new_resource.log_file_name)}"
+ ">> #{::File.join(new_resource.log_directory, new_resource.log_file_name)} 2>&1"
else
"> #{::File.join(new_resource.log_directory, new_resource.log_file_name)} 2>&1"
end
diff --git a/lib/chef/resource/chef_client_launchd.rb b/lib/chef/resource/chef_client_launchd.rb
index 0e173050d0..86c2b6ce7d 100644
--- a/lib/chef/resource/chef_client_launchd.rb
+++ b/lib/chef/resource/chef_client_launchd.rb
@@ -23,7 +23,7 @@ class Chef
provides :chef_client_launchd
- description "Use the **chef_client_launchd** resource to configure the #{ChefUtils::Dist::Infra::PRODUCT} to run on a schedule."
+ description "Use the **chef_client_launchd** resource to configure the #{ChefUtils::Dist::Infra::PRODUCT} to run on a schedule on macOS systems."
introduced "16.5"
examples <<~DOC
**Set the #{ChefUtils::Dist::Infra::PRODUCT} to run on a schedule**:
@@ -86,11 +86,11 @@ class Chef
property :daemon_options, Array,
description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
- default: lazy { [] }
+ default: []
property :environment, Hash,
description: "A Hash containing additional arbitrary environment variables under which the launchd daemon will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`.",
- default: lazy { {} }
+ default: {}
property :nice, [Integer, String],
description: "The process priority to run the #{ChefUtils::Dist::Infra::CLIENT} process at. A value of -20 is the highest priority and 19 is the lowest priority.",
@@ -101,7 +101,7 @@ class Chef
description: "Run the #{ChefUtils::Dist::Infra::CLIENT} process with low priority disk IO",
default: true
- action :enable do
+ action :enable, description: "Enable running #{ChefUtils::Dist::Infra::PRODUCT} on a schedule using launchd." do
unless ::Dir.exist?(new_resource.log_directory)
directory new_resource.log_directory do
owner new_resource.user
@@ -134,7 +134,7 @@ class Chef
standard_error_path ::File.join(new_resource.log_directory, new_resource.log_file_name)
program_arguments ["/bin/bash",
"-c",
- "echo; echo #{ChefUtils::Dist::Infra::PRODUCT} launchd daemon config has been updated. Manually unloading and reloading the daemon; echo Now unloading the daemon; launchctl unload /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist; sleep 2; echo Now loading the daemon; launchctl load /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist"]
+ "echo; echo #{ChefUtils::Dist::Infra::PRODUCT} launchd daemon config has been updated. Manually unloading and reloading the daemon; echo Now unloading the daemon; /bin/launchctl unload /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist; sleep 2; echo Now loading the daemon; /bin/launchctl load /Library/LaunchDaemons/com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}.plist"]
action :enable # enable creates the plist & triggers service restarts on change
end
@@ -148,7 +148,7 @@ class Chef
end
end
- action :disable do
+ action :disable, description: "Disable running #{ChefUtils::Dist::Infra::PRODUCT} on a schedule using launchd" do
service ChefUtils::Dist::Infra::PRODUCT do
service_name "com.#{ChefUtils::Dist::Infra::SHORT}.#{ChefUtils::Dist::Infra::CLIENT}"
action :disable
diff --git a/lib/chef/resource/chef_client_scheduled_task.rb b/lib/chef/resource/chef_client_scheduled_task.rb
index 25780afdf4..e963ac21cc 100644
--- a/lib/chef/resource/chef_client_scheduled_task.rb
+++ b/lib/chef/resource/chef_client_scheduled_task.rb
@@ -30,33 +30,33 @@ class Chef
**Setup #{ChefUtils::Dist::Infra::PRODUCT} to run using the default 30 minute cadence**:
```ruby
- chef_client_scheduled_task 'Run #{ChefUtils::Dist::Infra::PRODUCT} as a scheduled task'
+ chef_client_scheduled_task 'Run #{ChefUtils::Dist::Infra::PRODUCT} as a scheduled task'
```
**Run #{ChefUtils::Dist::Infra::PRODUCT} on system start**:
```ruby
- chef_client_scheduled_task '#{ChefUtils::Dist::Infra::PRODUCT} on start' do
- frequency 'onstart'
- end
+ chef_client_scheduled_task '#{ChefUtils::Dist::Infra::PRODUCT} on start' do
+ frequency 'onstart'
+ end
```
**Run #{ChefUtils::Dist::Infra::PRODUCT} with extra options passed to the client**:
```ruby
- chef_client_scheduled_task 'Run an override recipe' do
- daemon_options ['--override-runlist mycorp_base::default']
- end
+ chef_client_scheduled_task 'Run an override recipe' do
+ daemon_options ['--override-runlist mycorp_base::default']
+ end
```
**Run #{ChefUtils::Dist::Infra::PRODUCT} daily at 01:00 am, specifying a named run-list**:
```ruby
- chef_client_scheduled_task 'Run chef-client named run-list daily' do
- frequency 'daily'
- start_time '01:00'
- daemon_options ['-n audit_only']
- end
+ chef_client_scheduled_task 'Run chef-client named run-list daily' do
+ frequency 'daily'
+ start_time '01:00'
+ daemon_options ['-n audit_only']
+ end
```
DOC
@@ -127,9 +127,9 @@ class Chef
property :daemon_options, Array,
description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
- default: lazy { [] }
+ default: []
- action :add do
+ action :add, description: "Add a Windows Scheduled Task that runs #{ChefUtils::Dist::Infra::PRODUCT}." do
# TODO: Replace this with a :create_if_missing action on directory when that exists
unless Dir.exist?(new_resource.log_directory)
directory new_resource.log_directory do
@@ -157,7 +157,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a Windows Scheduled Task that runs #{ChefUtils::Dist::Infra::PRODUCT}." do
windows_task new_resource.task_name do
action :delete
end
diff --git a/lib/chef/resource/chef_client_systemd_timer.rb b/lib/chef/resource/chef_client_systemd_timer.rb
index e911fc2cb0..57dc8a36a4 100644
--- a/lib/chef/resource/chef_client_systemd_timer.rb
+++ b/lib/chef/resource/chef_client_systemd_timer.rb
@@ -92,11 +92,11 @@ class Chef
property :daemon_options, Array,
description: "An array of options to pass to the #{ChefUtils::Dist::Infra::CLIENT} command.",
- default: lazy { [] }
+ default: []
property :environment, Hash,
description: "A Hash containing additional arbitrary environment variables under which the systemd timer will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`.",
- default: lazy { {} }
+ default: {}
property :cpu_quota, [Integer, String],
description: "The systemd CPUQuota to run the #{ChefUtils::Dist::Infra::CLIENT} process with. This is a percentage value of the total CPU time available on the system. If the system has more than 1 core this may be a value greater than 100.",
@@ -104,7 +104,7 @@ class Chef
coerce: proc { |x| Integer(x) },
callbacks: { "should be a positive Integer" => proc { |v| v > 0 } }
- action :add do
+ action :add, description: "Add a systemd timer that runs #{ChefUtils::Dist::Infra::PRODUCT}." do
systemd_unit "#{new_resource.job_name}.service" do
content service_content
action :create
@@ -116,7 +116,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a systemd timer that runs #{ChefUtils::Dist::Infra::PRODUCT}." do
systemd_unit "#{new_resource.job_name}.service" do
action :delete
end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index fc23555cbd..2c5b342bce 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -49,7 +49,7 @@ class Chef
To install a gem while #{ChefUtils::Dist::Infra::PRODUCT} is configuring the node (the converge phase), set the `compile_time` property to `false`:
```ruby
- chef_gem 'right_aws' do
+ chef_gem 'loofah' do
compile_time false
action :install
end
@@ -57,7 +57,7 @@ class Chef
To install a gem while the resource collection is being built (the compile phase), set the `compile_time` property to `true`:
```ruby
- chef_gem 'right_aws' do
+ chef_gem 'loofah' do
compile_time true
action :install
end
diff --git a/lib/chef/resource/chef_handler.rb b/lib/chef/resource/chef_handler.rb
index a006b2648a..099f95a52e 100644
--- a/lib/chef/resource/chef_handler.rb
+++ b/lib/chef/resource/chef_handler.rb
@@ -182,7 +182,7 @@ class Chef
property :arguments, [Array, Hash],
description: "Arguments to pass the handler's class initializer.",
- default: lazy { [] }
+ default: []
property :type, Hash,
description: "The type of handler to register as, i.e. :report, :exception or both.",
@@ -194,9 +194,7 @@ class Chef
# This action needs to find an rb file that presumably contains the indicated class in it and the
# load that file. It then instantiates that class by name and registers it as a handler.
- action :enable do
- description "Enables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node"
-
+ action :enable, description: "Enables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node." do
class_name = new_resource.class_name
new_resource.type.each do |type, enable|
next unless enable
@@ -218,9 +216,7 @@ class Chef
end
end
- action :disable do
- description "Disables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node"
-
+ action :disable, description: "Disables the handler for the current #{ChefUtils::Dist::Infra::PRODUCT} run on the current node." do
new_resource.type.each_key do |type|
unregister_handler(type, new_resource.class_name)
end
diff --git a/lib/chef/resource/chef_sleep.rb b/lib/chef/resource/chef_sleep.rb
index c6d3e45877..13f737c0c4 100644
--- a/lib/chef/resource/chef_sleep.rb
+++ b/lib/chef/resource/chef_sleep.rb
@@ -62,7 +62,7 @@ class Chef
coerce: proc { |s| Integer(s) },
name_property: true
- action :sleep do
+ action :sleep, description: "Pause the #{ChefUtils::Dist::Infra::PRODUCT} run for a specified number of seconds." do
converge_by("sleep #{new_resource.seconds} seconds") do
sleep(new_resource.seconds)
end
diff --git a/lib/chef/resource/chef_vault_secret.rb b/lib/chef/resource/chef_vault_secret.rb
index 1c8fa985f9..80e3f0f471 100644
--- a/lib/chef/resource/chef_vault_secret.rb
+++ b/lib/chef/resource/chef_vault_secret.rb
@@ -88,8 +88,7 @@ class Chef
end
- action :create do
- description "Creates the item, or updates it if it already exists."
+ action :create, description: "Creates the item, or updates it if it already exists." do
converge_if_changed do
item = ChefVault::Item.new(new_resource.data_bag, new_resource.id)
@@ -111,15 +110,11 @@ class Chef
end
end
- action :create_if_missing do
- description "Calls the create action unless it exists."
-
+ action :create_if_missing, description: "Calls the create action unless it exists." do
action_create if current_resource.nil?
end
- action :delete do
- description "Deletes the item and the item's keys ('id'_keys)."
-
+ action :delete, description: "Deletes the item and the item's keys ('id'_keys)." do
chef_data_bag_item new_resource.id do
data_bag new_resource.data_bag
action :delete
diff --git a/lib/chef/resource/chocolatey_config.rb b/lib/chef/resource/chocolatey_config.rb
index c4f100c28d..637d250e9b 100644
--- a/lib/chef/resource/chocolatey_config.rb
+++ b/lib/chef/resource/chocolatey_config.rb
@@ -68,9 +68,7 @@ class Chef
data ? data.attribute("value").to_s : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
end
- action :set do
- description "Sets a Chocolatey config value."
-
+ action :set, description: "Sets a Chocolatey config value." do
raise "#{new_resource}: When adding a Chocolatey config you must pass the 'value' property!" unless new_resource.value
converge_if_changed do
@@ -78,9 +76,7 @@ class Chef
end
end
- action :unset do
- description "Unsets a Chocolatey config value."
-
+ action :unset, description: "Unsets a Chocolatey config value." do
if current_resource
converge_by("unset Chocolatey config '#{new_resource.config_key}'") do
shell_out!(choco_cmd("unset"))
diff --git a/lib/chef/resource/chocolatey_feature.rb b/lib/chef/resource/chocolatey_feature.rb
index 66752fbd5d..b56d11376e 100644
--- a/lib/chef/resource/chocolatey_feature.rb
+++ b/lib/chef/resource/chocolatey_feature.rb
@@ -65,9 +65,7 @@ class Chef
data ? data.attribute("enabled").to_s : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
end
- action :enable do
- description "Enables a named Chocolatey feature."
-
+ action :enable, description: "Enables a named Chocolatey feature." do
if current_resource.feature_state != true
converge_by("enable Chocolatey feature '#{new_resource.feature_name}'") do
shell_out!(choco_cmd("enable"))
@@ -75,9 +73,7 @@ class Chef
end
end
- action :disable do
- description "Disables a named Chocolatey feature."
-
+ action :disable, description: "Disables a named Chocolatey feature." do
if current_resource.feature_state == true
converge_by("disable Chocolatey feature '#{new_resource.feature_name}'") do
shell_out!(choco_cmd("disable"))
diff --git a/lib/chef/resource/chocolatey_source.rb b/lib/chef/resource/chocolatey_source.rb
index cee4289682..1b287fc188 100644
--- a/lib/chef/resource/chocolatey_source.rb
+++ b/lib/chef/resource/chocolatey_source.rb
@@ -89,8 +89,7 @@ class Chef
data ? data.attributes : nil # REXML just returns nil if it can't find anything so avoid an undefined method error
end
- action :add do
- description "Adds a Chocolatey source."
+ action :add, description: "Adds a Chocolatey source" do
raise "#{new_resource}: When adding a Chocolatey source you must pass the 'source' property!" unless new_resource.source
@@ -99,8 +98,7 @@ class Chef
end
end
- action :remove do
- description "Removes a Chocolatey source."
+ action :remove, description: "Removes a Chocolatey source." do
if current_resource
converge_by("remove Chocolatey source '#{new_resource.source_name}'") do
@@ -109,9 +107,7 @@ class Chef
end
end
- action :disable do
- description "Disables a Chocolatey source."
-
+ action :disable, description: "Disables a Chocolatey source. **New in Chef Infra Client 15.1.**" do
if current_resource.disabled != true
converge_by("disable Chocolatey source '#{new_resource.source_name}'") do
shell_out!(choco_cmd("disable"))
@@ -119,9 +115,7 @@ class Chef
end
end
- action :enable do
- description "Enables a Chocolatey source."
-
+ action :enable, description: "Enables a Chocolatey source. **New in Chef Infra Client 15.1.**" do
if current_resource.disabled == true
converge_by("enable Chocolatey source '#{new_resource.source_name}'") do
shell_out!(choco_cmd("enable"))
diff --git a/lib/chef/resource/cron/_cron_shared.rb b/lib/chef/resource/cron/_cron_shared.rb
index 6d11035862..3dba74e77f 100644
--- a/lib/chef/resource/cron/_cron_shared.rb
+++ b/lib/chef/resource/cron/_cron_shared.rb
@@ -61,7 +61,7 @@ property :user, String,
property :environment, Hash,
description: "A Hash containing additional arbitrary environment variables under which the cron job will be run in the form of `({'ENV_VARIABLE' => 'VALUE'})`. **Note**: These variables must exist for a command to be run successfully.",
- default: lazy { {} }
+ default: {}
property :time_out, Hash,
description: "A Hash of timeouts in the form of `({'OPTION' => 'VALUE'})`. Accepted valid options are:
@@ -69,7 +69,7 @@ property :time_out, Hash,
- `foreground` (BOOL, default: 'false'),
- `kill-after` (in seconds),
- `signal` (a name like 'HUP' or a number)",
- default: lazy { {} },
+ default: {},
introduced: "15.7",
coerce: proc { |h|
if h.is_a?(Hash)
diff --git a/lib/chef/resource/cron/cron_d.rb b/lib/chef/resource/cron/cron_d.rb
index 6262ef453a..2806f55415 100644
--- a/lib/chef/resource/cron/cron_d.rb
+++ b/lib/chef/resource/cron/cron_d.rb
@@ -29,7 +29,7 @@ class Chef
provides :cron_d
introduced "14.4"
- description "Use the **cron_d** resource to manage cron job files in the /etc/cron.d directory. This is similar to the 'cron' resource, but it does not use the monolithic /etc/crontab file."
+ description "Use the **cron_d** resource to manage cron job files in the `/etc/cron.d` directory. This is similar to the 'cron' resource, but it does not use the monolithic /etc/crontab file."
examples <<~DOC
**Run a program on the fifth hour of the day**
@@ -116,19 +116,17 @@ class Chef
end
action :create do
- description "Add a cron definition file to /etc/cron.d."
+ description "Add a cron definition file to `/etc/cron.d`."
create_template(:create)
end
- action :create_if_missing do
- description "Add a cron definition file to /etc/cron.d, but do not update an existing file."
+ action :create_if_missing, description: "Add a cron definition file to `/etc/cron.d`, but do not update an existing file." do
create_template(:create_if_missing)
end
- action :delete do
- description "Remove a cron definition file from /etc/cron.d if it exists."
+ action :delete, description: "Remove a cron definition file from `/etc/cron.d` if it exists." do
# cleanup the legacy named job if it exists
file "legacy named cron.d file" do
@@ -160,6 +158,7 @@ class Chef
source ::File.expand_path("../support/cron.d.erb", __dir__)
local true
mode new_resource.mode
+ sensitive new_resource.sensitive
variables(
name: sanitized_name,
predefined_value: new_resource.predefined_value,
diff --git a/lib/chef/resource/cron_access.rb b/lib/chef/resource/cron_access.rb
index 3ea777ce9c..233931e64a 100644
--- a/lib/chef/resource/cron_access.rb
+++ b/lib/chef/resource/cron_access.rb
@@ -64,8 +64,7 @@ class Chef
"default" => "/etc",
}.freeze
- action :allow do
- description "Add the user to the cron.allow file."
+ action :allow, description: "Add the user to the cron.allow file." do
allow_path = ::File.join(value_for_platform_family(CRON_PATHS), "cron.allow")
with_run_context :root do
@@ -81,8 +80,7 @@ class Chef
end
end
- action :deny do
- description "Add the user to the cron.deny file."
+ action :deny, description: "Add the user to the cron.deny file." do
deny_path = ::File.join(value_for_platform_family(CRON_PATHS), "cron.deny")
with_run_context :root do
diff --git a/lib/chef/resource/dmg_package.rb b/lib/chef/resource/dmg_package.rb
index c6cd04156c..9f898e92ea 100644
--- a/lib/chef/resource/dmg_package.rb
+++ b/lib/chef/resource/dmg_package.rb
@@ -121,9 +121,7 @@ class Chef
end
end
- action :install do
- description "Installs the application."
-
+ action :install, description: "Installs the application." do
if current_resource.nil?
if new_resource.source
remote_file dmg_file do
@@ -171,13 +169,11 @@ class Chef
action_class do
# @return [String] the path to the dmg file
def dmg_file
- @dmg_file ||= begin
- if new_resource.file.nil?
- "#{Chef::Config[:file_cache_path]}/#{new_resource.dmg_name}.dmg"
- else
- new_resource.file
- end
- end
+ @dmg_file ||= if new_resource.file.nil?
+ "#{Chef::Config[:file_cache_path]}/#{new_resource.dmg_name}.dmg"
+ else
+ new_resource.file
+ end
end
# @return [String] the hdiutil flag for handling DMGs with a password
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 466b17d702..1ad460e591 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -36,7 +36,7 @@ class Chef
property :response_file_variables, Hash,
description: "A Hash of response file variables in the form of {'VARIABLE' => 'VALUE'}.",
- default: lazy { {} }, desired_state: false
+ default: {}, desired_state: false
end
end
end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 679deef47b..a869e2acc4 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -74,7 +74,7 @@ class Chef
property :module_version, String,
introduced: "12.21",
- description: "The version number of the module to use. PowerShell 5.0.10018.0 (or higher) supports having multiple versions of a module installed. This should be specified along with the module_name."
+ description: "The version number of the module to use. PowerShell 5.0.10018.0 (or higher) supports having multiple versions of a module installed. This should be specified along with the `module_name` property."
def property(property_name, value = nil)
unless property_name.is_a?(Symbol)
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 5a78160642..1f7e1b2c1b 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -304,9 +304,9 @@ class Chef
gives a recipe full control over the command issued in a much cleaner, more
direct manner.
- **Use the search recipe DSL method to find users**:
+ **Use the search Infra Language helper to find users**:
- The following example shows how to use the `search` method in the Recipe DSL to
+ The following example shows how to use the `search` method in the Chef Infra Language to
search for users:
```ruby
@@ -515,7 +515,7 @@ class Chef
property :command, [ String, Array ],
name_property: true,
- description: "An optional property to set the command to be executed if it differs from the resource block's name."
+ description: "An optional property to set the command to be executed if it differs from the resource block's name. Note: Use the **execute** resource to run a single command. Use multiple **execute** resource blocks to run multiple commands."
property :umask, [ String, Integer ],
description: "The file mode creation mask, or umask."
@@ -572,6 +572,10 @@ class Chef
introduced: "16.2",
description: "An optional property to set the input sent to the command as STDIN."
+ property :login, [ TrueClass, FalseClass ], default: false,
+ introduced: "17.0",
+ description: "Use a login shell to run the commands instead of inheriting the existing execution environment."
+
alias :env :environment
def self.set_guard_inherited_attributes(*inherited_attributes)
@@ -629,13 +633,13 @@ class Chef
end
# if domain is provided in both username and domain
- if specified_user.is_a?(String) && ((specified_user.include? '\\') || (specified_user.include? "@")) && specified_domain
+ if specified_user.is_a?(String) && ((specified_user.include? "\\") || (specified_user.include? "@")) && specified_domain
raise ArgumentError, "The domain is provided twice. Username: `#{specified_user}`, Domain: `#{specified_domain}`. Please specify domain only once."
end
if specified_user.is_a?(String) && specified_domain.nil?
# Splitting username of format: Domain\Username
- domain_and_user = user.split('\\')
+ domain_and_user = user.split("\\")
if domain_and_user.length == 2
domain = domain_and_user[0]
@@ -666,7 +670,8 @@ class Chef
:group,
:password,
:user,
- :umask
+ :umask,
+ :login
)
end
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 214f8018ab..05cb4658d5 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -32,7 +32,7 @@ class Chef
provides :file
- description "Use the **file** resource to manage files directly on a node."
+ description "Use the **file** resource to manage files directly on a node. Note: Use the **cookbook_file** resource to copy a file from a cookbook's `/files` directory. Use the **template** resource to create a file based on a template in a cookbook's `/templates` directory. And use the **remote_file** resource to transfer a file to a node from a remote location."
if ChefUtils.windows?
# Use Windows rights instead of standard *nix permissions
@@ -81,7 +81,7 @@ class Chef
property :manage_symlink_source, [ TrueClass, FalseClass ], desired_state: false,
description: "Change the behavior of the file resource if it is pointed at a symlink. When this value is set to true, #{ChefUtils::Dist::Infra::PRODUCT} will manage the symlink's permissions or will replace the symlink with a normal file if the resource has content. When this value is set to false, #{ChefUtils::Dist::Infra::PRODUCT} will follow the symlink and will manage the permissions and content of symlink's target file. The default behavior is true but emits a warning that the default value will be changed to false in a future version; setting this explicitly to true or false suppresses this warning."
- property :verifications, Array, default: lazy { [] }
+ property :verifications, Array, default: lazy { [] }, desired_state: false, skip_docs: true
def verify(command = nil, opts = {}, &block)
unless command.nil? || [String, Symbol].include?(command.class)
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index c893e7a2f9..a3ad5f614b 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -44,8 +44,8 @@ class Chef
**Install a gem file from the local file system**
```ruby
- gem_package 'right_aws' do
- source '/tmp/right_aws-1.11.0.gem'
+ gem_package 'loofah' do
+ source '/tmp/loofah-2.7.0.gem'
action :install
end
```
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index 3a129592d0..545cd0b35d 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -37,22 +37,22 @@ class Chef
property :gid, [ String, Integer ],
description: "The identifier for the group."
- property :members, [String, Array], default: lazy { [] },
+ property :members, [String, Array], default: [],
coerce: proc { |arg| arg.is_a?(String) ? arg.split(/\s*,\s*/) : arg },
description: "Which users should be set or appended to a group. When more than one group member is identified, the list of members should be an array: members ['user1', 'user2']."
- property :excluded_members, [String, Array], default: lazy { [] },
+ property :excluded_members, [String, Array], default: [],
coerce: proc { |arg| arg.is_a?(String) ? arg.split(/\s*,\s*/) : arg },
- description: "Remove users from a group. May only be used when append is set to true."
+ description: "Remove users from a group. May only be used when `append` is set to `true`."
property :append, [ TrueClass, FalseClass ], default: false,
- description: "How members should be appended and/or removed from a group. When true, members are appended and excluded_members are removed. When false, group members are reset to the value of the members property."
+ description: "How members should be appended and/or removed from a group. When true, `members` are appended and `excluded_members` are removed. When `false`, group members are reset to the value of the `members` property."
property :system, [ TrueClass, FalseClass ], default: false,
- description: "Set if a group belongs to a system group. Set to true if the group belongs to a system group."
+ description: "Set to `true` if the group belongs to a system group."
property :non_unique, [ TrueClass, FalseClass ], default: false,
- description: "Allow gid duplication. May only be used with the Groupadd provider."
+ description: "Allow gid duplication. May only be used with the `Groupadd` user resource provider."
property :comment, String,
introduced: "14.9",
diff --git a/lib/chef/resource/homebrew_cask.rb b/lib/chef/resource/homebrew_cask.rb
index 3b0974348c..7a862e8638 100644
--- a/lib/chef/resource/homebrew_cask.rb
+++ b/lib/chef/resource/homebrew_cask.rb
@@ -51,16 +51,20 @@ class Chef
property :owner, [String, Integer],
description: "The owner of the Homebrew installation.",
- default: lazy { find_homebrew_username }
-
- action :install do
- description "Install an application packaged as a Homebrew cask."
-
- homebrew_tap "homebrew/cask" if new_resource.install_cask
+ default: lazy { find_homebrew_username },
+ default_description: "Calculated default username"\
+
+ action :install, description: "Install an application that is packaged as a Homebrew cask." do
+ if new_resource.install_cask
+ homebrew_tap "homebrew/cask" do
+ homebrew_path new_resource.homebrew_path
+ owner new_resource.owner
+ end
+ end
unless casked?
converge_by("install cask #{new_resource.cask_name} #{new_resource.options}") do
- shell_out!("#{new_resource.homebrew_path} cask install #{new_resource.cask_name} #{new_resource.options}",
+ shell_out!("#{new_resource.homebrew_path} install --cask #{new_resource.cask_name} #{new_resource.options}",
user: new_resource.owner,
env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
cwd: ::Dir.home(new_resource.owner))
@@ -68,14 +72,17 @@ class Chef
end
end
- action :remove do
- description "Remove an application packaged as a Homebrew cask."
-
- homebrew_tap "homebrew/cask" if new_resource.install_cask
+ action :remove, description: "Remove an application that is packaged as a Homebrew cask." do
+ if new_resource.install_cask
+ homebrew_tap "homebrew/cask" do
+ homebrew_path new_resource.homebrew_path
+ owner new_resource.owner
+ end
+ end
if casked?
converge_by("uninstall cask #{new_resource.cask_name}") do
- shell_out!("#{new_resource.homebrew_path} cask uninstall #{new_resource.cask_name}",
+ shell_out!("#{new_resource.homebrew_path} uninstall --cask #{new_resource.cask_name}",
user: new_resource.owner,
env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
cwd: ::Dir.home(new_resource.owner))
@@ -93,7 +100,7 @@ class Chef
# @return [Boolean]
def casked?
unscoped_name = new_resource.cask_name.split("/").last
- shell_out!("#{new_resource.homebrew_path} cask list 2>/dev/null",
+ shell_out!("#{new_resource.homebrew_path} list --cask 2>/dev/null",
user: new_resource.owner,
env: { "HOME" => ::Dir.home(new_resource.owner), "USER" => new_resource.owner },
cwd: ::Dir.home(new_resource.owner)).stdout.split.include?(unscoped_name)
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 3874622005..040eff9f59 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -62,7 +62,7 @@ class Chef
DOC
property :homebrew_user, [ String, Integer ],
- description: "The name or uid of the Homebrew owner to be used by #{ChefUtils::Dist::Infra::PRODUCT} when executing a command."
+ description: "The name or uid of the Homebrew owner to be used by #{ChefUtils::Dist::Infra::PRODUCT} when executing a command.\n\n#{ChefUtils::Dist::Infra::PRODUCT}, by default, will attempt to execute a Homebrew command as the owner of the `/usr/local/bin/brew` executable. If that executable does not exist, #{ChefUtils::Dist::Infra::PRODUCT} will attempt to find the user by executing `which brew`. If that executable cannot be found, #{ChefUtils::Dist::Infra::PRODUCT} will print an error message: `Could not find the 'brew' executable in /usr/local/bin or anywhere on the path.`.\n\nSet this property to specify the Homebrew owner for situations where Chef Infra Client cannot automatically detect the correct owner.'"
end
end
diff --git a/lib/chef/resource/homebrew_tap.rb b/lib/chef/resource/homebrew_tap.rb
index 937a9ab420..05567c5166 100644
--- a/lib/chef/resource/homebrew_tap.rb
+++ b/lib/chef/resource/homebrew_tap.rb
@@ -51,11 +51,10 @@ class Chef
property :owner, String,
description: "The owner of the Homebrew installation.",
- default: lazy { find_homebrew_username }
-
- action :tap do
- description "Add a Homebrew tap."
+ default: lazy { find_homebrew_username },
+ default_description: "Calculated default username"
+ action :tap, description: "Add a Homebrew tap." do
unless tapped?(new_resource.tap_name)
converge_by("tap #{new_resource.tap_name}") do
shell_out!("#{new_resource.homebrew_path} tap #{new_resource.full ? "--full" : ""} #{new_resource.tap_name} #{new_resource.url || ""}",
@@ -66,9 +65,7 @@ class Chef
end
end
- action :untap do
- description "Remove a Homebrew tap."
-
+ action :untap, description: "Remove a Homebrew tap." do
if tapped?(new_resource.tap_name)
converge_by("untap #{new_resource.tap_name}") do
shell_out!("#{new_resource.homebrew_path} untap #{new_resource.tap_name}",
diff --git a/lib/chef/resource/homebrew_update.rb b/lib/chef/resource/homebrew_update.rb
index 27b352bfb6..ee353f1fe5 100644
--- a/lib/chef/resource/homebrew_update.rb
+++ b/lib/chef/resource/homebrew_update.rb
@@ -88,7 +88,7 @@ class Chef
end
end
- action :periodic do
+ action :periodic, description: "Run a periodic update based on the frequency property." do
return unless macos?
unless brew_up_to_date?
@@ -98,7 +98,7 @@ class Chef
end
end
- action :update do
+ action :update, description: "Run an immediate update." do
return unless macos?
converge_by "force update new lists of packages" do
diff --git a/lib/chef/resource/hostname.rb b/lib/chef/resource/hostname.rb
index 27903e5807..ab4ab73961 100644
--- a/lib/chef/resource/hostname.rb
+++ b/lib/chef/resource/hostname.rb
@@ -44,12 +44,34 @@ class Chef
ipaddress '198.51.100.2'
end
```
+
+ **Change the hostname of a Windows, Non-Domain joined node**:
+
+ ```ruby
+ hostname 'renaming a workgroup computer' do
+ hostname 'Foo'
+ end
+ ```
+
+ **Change the hostname of a Windows, Domain-joined node (new in 17.2)**:
+
+ ```ruby
+ hostname 'renaming a domain-joined computer' do
+ hostname 'Foo'
+ domain_user "Domain\\Someone"
+ domain_password 'SomePassword'
+ end
+ ```
DOC
property :hostname, String,
description: "An optional property to set the hostname if it differs from the resource block's name.",
name_property: true
+ property :fqdn, String,
+ description: "An optional property to set the fqdn if it differs from the resource block's hostname.",
+ introduced: "17.0"
+
property :ipaddress, String,
description: "The IP address to use when configuring the hosts file.",
default: lazy { node["ipaddress"] }, default_description: "The node's IP address as determined by Ohai."
@@ -67,6 +89,15 @@ class Chef
description: "Determines whether or not Windows should be reboot after changing the hostname, as this is required for the change to take effect.",
default: true
+ property :domain_user, String,
+ description: "A domain account specified in the form of DOMAIN\\user used when renaming a domain-joined device",
+ introduced: "17.2"
+
+ property :domain_password, String,
+ description: "The password to accompany the domain_user parameter",
+ sensitive: true,
+ introduced: "17.2"
+
action_class do
def append_replacing_matching_lines(path, regex, string)
text = IO.read(path).split("\n")
@@ -99,9 +130,11 @@ class Chef
end
end
- action :set do
- description "Sets the node's hostname."
+ def is_domain_joined?
+ powershell_exec!("(Get-CIMInstance -Class Win32_ComputerSystem).PartofDomain").result
+ end
+ action :set, description: "Sets the node's hostname." do
if !windows?
ohai "reload hostname" do
plugin "hostname"
@@ -109,7 +142,7 @@ class Chef
end
# set the hostname via /bin/hostname
- declare_resource(:execute, "set hostname to #{new_resource.hostname}") do
+ execute "set hostname to #{new_resource.hostname}" do
command "/bin/hostname #{new_resource.hostname}"
not_if { shell_out!("hostname").stdout.chomp == new_resource.hostname }
notifies :reload, "ohai[reload hostname]"
@@ -117,7 +150,9 @@ class Chef
# make sure node['fqdn'] resolves via /etc/hosts
unless new_resource.ipaddress.nil?
- newline = "#{new_resource.ipaddress} #{new_resource.hostname}"
+ newline = "#{new_resource.ipaddress}"
+ newline << " #{new_resource.fqdn}" unless new_resource.fqdn.to_s.empty?
+ newline << " #{new_resource.hostname}"
newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty?
newline << " #{new_resource.hostname[/[^\.]*/]}"
r = append_replacing_matching_lines("/etc/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline)
@@ -127,22 +162,22 @@ class Chef
# setup the hostname to persist on a reboot
case
- when ::File.exist?("/usr/sbin/scutil")
+ when darwin?
# darwin
- declare_resource(:execute, "set HostName via scutil") do
+ execute "set HostName via scutil" do
command "/usr/sbin/scutil --set HostName #{new_resource.hostname}"
- not_if { shell_out!("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname }
+ not_if { shell_out("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname }
notifies :reload, "ohai[reload hostname]"
end
- declare_resource(:execute, "set ComputerName via scutil") do
+ execute "set ComputerName via scutil" do
command "/usr/sbin/scutil --set ComputerName #{new_resource.hostname}"
- not_if { shell_out!("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname }
+ not_if { shell_out("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname }
notifies :reload, "ohai[reload hostname]"
end
shortname = new_resource.hostname[/[^\.]*/]
- declare_resource(:execute, "set LocalHostName via scutil") do
+ execute "set LocalHostName via scutil" do
command "/usr/sbin/scutil --set LocalHostName #{shortname}"
- not_if { shell_out!("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname }
+ not_if { shell_out("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname }
notifies :reload, "ohai[reload hostname]"
end
when linux?
@@ -150,7 +185,7 @@ class Chef
when ::File.exist?("/usr/bin/hostnamectl") && !docker?
# use hostnamectl whenever we find it on linux (as systemd takes over the world)
# this must come before other methods like /etc/hostname and /etc/sysconfig/network
- declare_resource(:execute, "hostnamectl set-hostname #{new_resource.hostname}") do
+ execute "hostnamectl set-hostname #{new_resource.hostname}" do
notifies :reload, "ohai[reload hostname]"
not_if { shell_out!("hostnamectl status", returns: [0, 1]).stdout =~ /Static hostname:\s*#{new_resource.hostname}\s*$/ }
end
@@ -160,7 +195,7 @@ class Chef
# the "platform: iox_xr, platform_family: wrlinux, os: linux" platform also hits this
# the "platform: nexus, platform_family: wrlinux, os: linux" platform also hits this
# this is also fallback for any linux systemd host in a docker container (where /usr/bin/hostnamectl will fail)
- declare_resource(:file, "/etc/hostname") do
+ file "/etc/hostname" do
atomic_update false if docker?
content "#{new_resource.hostname}\n"
owner "root"
@@ -172,7 +207,7 @@ class Chef
append_replacing_matching_lines("/etc/sysconfig/network", /^HOSTNAME\s*=/, "HOSTNAME=#{new_resource.hostname}")
when ::File.exist?("/etc/HOSTNAME")
# SuSE/openSUSE uses /etc/HOSTNAME
- declare_resource(:file, "/etc/HOSTNAME") do
+ file "/etc/HOSTNAME" do
content "#{new_resource.hostname}\n"
owner "root"
group node["root_group"]
@@ -180,7 +215,7 @@ class Chef
end
when ::File.exist?("/etc/conf.d/hostname")
# Gentoo
- declare_resource(:file, "/etc/conf.d/hostname") do
+ file "/etc/conf.d/hostname" do
content "hostname=\"#{new_resource.hostname}\"\n"
owner "root"
group node["root_group"]
@@ -196,23 +231,23 @@ class Chef
# *BSD systems with /etc/rc.conf + /etc/myname
append_replacing_matching_lines("/etc/rc.conf", /^\s+hostname\s+=/, "hostname=#{new_resource.hostname}")
- declare_resource(:file, "/etc/myname") do
+ file "/etc/myname" do
content "#{new_resource.hostname}\n"
owner "root"
group node["root_group"]
mode "0644"
end
when ::File.exist?("/usr/sbin/svccfg") # solaris 5.11
- declare_resource(:execute, "svccfg -s system/identity:node setprop config/nodename=\'#{new_resource.hostname}\'") do
+ execute "svccfg -s system/identity:node setprop config/nodename=\'#{new_resource.hostname}\'" do
notifies :run, "execute[svcadm refresh]", :immediately
notifies :run, "execute[svcadm restart]", :immediately
not_if { shell_out!("svccfg -s system/identity:node listprop config/nodename").stdout.chomp =~ %r{config/nodename\s+astring\s+#{new_resource.hostname}} }
end
- declare_resource(:execute, "svcadm refresh") do
+ execute "svcadm refresh" do
command "svcadm refresh system/identity:node"
action :nothing
end
- declare_resource(:execute, "svcadm restart") do
+ execute "svcadm restart" do
command "svcadm restart system/identity:node"
action :nothing
end
@@ -239,13 +274,24 @@ class Chef
end
unless Socket.gethostbyname(Socket.gethostname).first == new_resource.hostname
- converge_by "set hostname to #{new_resource.hostname}" do
- powershell_exec! <<~EOH
- $sysInfo = Get-WmiObject -Class Win32_ComputerSystem
- $sysInfo.Rename("#{new_resource.hostname}")
- EOH
+ if is_domain_joined?
+ if new_resource.domain_user.nil? || new_resource.domain_password.nil?
+ raise "The `domain_user` and `domain_password` properties are required to change the hostname of a domain-connected Windows system."
+ else
+ converge_by "set hostname to #{new_resource.hostname}" do
+ powershell_exec! <<~EOH
+ $user = #{new_resource.domain_user}
+ $secure_password = #{new_resource.domain_password} | Convertto-SecureString -AsPlainText -Force
+ $Credentials = New-Object System.Management.Automation.PSCredential -Argumentlist ($user, $secure_password)
+ Rename-Computer -NewName #{new_resource.hostname} -DomainCredential $Credentials
+ EOH
+ end
+ end
+ else
+ converge_by "set hostname to #{new_resource.hostname}" do
+ powershell_exec!("Rename-Computer -NewName #{new_resource.hostname}")
+ end
end
-
# reboot because $windows
reboot "setting hostname" do
reason "#{ChefUtils::Dist::Infra::PRODUCT} updated system hostname"
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index de714ab4ab..8a1354e6cd 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -26,7 +26,7 @@ class Chef
provides :http_request
- description "Use the **http_request** resource to send an HTTP request (GET, PUT, POST, DELETE, HEAD, or OPTIONS) with an arbitrary message. This resource is often useful when custom callbacks are necessary."
+ description "Use the **http_request** resource to send an HTTP request (`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, or `OPTIONS`) with an arbitrary message. This resource is often useful when custom callbacks are necessary."
default_action :get
allowed_actions :get, :patch, :put, :post, :delete, :head, :options
@@ -34,7 +34,7 @@ class Chef
property :url, String, identity: true,
description: "The URL to which an HTTP request is sent."
- property :headers, Hash, default: lazy { {} },
+ property :headers, Hash, default: {},
description: "A Hash of custom headers."
def initialize(name, run_context = nil)
diff --git a/lib/chef/resource/inspec_waiver_file_entry.rb b/lib/chef/resource/inspec_waiver_file_entry.rb
new file mode 100644
index 0000000000..fb433edb28
--- /dev/null
+++ b/lib/chef/resource/inspec_waiver_file_entry.rb
@@ -0,0 +1,156 @@
+#
+# Author:: Davin Taddeo (<davin@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
+#
+# 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 "../resource"
+autoload :YAML, "yaml"
+require "date"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+
+class Chef
+ class Resource
+ class InspecWaiverFileEntry < Chef::Resource
+ provides :inspec_waiver_file_entry
+ unified_mode true
+
+ description "Use the **inspec_waiver_file_entry** resource to add or remove entries from an InSpec waiver file. This can be used in conjunction with the Compliance Phase."
+ introduced "17.1"
+ examples <<~DOC
+ **Add an InSpec waiver entry to a given waiver file**:
+
+ ```ruby
+ inspec_waiver_file_entry 'Add waiver entry for control' do
+ file_path 'C:\\chef\\inspec_waiver_file.yml'
+ control 'my_inspec_control_01'
+ run_test false
+ justification "The subject of this control is not managed by #{ChefUtils::Dist::Infra::PRODUCT} on the systems in policy group \#{node['policy_group']}"
+ expiration '2022-01-01'
+ action :add
+ end
+ ```
+
+ **Add an InSpec waiver entry to a given waiver file using the 'name' property to identify the control**:
+
+ ```ruby
+ inspec_waiver_file_entry 'my_inspec_control_01' do
+ justification "The subject of this control is not managed by #{ChefUtils::Dist::Infra::PRODUCT} on the systems in policy group \#{node['policy_group']}"
+ action :add
+ end
+ ```
+
+ **Remove an InSpec waiver entry to a given waiver file**:
+
+ ```ruby
+ inspec_waiver_file_entry "my_inspec_control_01" do
+ action :remove
+ end
+ ```
+ DOC
+
+ property :control, String,
+ name_property: true,
+ description: "The name of the control being added or removed to the waiver file"
+
+ property :file_path, String,
+ required: true,
+ description: "The path to the waiver file being modified",
+ default: "#{ChefConfig::Config.etc_chef_dir}/inspec_waivers.yml",
+ default_description: "`/etc/chef/inspec_waivers.yml` on Linux/Unix and `C:\\chef\\inspec_waivers.yml` on Windows"
+
+ property :expiration, String,
+ description: "The expiration date of the given waiver - provided in YYYY-MM-DD format",
+ callbacks: {
+ "Expiration date should be a valid calendar date and match the following format: YYYY-MM-DD" => proc { |e|
+ re = Regexp.new('\d{4}-\d{2}-\d{2}$').freeze
+ if re.match?(e)
+ Date.valid_date?(*e.split("-").map(&:to_i))
+ else
+ e.nil?
+ end
+ },
+ }
+
+ property :run_test, [true, false],
+ description: "If present and true, the control will run and be reported, but failures in it won’t make the overall run fail. If absent or false, the control will not be run."
+
+ property :justification, String,
+ description: "Can be any text you want and might include a reason for the waiver as well as who signed off on the waiver."
+
+ property :backup, [false, Integer],
+ description: "The number of backups to be kept in /var/chef/backup (for UNIX- and Linux-based platforms) or C:/chef/backup (for the Microsoft Windows platform). Set to false to prevent backups from being kept.",
+ default: false
+
+ action :add do
+ if new_resource.justification.nil? || new_resource.justification == ""
+ raise Chef::Exceptions::ValidationFailed, "Entries in the InSpec waiver file must have a justification given, this parameter must have a value."
+ end
+
+ filename = new_resource.file_path
+ waiver_hash = load_waiver_file_to_hash(filename)
+ control_hash = {}
+ control_hash["expiration_date"] = new_resource.expiration.to_s unless new_resource.expiration.nil?
+ control_hash["run"] = new_resource.run_test unless new_resource.run_test.nil?
+ control_hash["justification"] = new_resource.justification.to_s
+
+ unless waiver_hash[new_resource.control] == control_hash
+ waiver_hash[new_resource.control] = control_hash
+ waiver_hash = waiver_hash.sort.to_h
+
+ file "Update Waiver File #{new_resource.file_path} to update waiver for control #{new_resource.control}" do
+ path new_resource.file_path
+ content ::YAML.dump(waiver_hash)
+ backup new_resource.backup
+ action :create
+ end
+ end
+ end
+
+ action :remove do
+ filename = new_resource.file_path
+ waiver_hash = load_waiver_file_to_hash(filename)
+ if waiver_hash.key?(new_resource.control)
+ waiver_hash.delete(new_resource.control)
+ waiver_hash = waiver_hash.sort.to_h
+ file "Update Waiver File #{new_resource.file_path} to remove waiver for control #{new_resource.control}" do
+ path new_resource.file_path
+ content ::YAML.dump(waiver_hash)
+ backup new_resource.backup
+ action :create
+ end
+ end
+ end
+
+ action_class do
+ def load_waiver_file_to_hash(file_name)
+ if file_name =~ %r{(/|C:\\).*(.yaml|.yml)}i
+ if ::File.exist?(file_name)
+ hash = ::YAML.load_file(file_name)
+ if hash == false || hash.nil? || hash == ""
+ {}
+ else
+ ::YAML.load_file(file_name)
+ end
+ else
+ {}
+ end
+ else
+ raise "Waiver files needs to be a YAML file which should have a .yaml or .yml extension -\"#{file_name}\" does not have an appropriate extension"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/kernel_module.rb b/lib/chef/resource/kernel_module.rb
index 485511470e..c4a36af9eb 100644
--- a/lib/chef/resource/kernel_module.rb
+++ b/lib/chef/resource/kernel_module.rb
@@ -93,9 +93,7 @@ class Chef
description: "The modprobe.d directory.",
default: "/etc/modprobe.d"
- action :install do
- description "Load kernel module, and ensure it loads on reboot."
-
+ action :install, description: "Load kernel module, and ensure it loads on reboot." do
with_run_context :root do
find_resource(:execute, "update initramfs") do
command initramfs_command
@@ -123,8 +121,7 @@ class Chef
end
end
- action :uninstall do
- description "Unload a kernel module and remove module config, so it doesn't load on reboot."
+ action :uninstall, description: "Unload a kernel module and remove module config, so it doesn't load on reboot." do
with_run_context :root do
find_resource(:execute, "update initramfs") do
command initramfs_command
@@ -149,9 +146,7 @@ class Chef
action_unload
end
- action :blacklist do
- description "Blacklist a kernel module."
-
+ action :blacklist, description: "Blacklist a kernel module." do
with_run_context :root do
find_resource(:execute, "update initramfs") do
command initramfs_command
@@ -167,9 +162,7 @@ class Chef
action_unload
end
- action :disable do
- description "Disable a kernel module."
-
+ action :disable, description: "Disable a kernel module. **New in Chef Infra Client 15.2.**" do
with_run_context :root do
find_resource(:execute, "update initramfs") do
command initramfs_command
@@ -185,9 +178,7 @@ class Chef
action_unload
end
- action :load do
- description "Load a kernel module."
-
+ action :load, description: "Load a kernel module." do
unless module_loaded?
converge_by("load kernel module #{new_resource.modname}") do
shell_out!("modprobe #{new_resource.modname}")
@@ -195,9 +186,7 @@ class Chef
end
end
- action :unload do
- description "Unload kernel module."
-
+ action :unload, description: "Unload kernel module." do
if module_loaded?
converge_by("unload kernel module #{new_resource.modname}") do
shell_out!("modprobe -r #{new_resource.modname}")
diff --git a/lib/chef/resource/locale.rb b/lib/chef/resource/locale.rb
index 5e4a63c06b..90377ef93a 100644
--- a/lib/chef/resource/locale.rb
+++ b/lib/chef/resource/locale.rb
@@ -49,7 +49,7 @@ class Chef
property :lc_env, Hash,
description: "A Hash of LC_* env variables in the form of `({ 'LC_ENV_VARIABLE' => 'VALUE' })`.",
- default: lazy { {} },
+ default: {},
coerce: proc { |h|
if h.respond_to?(:keys)
invalid_keys = h.keys - LC_VARIABLES
@@ -97,8 +97,7 @@ class Chef
powershell_exec("Get-WinSystemLocale").result["Name"]
end
- action :update do
- description "Update the system's locale."
+ action :update, description: "Update the system's locale." do
converge_if_changed do
set_system_locale
end
@@ -125,7 +124,7 @@ class Chef
# @raise [Mixlib::ShellOut::ShellCommandFailed] not a supported language or locale
#
def generate_locales
- shell_out!("locale-gen #{unavailable_locales.join(" ")}")
+ shell_out!("locale-gen #{unavailable_locales.join(" ")}", timeout: 1800)
end
# Sets the system locale for the current computer.
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index 2cf891d530..1284572339 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -26,6 +26,7 @@ require_relative "../exceptions"
require_relative "../mixin/convert_to_class_name"
require_relative "../mixin/from_file"
require_relative "../mixin/params_validate" # for DelayedEvaluator
+require_relative "../version"
class Chef
class Resource
@@ -53,6 +54,10 @@ class Chef
resource_class.run_context = run_context
resource_class.class_from_file(filename)
+ if !resource_class.unified_mode && !deprecated_class(resource_class)
+ Chef.deprecated :unified_mode, "The #{resource_class.resource_name} resource in the #{cookbook_name} cookbook should declare `unified_mode true`", filename
+ end
+
# Make a useful string for the class (rather than <Class:312894723894>)
resource_class.instance_eval do
define_singleton_method(:to_s) do
@@ -117,6 +122,20 @@ class Chef
superclass.respond_to?(m) ? superclass.send(m) : default
end
+
+ # Return true if the resource has been deprecated on this version.
+ #
+ # XXX: for now we only look at chef_version_for_provides, reversing the
+ # resource node_map to determine if the resource provides anything which is
+ # wired up is difficult.
+ #
+ def deprecated_class(resource_class)
+ if resource_class.chef_version_for_provides && Chef::VERSION !~ resource_class.chef_version_for_provides
+ return true
+ end
+
+ false
+ end
end
end
end
diff --git a/lib/chef/resource/macos_userdefaults.rb b/lib/chef/resource/macos_userdefaults.rb
index a150aeb9ed..036bd96d8b 100644
--- a/lib/chef/resource/macos_userdefaults.rb
+++ b/lib/chef/resource/macos_userdefaults.rb
@@ -97,29 +97,29 @@ class Chef
desired_state: false
property :sudo, [TrueClass, FalseClass],
- description: "Set to true if the setting you wish to modify requires privileged access. This requires passwordless sudo for the '/usr/bin/defaults' command to be setup for the user running #{ChefUtils::Dist::Infra::PRODUCT}.",
+ description: "Set to true if the setting you wish to modify requires privileged access. This requires passwordless sudo for the `/usr/bin/defaults` command to be setup for the user running #{ChefUtils::Dist::Infra::PRODUCT}.",
default: false,
desired_state: false
- load_current_value do |desired|
- Chef::Log.debug "#load_current_value: shelling out \"#{defaults_export_cmd(desired).join(" ")}\" to determine state"
- state = shell_out(defaults_export_cmd(desired), user: desired.user)
+ load_current_value do |new_resource|
+ Chef::Log.debug "#load_current_value: shelling out \"#{defaults_export_cmd(new_resource).join(" ")}\" to determine state"
+ state = shell_out(defaults_export_cmd(new_resource), user: new_resource.user)
if state.error? || state.stdout.empty?
- Chef::Log.debug "#load_current_value: #{defaults_export_cmd(desired).join(" ")} returned stdout: #{state.stdout} and stderr: #{state.stderr}"
+ Chef::Log.debug "#load_current_value: #{defaults_export_cmd(new_resource).join(" ")} returned stdout: #{state.stdout} and stderr: #{state.stderr}"
current_value_does_not_exist!
end
plist_data = ::Plist.parse_xml(state.stdout)
# handle the situation where the key doesn't exist in the domain
- if plist_data.key?(desired.key)
- key desired.key
+ if plist_data.key?(new_resource.key)
+ key new_resource.key
else
current_value_does_not_exist!
end
- value plist_data[desired.key]
+ value plist_data[new_resource.key]
end
#
@@ -140,9 +140,7 @@ class Chef
state_cmd
end
- action :write do
- description "Write the value to the specified domain/key."
-
+ action :write, description: "Write the value to the specified domain/key." do
converge_if_changed do
cmd = defaults_modify_cmd
Chef::Log.debug("Updating defaults value by shelling out: #{cmd.join(" ")}")
@@ -151,9 +149,7 @@ class Chef
end
end
- action :delete do
- description "Delete a key from a domain."
-
+ action :delete, description: "Delete a key from a domain." do
# if it's not there there's nothing to remove
return unless current_resource
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 7c1d066488..86dbb0c42a 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -31,15 +31,61 @@ class Chef
" reboot. If the config file is required, it must be done by specifying a template with the correct array layout,"\
" and then by using the mount provider to create a file systems table (fstab) entry."
+ examples <<~DOC
+ **Create and assemble a RAID 0 array**
+
+ The mdadm command can be used to create RAID arrays. For example, a RAID 0 array named /dev/md0 with 10 devices would have a command similar to the following:
+
+ ```
+ mdadm --create /dev/md0 --level=0 --raid-devices=10 /dev/s01.../dev/s10
+ ```
+
+ where /dev/s01 .. /dev/s10 represents 10 devices (01, 02, 03, and so on). This same command, when expressed as a recipe using the mdadm resource, would be similar to:
+
+ ```ruby
+ mdadm '/dev/md0' do
+ devices [ '/dev/s01', ... '/dev/s10' ]
+ level 0
+ action :create
+ end
+ ```
+
+ (again, where /dev/s01 .. /dev/s10 represents devices /dev/s01, /dev/s02, /dev/s03, and so on).
+
+ **Create and assemble a RAID 1 array**
+
+ ```ruby
+ mdadm '/dev/md0' do
+ devices [ '/dev/sda', '/dev/sdb' ]
+ level 1
+ action [ :create, :assemble ]
+ end
+ ```
+
+ **Create and assemble a RAID 5 array**
+
+ The mdadm command can be used to create RAID arrays. For example, a RAID 5 array named /dev/sd0 with 4, and a superblock type of 0.90 would be similar to:
+
+ ```ruby
+ mdadm '/dev/sd0' do
+ devices [ '/dev/s1', '/dev/s2', '/dev/s3', '/dev/s4' ]
+ level 5
+ metadata '0.90'
+ chunk 32
+ action :create
+ end
+ ```
+ DOC
+
default_action :create
allowed_actions :create, :assemble, :stop
property :chunk, Integer,
default: 16,
- description: "The chunk size. This property should not be used for a RAID 1 mirrored pair (i.e. when the level property is set to 1)."
+ description: "The chunk size. This property should not be used for a RAID 1 mirrored pair (i.e. when the `level` property is set to `1`)."
property :devices, Array,
- default: lazy { [] },
+ default: [],
description: "The devices to be part of a RAID array."
# @todo this should get refactored away
@@ -63,7 +109,7 @@ class Chef
description: "An optional property to specify the name of the RAID device if it differs from the resource block's name."
property :layout, String,
- description: "The RAID5 parity algorithm. Possible values: left-asymmetric (or la), left-symmetric (or ls), right-asymmetric (or ra), or right-symmetric (or rs)."
+ description: "The RAID5 parity algorithm. Possible values: `left-asymmetric` (or `la`), `left-symmetric` (or ls), `right-asymmetric` (or `ra`), or `right-symmetric` (or `rs`)."
action_class do
def load_current_resource
@@ -78,7 +124,7 @@ class Chef
end
end
- action :create do
+ action :create, description: "Create an array with per-device superblocks. If an array already exists (but does not match), update that array to match." do
unless current_resource.exists
converge_by("create RAID device #{new_resource.raid_device}") do
command = "yes | mdadm --create #{new_resource.raid_device} --level #{new_resource.level}"
@@ -92,11 +138,11 @@ class Chef
logger.info("#{new_resource} created raid device (#{new_resource.raid_device})")
end
else
- logger.trace("#{new_resource} raid device already exists, skipping create (#{new_resource.raid_device})")
+ logger.debug("#{new_resource} raid device already exists, skipping create (#{new_resource.raid_device})")
end
end
- action :assemble do
+ action :assemble, description: "Assemble a previously created array into an active array." do
unless current_resource.exists
converge_by("assemble RAID device #{new_resource.raid_device}") do
command = "yes | mdadm --assemble #{new_resource.raid_device} #{new_resource.devices.join(" ")}"
@@ -105,11 +151,11 @@ class Chef
logger.info("#{new_resource} assembled raid device (#{new_resource.raid_device})")
end
else
- logger.trace("#{new_resource} raid device already exists, skipping assemble (#{new_resource.raid_device})")
+ logger.debug("#{new_resource} raid device already exists, skipping assemble (#{new_resource.raid_device})")
end
end
- action :stop do
+ action :stop, description: "Stop an active array." do
if current_resource.exists
converge_by("stop RAID device #{new_resource.raid_device}") do
command = "yes | mdadm --stop #{new_resource.raid_device}"
@@ -118,7 +164,7 @@ class Chef
logger.info("#{new_resource} stopped raid device (#{new_resource.raid_device})")
end
else
- logger.trace("#{new_resource} raid device doesn't exist (#{new_resource.raid_device}) - not stopping")
+ logger.debug("#{new_resource} raid device doesn't exist (#{new_resource.raid_device}) - not stopping")
end
end
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index c23ba9bbee..e46abead13 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -34,6 +34,7 @@ class Chef
property :supports, [Array, Hash],
description: "Specify a Hash of supported mount features.",
default: lazy { { remount: false } },
+ default_description: "{ remount: false }",
coerce: proc { |x| x.is_a?(Array) ? x.each_with_object({}) { |i, m| m[i] = true } : x }
property :password, String,
@@ -45,7 +46,7 @@ class Chef
description: "The directory (or path) in which the device is to be mounted. Defaults to the name of the resource block if not provided."
property :device, String, identity: true,
- description: "Required for :umount and :remount actions (for the purpose of checking the mount command output for presence). The special block device or remote node, a label, or a uuid to be mounted."
+ description: "Required for `:umount` and `:remount` actions (for the purpose of checking the mount command output for presence). The special block device or remote node, a label, or a uuid to be mounted."
property :device_type, [String, Symbol],
description: "The type of device: :device, :label, or :uuid",
diff --git a/lib/chef/resource/ohai_hint.rb b/lib/chef/resource/ohai_hint.rb
index 88ea02c809..63ddc595c5 100644
--- a/lib/chef/resource/ohai_hint.rb
+++ b/lib/chef/resource/ohai_hint.rb
@@ -72,9 +72,7 @@ class Chef
description: "Determines whether or not the resource is executed during the compile time phase.",
default: true, desired_state: false
- action :create do
- description "Create an Ohai hint file."
-
+ action :create, description: "Create an Ohai hint file." do
directory ::Ohai::Config.ohai.hints_path.first do
action :create
recursive true
@@ -86,9 +84,7 @@ class Chef
end
end
- action :delete do
- description "Delete an Ohai hint file."
-
+ action :delete, description: "Delete an Ohai hint file." do
file ohai_hint_file_path(new_resource.hint_name) do
action :delete
notifies :reload, ohai[reload ohai post hint removal]
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index af632ebb57..eecb188999 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -31,6 +31,23 @@ class Chef
description "Use the **openbsd_package** resource to manage packages for the OpenBSD platform."
introduced "12.1"
+ examples <<~DOC
+ **Install a package**
+
+ ```ruby
+ openbsd_package 'name of package' do
+ action :install
+ end
+ ```
+
+ **Remove a package**
+
+ ```ruby
+ openbsd_package 'name of package' do
+ action :remove
+ end
+ ```
+ DOC
property :package_name, String,
description: "An optional property to set the package name if it differs from the resource block's name.",
diff --git a/lib/chef/resource/openssl_dhparam.rb b/lib/chef/resource/openssl_dhparam.rb
index 3d20b1b439..a34c79df17 100644
--- a/lib/chef/resource/openssl_dhparam.rb
+++ b/lib/chef/resource/openssl_dhparam.rb
@@ -88,8 +88,7 @@ class Chef
description: "The permission mode applied to all files created by the resource.",
default: "0640"
- action :create do
- description "Create the dhparam file."
+ action :create, description: "Create the `dhparam.pem` file." do
dhparam_content = nil
unless dhparam_pem_valid?(new_resource.path)
dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem
diff --git a/lib/chef/resource/openssl_ec_private_key.rb b/lib/chef/resource/openssl_ec_private_key.rb
index 7625b5ea6e..5cd5b97c83 100644
--- a/lib/chef/resource/openssl_ec_private_key.rb
+++ b/lib/chef/resource/openssl_ec_private_key.rb
@@ -31,7 +31,7 @@ class Chef
description "Use the **openssl_ec_private_key** resource to generate an elliptic curve (EC) private key file. If a valid EC key file can be opened at the specified location, no new file will be created. If the EC key file cannot be opened, either because it does not exist or because the password to the EC key file does not match the password in the recipe, then it will be overwritten."
introduced "14.4"
examples <<~DOC
- Generate a new ec privatekey with prime256v1 key curve and default des3 cipher
+ **Generate a new ec privatekey with prime256v1 key curve and default des3 cipher**
```ruby
openssl_ec_private_key '/etc/ssl_files/eckey_prime256v1_des3.pem' do
@@ -41,7 +41,7 @@ class Chef
end
```
- Generate a new ec private key with prime256v1 key curve and aes-128-cbc cipher
+ **Generate a new ec private key with prime256v1 key curve and aes-128-cbc cipher**
```ruby
openssl_ec_private_key '/etc/ssl_files/eckey_prime256v1_des3.pem' do
@@ -88,9 +88,7 @@ class Chef
description: "Force creation of the key even if the same key already exists on the node.",
default: false, desired_state: false
- action :create do
- description "Generate the ec private key"
-
+ action :create, description: "Generate the EC private key file." do
unless new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
converge_by("Create an EC private key #{new_resource.path}") do
log "Generating an #{new_resource.key_curve} "\
diff --git a/lib/chef/resource/openssl_ec_public_key.rb b/lib/chef/resource/openssl_ec_public_key.rb
index 44441eb72d..b66687f6e7 100644
--- a/lib/chef/resource/openssl_ec_public_key.rb
+++ b/lib/chef/resource/openssl_ec_public_key.rb
@@ -74,9 +74,7 @@ class Chef
description: "The permission mode applied to all files created by the resource.",
default: "0640"
- action :create do
- description "Generate the ec public key from a private key"
-
+ action :create, description: "Generate the EC public key file from a private key." do
raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
raise "#{new_resource.private_key_path} not a valid private EC key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
diff --git a/lib/chef/resource/openssl_rsa_private_key.rb b/lib/chef/resource/openssl_rsa_private_key.rb
index e9e6ef24ca..5f0f50f5b1 100644
--- a/lib/chef/resource/openssl_rsa_private_key.rb
+++ b/lib/chef/resource/openssl_rsa_private_key.rb
@@ -43,7 +43,7 @@ class Chef
Generate new 1024bit key with the aes-128-cbc cipher
```ruby
- openssl_rsa_key '/etc/ssl_files/rsakey_aes128cbc.pem' do
+ openssl_rsa_private_key '/etc/ssl_files/rsakey_aes128cbc.pem' do
key_length 1024
key_cipher 'aes-128-cbc'
action :create
@@ -87,9 +87,7 @@ class Chef
description: "Force creation of the key even if the same key already exists on the node.",
default: false, desired_state: false
- action :create do
- description "Create the RSA private key."
-
+ action :create, description: "Create the RSA private key file." do
return if new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
converge_by("create #{new_resource.key_length} bit RSA key #{new_resource.path}") do
diff --git a/lib/chef/resource/openssl_rsa_public_key.rb b/lib/chef/resource/openssl_rsa_public_key.rb
index 8fd8ab558e..54362b45a4 100644
--- a/lib/chef/resource/openssl_rsa_public_key.rb
+++ b/lib/chef/resource/openssl_rsa_public_key.rb
@@ -75,9 +75,7 @@ class Chef
description: "The permission mode applied to all files created by the resource.",
default: "0640"
- action :create do
- description "Create the RSA public key."
-
+ action :create, description: "Create the RSA public key file." do
raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
raise "#{new_resource.private_key_path} not a valid private RSA key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
diff --git a/lib/chef/resource/openssl_x509_certificate.rb b/lib/chef/resource/openssl_x509_certificate.rb
index c723f47d61..a6e0eb97f2 100644
--- a/lib/chef/resource/openssl_x509_certificate.rb
+++ b/lib/chef/resource/openssl_x509_certificate.rb
@@ -108,11 +108,11 @@ class Chef
property :extensions, Hash,
description: "Hash of X509 Extensions entries, in format `{ 'keyUsage' => { 'values' => %w( keyEncipherment digitalSignature), 'critical' => true } }`.",
- default: lazy { {} }
+ default: {}
property :subject_alt_name, Array,
description: "Array of Subject Alternative Name entries, in format `DNS:example.com` or `IP:1.2.3.4`.",
- default: lazy { [] }
+ default: []
property :key_file, String,
description: "The path to a certificate key file on the filesystem. If the key_file property is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the key_file property is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate."
@@ -151,15 +151,12 @@ class Chef
description: "The number of days before the expiry. The certificate will be automatically renewed when the value is reached.",
introduced: "15.7"
- action :create do
- description "Generate a certificate"
-
+ action :create, description: "Generate a certificate file." do
file new_resource.path do
action :create_if_missing
owner new_resource.owner unless new_resource.owner.nil?
group new_resource.group unless new_resource.group.nil?
mode new_resource.mode unless new_resource.mode.nil?
- sensitive true
content cert.to_pem
end
diff --git a/lib/chef/resource/openssl_x509_crl.rb b/lib/chef/resource/openssl_x509_crl.rb
index 6e7f905084..ecf9778f25 100644
--- a/lib/chef/resource/openssl_x509_crl.rb
+++ b/lib/chef/resource/openssl_x509_crl.rb
@@ -90,9 +90,7 @@ class Chef
property :mode, [Integer, String],
description: "The permission mode of the CRL file."
- action :create do
- description "Create the CRL file."
-
+ action :create, description: "Create the certificate revocation list (CRL) file." do
file new_resource.path do
owner new_resource.owner unless new_resource.owner.nil?
group new_resource.group unless new_resource.group.nil?
diff --git a/lib/chef/resource/openssl_x509_request.rb b/lib/chef/resource/openssl_x509_request.rb
index 0e68337b05..378c484028 100644
--- a/lib/chef/resource/openssl_x509_request.rb
+++ b/lib/chef/resource/openssl_x509_request.rb
@@ -119,9 +119,7 @@ class Chef
equal_to: %w{secp384r1 secp521r1 prime256v1}, default: "prime256v1",
description: "The desired curve of the generated key (if key_type is equal to `ec`). Run `openssl ecparam -list_curves` to see available options."
- action :create do
- description "Generate a certificate request."
-
+ action :create, description: "Generate a certificate request file." do
unless ::File.exist? new_resource.path
converge_by("Create CSR #{@new_resource}") do
file new_resource.path do
diff --git a/lib/chef/resource/osx_profile.rb b/lib/chef/resource/osx_profile.rb
index 6c0028301d..ebeb8e34ca 100644
--- a/lib/chef/resource/osx_profile.rb
+++ b/lib/chef/resource/osx_profile.rb
@@ -51,7 +51,7 @@ class Chef
'PayloadOrganization' => 'Chef',
'PayloadVersion' => 1,
'PayloadDisplayName' => 'Screensaver Settings',
- 'PayloadContent'=> [
+ 'PayloadContent' => [
{
'PayloadType' => 'com.apple.ManagedClient.preferences',
'PayloadVersion' => 1,
@@ -65,13 +65,13 @@ class Chef
{
'mcx_preference_settings' => {
'idleTime' => 0,
- }
- }
- ]
- }
- }
- }
- ]
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
}
osx_profile 'Install screensaver profile' do
@@ -172,7 +172,7 @@ class Chef
end
end
- action :install do
+ action :install, description: "Install the specified configuration profile." do
unless profile_installed?
converge_by("install profile #{new_profile_identifier}") do
profile_path = write_profile_to_disk
@@ -182,7 +182,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove the specified configuration profile." do
# Clean up profile after removing it
if profile_installed?
converge_by("remove profile #{new_profile_identifier}") do
diff --git a/lib/chef/resource/plist.rb b/lib/chef/resource/plist.rb
index a7cb88ef57..4a43605607 100644
--- a/lib/chef/resource/plist.rb
+++ b/lib/chef/resource/plist.rb
@@ -64,24 +64,24 @@ class Chef
"utf-8" => "xml1",
"binary" => "binary1" }.freeze
- load_current_value do |desired|
- current_value_does_not_exist! unless ::File.exist? desired.path
- entry desired.entry if entry_in_plist? desired.entry, desired.path
+ load_current_value do |new_resource|
+ current_value_does_not_exist! unless ::File.exist? new_resource.path
+ entry new_resource.entry if entry_in_plist? new_resource.entry, new_resource.path
- setting = setting_from_plist desired.entry, desired.path
+ setting = setting_from_plist new_resource.entry, new_resource.path
value convert_to_data_type_from_string(setting[:key_type], setting[:key_value])
- file_type_cmd = shell_out "/usr/bin/file", "--brief", "--mime-encoding", "--preserve-date", desired.path
+ file_type_cmd = shell_out "/usr/bin/file", "--brief", "--mime-encoding", "--preserve-date", new_resource.path
encoding file_type_cmd.stdout.chomp
- file_owner_cmd = shell_out("/usr/bin/stat", "-f", "%Su", desired.path)
+ file_owner_cmd = shell_out("/usr/bin/stat", "-f", "%Su", new_resource.path)
owner file_owner_cmd.stdout.chomp
- file_group_cmd = shell_out("/usr/bin/stat", "-f", "%Sg", desired.path)
+ file_group_cmd = shell_out("/usr/bin/stat", "-f", "%Sg", new_resource.path)
group file_group_cmd.stdout.chomp
end
- action :set do
+ action :set, description: "Set a value in a plist file." do
converge_if_changed :path do
converge_by "create new plist: '#{new_resource.path}'" do
file new_resource.path do
diff --git a/lib/chef/resource/powershell_package_source.rb b/lib/chef/resource/powershell_package_source.rb
index 066efc6a72..c4c4d2c841 100644
--- a/lib/chef/resource/powershell_package_source.rb
+++ b/lib/chef/resource/powershell_package_source.rb
@@ -70,8 +70,7 @@ class Chef
script_publish_location status["script_publish_location"]
end
- action :register do
- description "Registers and updates the powershell package source."
+ action :register, description: "Registers and updates the PowerShell package source." do
# TODO: Ensure package provider is installed?
if psrepository_cmdlet_appropriate?
if package_source_exists?
@@ -104,8 +103,7 @@ class Chef
end
end
- action :unregister do
- description "Unregisters the powershell package source."
+ action :unregister, description: "Unregisters the PowerShell package source." do
if package_source_exists?
unregister_cmd = "Get-PackageSource -Name '#{new_resource.source_name}' | Unregister-PackageSource"
converge_by("unregister source: #{new_resource.source_name}") do
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index eb72518009..b0be81feaa 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -27,7 +27,7 @@ class Chef
provides :powershell_script, os: "windows"
description <<~DESC
- Use the **powershell_script** resource to execute a script using the Windows PowerShell interpreter, much like how the script and script-based resources **bash**, **csh**, **perl**, **python**, and **ruby** are used. The **powershell_script** resource is specific to the Microsoft Windows platform, but may use both the the Windows PowerShell interpreter or the PowerShell Core (pwsh) interpreter as of Chef Infra Client 16.6 and later.
+ Use the **powershell_script** resource to execute a script using the Windows PowerShell interpreter, much like how the script and script-based resources **bash**, **csh**, **perl**, **python**, and **ruby** are used. The **powershell_script** resource is specific to the Microsoft Windows platform, but may use both the Windows PowerShell interpreter or the PowerShell Core (pwsh) interpreter as of Chef Infra Client 16.6 and later.
The **powershell_script** resource creates and executes a temporary file rather than running the command inline. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` conditionals to guard this resource for idempotence.
DESC
diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb
index 6ac19e299b..718fb8dd51 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -33,6 +33,41 @@ class Chef
" immediate notifications. Delayed notifications produce unintuitive and"\
" probably undesired results."
introduced "12.0"
+ examples <<~DOC
+ **Reboot a node immediately**
+
+ ```ruby
+ reboot 'now' do
+ action :nothing
+ reason 'Cannot continue Chef run without a reboot.'
+ delay_mins 2
+ end
+
+ execute 'foo' do
+ command '...'
+ notifies :reboot_now, 'reboot[now]', :immediately
+ end
+ ```
+
+ **Reboot a node at the end of a Chef Infra Client run**
+
+ ```ruby
+ reboot 'app_requires_reboot' do
+ action :request_reboot
+ reason 'Need to reboot when the run completes successfully.'
+ delay_mins 5
+ end
+ ```
+
+ **Cancel a reboot**
+
+ ```ruby
+ reboot 'cancel_reboot_request' do
+ action :cancel
+ reason 'Cancel a previous end-of-run reboot request.'
+ end
+ ```
+ DOC
property :reason, String,
description: "A string that describes the reboot action.",
@@ -42,18 +77,14 @@ class Chef
description: "The amount of time (in minutes) to delay a reboot request.",
default: 0
- action :request_reboot do
- description "Reboot a node at the end of a chef-client run."
-
+ action :request_reboot, description: "Reboot a node at the end of a #{ChefUtils::Dist::Infra::PRODUCT} run." do
converge_by("request a system reboot to occur if the run succeeds") do
logger.warn "Reboot requested:'#{new_resource.name}'"
request_reboot
end
end
- action :reboot_now do
- description "Reboot a node so that the chef-client may continue the installation process."
-
+ action :reboot_now, description: "Reboot a node so that the #{ChefUtils::Dist::Infra::PRODUCT} may continue the installation process." do
converge_by("rebooting the system immediately") do
logger.warn "Rebooting system immediately, requested by '#{new_resource.name}'"
request_reboot
@@ -61,9 +92,7 @@ class Chef
end
end
- action :cancel do
- description "Cancel a pending reboot request."
-
+ action :cancel, description: "Cancel a pending reboot request." do
converge_by("cancel any existing end-of-run reboot request") do
logger.warn "Reboot canceled: '#{new_resource.name}'"
node.run_context.cancel_reboot
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index b6dc0b7a98..e7640e686e 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -29,7 +29,7 @@ class Chef
provides :remote_directory
- description "Use the **remote_directory** resource to incrementally transfer a directory from a cookbook to a node. The director that is copied from the cookbook should be located under COOKBOOK_NAME/files/default/REMOTE_DIRECTORY. The remote_directory resource will obey file specificity."
+ description "Use the **remote_directory** resource to incrementally transfer a directory from a cookbook to a node. The directory that is copied from the cookbook should be located under `COOKBOOK_NAME/files/default/REMOTE_DIRECTORY`. The `remote_directory` resource will obey file specificity."
default_action :create
allowed_actions :create, :create_if_missing, :delete
@@ -71,7 +71,7 @@ class Chef
desired_state: false
property :files_group, [String, Integer],
- description: "Configure group permissions for files. A string or ID that identifies the group owner by group name, including fully qualified group names such as domain\\group or group@domain. If this value is not specified, existing groups remain unchanged and new group assignments use the default POSIX group (if available).",
+ description: "Configure group permissions for files. A string or ID that identifies the group owner by group name, including fully qualified group names such as `domain\\group` or `group@domain`. If this value is not specified, existing groups remain unchanged and new group assignments use the default POSIX group (if available).",
regex: Chef::Config[:group_valid_regex]
property :files_mode, [String, Integer, nil],
@@ -80,7 +80,7 @@ class Chef
regex: /^\d{3,4}$/, default: lazy { 0644 unless Chef::Platform.windows? }
property :files_owner, [String, Integer],
- description: "Configure owner permissions for files. A string or ID that identifies the group owner by user name, including fully qualified user names such as domain\\user or user@domain. If this value is not specified, existing owners remain unchanged and new owner assignments use the current user (when necessary).",
+ description: "Configure owner permissions for files. A string or ID that identifies the group owner by user name, including fully qualified user names such as `domain\\user` or `user@domain`. If this value is not specified, existing owners remain unchanged and new owner assignments use the current user (when necessary).",
regex: Chef::Config[:user_valid_regex]
end
end
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index ac0b2fe6a7..8eca1c3337 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -95,7 +95,7 @@ class Chef
property :ftp_active_mode, [ TrueClass, FalseClass ], default: false,
description: "Whether #{ChefUtils::Dist::Infra::PRODUCT} uses active or passive FTP. Set to `true` to use active FTP."
- property :headers, Hash, default: lazy { {} },
+ property :headers, Hash, default: {},
description: "A Hash of custom HTTP headers."
property :show_progress, [ TrueClass, FalseClass ], default: false
@@ -142,13 +142,13 @@ class Chef
end
# if domain is provided in both username and domain
- if specified_user && ((specified_user.include? '\\') || (specified_user.include? "@")) && specified_domain
+ if specified_user && ((specified_user.include? "\\") || (specified_user.include? "@")) && specified_domain
raise ArgumentError, "The domain is provided twice. Username: `#{specified_user}`, Domain: `#{specified_domain}`. Please specify domain only once."
end
if ! specified_user.nil? && specified_domain.nil?
# Splitting username of format: Domain\Username
- domain_and_user = user.split('\\')
+ domain_and_user = user.split("\\")
if domain_and_user.length == 2
domain = domain_and_user[0]
diff --git a/lib/chef/resource/rhsm_errata.rb b/lib/chef/resource/rhsm_errata.rb
index 7a1e3df325..d3e4a8b8b2 100644
--- a/lib/chef/resource/rhsm_errata.rb
+++ b/lib/chef/resource/rhsm_errata.rb
@@ -25,14 +25,27 @@ class Chef
description "Use the **rhsm_errata** resource to install packages associated with a given Red Hat Subscription Manager Errata ID. This is helpful if packages to mitigate a single vulnerability must be installed on your hosts."
introduced "14.0"
+ examples <<~DOC
+ **Install a package from an Errata ID**
+
+ ```ruby
+ rhsm_errata 'RHSA:2018-1234'
+ ```
+
+ **Specify an Errata ID that differs from the resource name**
+
+ ```ruby
+ rhsm_errata 'errata-install'
+ errata_id 'RHSA:2018-1234'
+ end
+ ```
+ DOC
property :errata_id, String,
description: "An optional property for specifying the errata ID if it differs from the resource block's name.",
name_property: true
- action :install do
- description "Installs a package for a specific errata ID."
-
+ action :install, description: "Install a package for a specific errata ID." do
execute "Install errata packages for #{new_resource.errata_id}" do
command "#{package_manager_command} update --advisory #{new_resource.errata_id} -y"
default_env true
diff --git a/lib/chef/resource/rhsm_errata_level.rb b/lib/chef/resource/rhsm_errata_level.rb
index 9ac3944153..52d3458d91 100644
--- a/lib/chef/resource/rhsm_errata_level.rb
+++ b/lib/chef/resource/rhsm_errata_level.rb
@@ -25,6 +25,15 @@ class Chef
description "Use the **rhsm_errata_level** resource to install all packages of a specified errata level from the Red Hat Subscription Manager. For example, you can ensure that all packages associated with errata marked at a 'Critical' security level are installed."
introduced "14.0"
+ examples <<~DOC
+ **Specify an errata level that differs from the resource name**
+
+ ```ruby
+ rhsm_errata_level 'example_install_moderate' do
+ errata_level 'moderate'
+ end
+ ```
+ DOC
property :errata_level, String,
coerce: proc { |x| x.downcase },
@@ -32,12 +41,8 @@ class Chef
description: "An optional property for specifying the errata level of packages to install if it differs from the resource block's name.",
name_property: true
- action :install do
- description "Install all packages of the specified errata level."
-
- if rhel6?
- yum_package "yum-plugin-security"
- end
+ action :install, description: "Install all packages of the specified errata level." do
+ yum_package "yum-plugin-security" if rhel6?
execute "Install any #{new_resource.errata_level} errata" do
command "#{package_manager_command} update --sec-severity=#{new_resource.errata_level.capitalize} -y"
diff --git a/lib/chef/resource/rhsm_register.rb b/lib/chef/resource/rhsm_register.rb
index 07c4dbc8d7..3e1d251e76 100644
--- a/lib/chef/resource/rhsm_register.rb
+++ b/lib/chef/resource/rhsm_register.rb
@@ -27,6 +27,16 @@ class Chef
description "Use the **rhsm_register** resource to register a node with the Red Hat Subscription Manager or a local Red Hat Satellite server."
introduced "14.0"
+ examples <<~DOC
+ **Register a node with RHSM*
+
+ ```ruby
+ rhsm_register 'my-host' do
+ activation_key 'ABCD1234'
+ organization 'my_org'
+ end
+ ```
+ DOC
property :activation_key, [String, Array],
coerce: proc { |x| Array(x) },
@@ -69,9 +79,7 @@ class Chef
default: false, desired_state: false,
introduced: "15.9"
- action :register do
- description "Register the node with RHSM."
-
+ action :register, description: "Register the node with RHSM." do
package "subscription-manager"
unless new_resource.satellite_host.nil? || registered_with_rhsm?
@@ -106,9 +114,7 @@ class Chef
end
end
- action :unregister do
- description "Unregister the node from RHSM."
-
+ action :unregister, description: "Unregister the node from RHSM." do
execute "Unregister from RHSM" do
command "subscription-manager unregister"
default_env true
diff --git a/lib/chef/resource/rhsm_repo.rb b/lib/chef/resource/rhsm_repo.rb
index d8959695cf..03567e004b 100644
--- a/lib/chef/resource/rhsm_repo.rb
+++ b/lib/chef/resource/rhsm_repo.rb
@@ -26,14 +26,27 @@ class Chef
description "Use the **rhsm_repo** resource to enable or disable Red Hat Subscription Manager repositories that are made available via attached subscriptions."
introduced "14.0"
+ examples <<~DOC
+ **Enable an RHSM repository**
+
+ ```ruby
+ rhsm_repo 'rhel-7-server-extras-rpms'
+ ```
+
+ **Disable an RHSM repository**
+
+ ```ruby
+ rhsm_repo 'rhel-7-server-extras-rpms' do
+ action :disable
+ end
+ ```
+ DOC
property :repo_name, String,
description: "An optional property for specifying the repository name if it differs from the resource block's name.",
name_property: true
- action :enable do
- description "Enable a RHSM repository."
-
+ action :enable, description: "Enable a RHSM repository." do
execute "Enable repository #{new_resource.repo_name}" do
command "subscription-manager repos --enable=#{new_resource.repo_name}"
default_env true
@@ -42,9 +55,7 @@ class Chef
end
end
- action :disable do
- description "Disable a RHSM repository."
-
+ action :disable, description: "Disable a RHSM repository." do
execute "Enable repository #{new_resource.repo_name}" do
command "subscription-manager repos --disable=#{new_resource.repo_name}"
default_env true
diff --git a/lib/chef/resource/rhsm_subscription.rb b/lib/chef/resource/rhsm_subscription.rb
index 15a4822ecd..5ae04bbfcd 100644
--- a/lib/chef/resource/rhsm_subscription.rb
+++ b/lib/chef/resource/rhsm_subscription.rb
@@ -31,9 +31,7 @@ class Chef
description: "An optional property for specifying the Pool ID if it differs from the resource block's name.",
name_property: true
- action :attach do
- description "Attach the node to a subscription pool."
-
+ action :attach, description: "Attach the node to a subscription pool." do
execute "Attach subscription pool #{new_resource.pool_id}" do
command "subscription-manager attach --pool=#{new_resource.pool_id}"
default_env true
@@ -42,9 +40,7 @@ class Chef
end
end
- action :remove do
- description "Remove the node from a subscription pool."
-
+ action :remove, description: "Remove the node from a subscription pool." do
execute "Remove subscription pool #{new_resource.pool_id}" do
command "subscription-manager remove --serial=#{pool_serial(new_resource.pool_id)}"
default_env true
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index a9f3ae24fd..2c0e65e9da 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -25,11 +25,7 @@ class Chef
provides :ruby
- description "Use the **ruby** resource to execute scripts using the Ruby interpreter. This"\
- " resource may also use any of the actions and properties that are available"\
- " to the **execute** resource. Commands that are executed with this resource are (by"\
- " their nature) not idempotent, as they are typically unique to the environment"\
- " in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
+ description "Use the **ruby** resource to execute scripts using the Ruby interpreter. This resource may also use any of the actions and properties that are available to the **execute** resource. Commands that are executed with this resource are (by their nature) not idempotent, as they are typically unique to the environment in which they are run. Use `not_if` and `only_if` to guard this resource for idempotence."
def initialize(name, run_context = nil)
super
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index 427c3e25da..2d7d2fe8b6 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -28,7 +28,7 @@ class Chef
provides :ruby_block, target_mode: true
- description "Use the **ruby_block** resource to execute Ruby code during a #{ChefUtils::Dist::Infra::PRODUCT} run. Ruby code in the ruby_block resource is evaluated with other resources during convergence, whereas Ruby code outside of a ruby_block resource is evaluated before other resources, as the recipe is compiled."
+ description "Use the **ruby_block** resource to execute Ruby code during a #{ChefUtils::Dist::Infra::PRODUCT} run. Ruby code in the `ruby_block` resource is evaluated with other resources during convergence, whereas Ruby code outside of a `ruby_block` resource is evaluated before other resources, as the recipe is compiled."
default_action :run
allowed_actions :create, :run
diff --git a/lib/chef/resource/scm/git.rb b/lib/chef/resource/scm/git.rb
index 8293d1ed4a..af49704ee6 100644
--- a/lib/chef/resource/scm/git.rb
+++ b/lib/chef/resource/scm/git.rb
@@ -112,7 +112,7 @@ class Chef
property :additional_remotes, Hash,
description: "A Hash of additional remotes that are added to the git repository configuration.",
- default: lazy { {} }
+ default: {}
property :depth, Integer,
description: "The number of past revisions to be included in the git shallow clone. Unless specified the default behavior will do a full clone."
diff --git a/lib/chef/resource/ssh_known_hosts_entry.rb b/lib/chef/resource/ssh_known_hosts_entry.rb
index 1db811978c..a8d45f4c12 100644
--- a/lib/chef/resource/ssh_known_hosts_entry.rb
+++ b/lib/chef/resource/ssh_known_hosts_entry.rb
@@ -75,7 +75,8 @@ class Chef
property :group, [String, Integer],
description: "The file group for the ssh_known_hosts file.",
- default: lazy { node["root_group"] }
+ default: lazy { node["root_group"] },
+ default_description: "The root user's group depending on platform."
property :hash_entries, [TrueClass, FalseClass],
description: "Hash the hostname and addresses in the ssh_known_hosts file for privacy.",
@@ -85,9 +86,7 @@ class Chef
description: "The location of the ssh known hosts file. Change this to set a known host file for a particular user.",
default: "/etc/ssh/ssh_known_hosts"
- action :create do
- description "Create an entry in the ssh_known_hosts file."
-
+ action :create, description: "Create an entry in the ssh_known_hosts file." do
key =
if new_resource.key
hoststr = (new_resource.port != 22) ? "[#{new_resource.host}]:#{new_resource.port}" : new_resource.host
@@ -129,9 +128,7 @@ class Chef
end
# all this does is send an immediate run_action(:create) to the template resource
- action :flush do
- description "Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the #{ChefUtils::Dist::Infra::PRODUCT} run so all entries can be accumulated before writing the file out."
-
+ action :flush, description: "Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the #{ChefUtils::Dist::Infra::PRODUCT} run so all entries can be accumulated before writing the file out." do
with_run_context :root do
# if you haven't ever called ssh_known_hosts_entry before you're definitely doing it wrong so we blow up hard.
find_resource!(:template, "update ssh known hosts file #{new_resource.file_location}").run_action(:create)
diff --git a/lib/chef/resource/sudo.rb b/lib/chef/resource/sudo.rb
index d6587bd441..d028af80bf 100644
--- a/lib/chef/resource/sudo.rb
+++ b/lib/chef/resource/sudo.rb
@@ -71,12 +71,12 @@ class Chef
property :users, [String, Array],
description: "User(s) to provide sudo privileges to. This property accepts either an array or a comma separated list.",
- default: lazy { [] },
+ default: [],
coerce: proc { |x| x.is_a?(Array) ? x : x.split(/\s*,\s*/) }
property :groups, [String, Array],
description: "Group(s) to provide sudo privileges to. This property accepts either an array or a comma separated list. Leading % on group names is optional.",
- default: lazy { [] },
+ default: [],
coerce: proc { |x| coerce_groups(x) }
property :commands, Array,
@@ -108,11 +108,11 @@ class Chef
property :defaults, Array,
description: "An array of defaults for the user/group.",
- default: lazy { [] }
+ default: []
property :command_aliases, Array,
description: "Command aliases that can be used as allowed commands later in the configuration.",
- default: lazy { [] }
+ default: []
property :setenv, [TrueClass, FalseClass],
description: "Determines whether or not to permit preservation of the environment with `sudo -E`.",
@@ -120,11 +120,11 @@ class Chef
property :env_keep_add, Array,
description: "An array of strings to add to `env_keep`.",
- default: lazy { [] }
+ default: []
property :env_keep_subtract, Array,
description: "An array of strings to remove from `env_keep`.",
- default: lazy { [] }
+ default: []
property :visudo_path, String,
deprecated: true
@@ -170,9 +170,7 @@ class Chef
end
end
- action :create do
- description "Create a single sudoers config in the sudoers.d directory"
-
+ action :create, description: "Create a single sudoers configuration file in the `sudoers.d` directory." do
validate_properties
if docker? # don't even put this into resource collection unless we're in docker
@@ -230,9 +228,7 @@ class Chef
end
# Removes a user from the sudoers group
- action :delete do
- description "Remove a sudoers config from the sudoers.d directory"
-
+ action :delete, description: "Remove a sudoers configuration file from the `sudoers.d` directory." do
file "#{new_resource.config_prefix}/sudoers.d/#{new_resource.filename}" do
action :delete
end
diff --git a/lib/chef/resource/swap_file.rb b/lib/chef/resource/swap_file.rb
index 3d8f31de48..bd2c7bf632 100644
--- a/lib/chef/resource/swap_file.rb
+++ b/lib/chef/resource/swap_file.rb
@@ -63,9 +63,7 @@ class Chef
property :swappiness, Integer,
description: "The swappiness value to set on the system."
- action :create do
- description "Create a swapfile."
-
+ action :create, description: "Create a swapfile." do
if swap_enabled?
Chef::Log.debug("#{new_resource} already created - nothing to do")
else
@@ -85,9 +83,7 @@ class Chef
end
end
- action :remove do
- description "Remove a swapfile and disable swap."
-
+ action :remove, description: "Remove a swapfile and disable swap." do
swapoff if swap_enabled?
remove_swapfile if ::File.exist?(new_resource.path)
end
diff --git a/lib/chef/resource/sysctl.rb b/lib/chef/resource/sysctl.rb
index bf9424f35f..9c36206e14 100644
--- a/lib/chef/resource/sysctl.rb
+++ b/lib/chef/resource/sysctl.rb
@@ -131,9 +131,7 @@ class Chef
end
- action :apply do
- description "Apply a sysctl value."
-
+ action :apply, description: "Apply a sysctl value." do
converge_if_changed do
# set it temporarily
set_sysctl_param(new_resource.key, new_resource.value)
@@ -152,9 +150,7 @@ class Chef
end
end
- action :remove do
- description "Remove a sysctl value."
-
+ action :remove, description: "Remove a sysctl value." do
# only converge the resource if the file actually exists to delete
if ::File.exist?("#{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf")
converge_by "removing sysctl config at #{new_resource.conf_dir}/99-chef-#{new_resource.key.tr("/", ".")}.conf" do
diff --git a/lib/chef/resource/systemd_unit.rb b/lib/chef/resource/systemd_unit.rb
index b028214441..f6384ac947 100644
--- a/lib/chef/resource/systemd_unit.rb
+++ b/lib/chef/resource/systemd_unit.rb
@@ -34,7 +34,7 @@ class Chef
```ruby
systemd_unit 'etcd.service' do
- content({Unit: {
+ content(Unit: {
Description: 'Etcd',
Documentation: ['https://coreos.com/etcd', 'man:etcd(1)'],
After: 'network.target',
@@ -46,7 +46,7 @@ class Chef
},
Install: {
WantedBy: 'multi-user.target',
- }})
+ })
action [:create, :enable]
end
```
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 88fde45a25..60b431ab8e 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -61,7 +61,7 @@ class Chef
property :variables, Hash,
description: "The variables property of the template resource can be used to reference a partial template file by using a Hash.",
- default: lazy { {} }
+ default: {}
property :cookbook, String,
description: "The cookbook in which a file is located (if it is not located in the current cookbook). The default value is the current cookbook.",
@@ -169,8 +169,8 @@ class Chef
elsif module_name.nil?
raise Exceptions::ValidationFailed,
"#helpers requires either a module name or inline module code as a block.\n" +
- "e.g.: helpers do; helper_code; end;\n" +
- "OR: helpers(MyHelpersModule)"
+ "e.g.: helpers do; helper_code; end;\n" +
+ "OR: helpers(MyHelpersModule)"
else
raise Exceptions::ValidationFailed,
"Argument to #helpers must be a module. You gave #{module_name.inspect} (#{module_name.class})"
diff --git a/lib/chef/resource/timezone.rb b/lib/chef/resource/timezone.rb
index 04e5884b88..489b94b0b3 100644
--- a/lib/chef/resource/timezone.rb
+++ b/lib/chef/resource/timezone.rb
@@ -119,9 +119,7 @@ class Chef
end
end
- action :set do
- description "Set the timezone."
-
+ action :set, description: "Set the system timezone." do
# we have to check windows first since the value isn't case sensitive here
if windows?
unless current_windows_tz.casecmp?(new_resource.timezone)
diff --git a/lib/chef/resource/user/dscl_user.rb b/lib/chef/resource/user/dscl_user.rb
deleted file mode 100644
index 91efd657de..0000000000
--- a/lib/chef/resource/user/dscl_user.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "../user"
-
-class Chef
- class Resource
- class User
- class DsclUser < Chef::Resource::User
- unified_mode true
-
- provides :dscl_user
- provides :user, platform: "mac_os_x", platform_version: "< 10.14"
-
- property :iterations, Integer,
- description: "macOS platform only. The number of iterations for a password with a SALTED-SHA512-PBKDF2 shadow hash.",
- default: 27855, desired_state: false
- end
- end
- end
-end
diff --git a/lib/chef/resource/user/mac_user.rb b/lib/chef/resource/user/mac_user.rb
index 2331283bbd..93eef5e893 100644
--- a/lib/chef/resource/user/mac_user.rb
+++ b/lib/chef/resource/user/mac_user.rb
@@ -61,7 +61,7 @@ class Chef
unified_mode true
provides :mac_user
- provides :user, platform: "mac_os_x", platform_version: ">= 10.14"
+ provides :user, platform: "mac_os_x"
introduced "15.3"
diff --git a/lib/chef/resource/user/windows_user.rb b/lib/chef/resource/user/windows_user.rb
index d504158edf..d738ba1636 100644
--- a/lib/chef/resource/user/windows_user.rb
+++ b/lib/chef/resource/user/windows_user.rb
@@ -29,6 +29,11 @@ class Chef
property :full_name, String,
description: "The full name of the user.",
introduced: "14.6"
+
+ # Override the property from the parent class to coerce to integer.
+ property :uid, [ String, Integer, NilClass ], # nil for backwards compat
+ description: "The numeric user identifier.",
+ coerce: proc { |n| n && Integer(n) rescue n }
end
end
end
diff --git a/lib/chef/resource/user_ulimit.rb b/lib/chef/resource/user_ulimit.rb
index d138eeabf3..55331dfc1c 100644
--- a/lib/chef/resource/user_ulimit.rb
+++ b/lib/chef/resource/user_ulimit.rb
@@ -78,7 +78,7 @@ class Chef
coerce: proc { |m| m.end_with?(".conf") ? m : m + ".conf" },
default: lazy { |r| r.username == "*" ? "00_all_limits.conf" : "#{r.username}_limits.conf" }
- action :create do
+ action :create, description: "Create a ulimit configuration file." do
template "/etc/security/limits.d/#{new_resource.filename}" do
source ::File.expand_path("support/ulimit.erb", __dir__)
local true
@@ -106,7 +106,7 @@ class Chef
end
end
- action :delete do
+ action :delete, description: "Delete an existing ulimit configuration file." do
file "/etc/security/limits.d/#{new_resource.filename}" do
action :delete
end
diff --git a/lib/chef/resource/windows_ad_join.rb b/lib/chef/resource/windows_ad_join.rb
index 731ce9333e..e285e1cf1e 100644
--- a/lib/chef/resource/windows_ad_join.rb
+++ b/lib/chef/resource/windows_ad_join.rb
@@ -97,9 +97,7 @@ class Chef
property :sensitive, [TrueClass, FalseClass],
default: true, desired_state: false
- action :join do
- description "Join the Active Directory domain."
-
+ action :join, description: "Join the Active Directory domain." do
unless on_desired_domain?
cmd = "$pswd = ConvertTo-SecureString \'#{new_resource.domain_password}\' -AsPlainText -Force;"
cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{sanitize_usename}\",$pswd);"
@@ -129,9 +127,7 @@ class Chef
end
end
- action :leave do
- description "Leave the Active Directory domain."
-
+ action :leave, description: "Leave an Active Directory domain and re-join a workgroup." do
if joined_to_domain?
cmd = ""
cmd << "$pswd = ConvertTo-SecureString \'#{new_resource.domain_password}\' -AsPlainText -Force;"
diff --git a/lib/chef/resource/windows_audit_policy.rb b/lib/chef/resource/windows_audit_policy.rb
index 433e18e197..b1fcbe2e44 100644
--- a/lib/chef/resource/windows_audit_policy.rb
+++ b/lib/chef/resource/windows_audit_policy.rb
@@ -106,7 +106,7 @@ class Chef
```ruby
windows_audit_policy "Set Audit Policy for 'Credential Validation' actions to 'Success'" do
- subcategory 'Credential Validation'
+ subcategory 'Credential Validation'
success true
failure false
action :set
@@ -152,7 +152,7 @@ class Chef
property :audit_base_directories, [true, false],
description: "Setting this audit policy option to true will force the system to assign a System Access Control List to named objects to enable auditing of container objects such as directories."
- action :set do
+ action :set, description: "Configure an audit policy." do
unless new_resource.subcategory.nil?
new_resource.subcategory.each do |subcategory|
next if subcategory_configured?(subcategory, new_resource.success, new_resource.failure)
diff --git a/lib/chef/resource/windows_auto_run.rb b/lib/chef/resource/windows_auto_run.rb
index 4885a02676..9e46b58b53 100644
--- a/lib/chef/resource/windows_auto_run.rb
+++ b/lib/chef/resource/windows_auto_run.rb
@@ -57,8 +57,7 @@ class Chef
alias_method :program, :path
- action :create do
- description "Create an item to be run at login."
+ action :create, description: "Create an item to be run at login." do
data = "\"#{new_resource.path}\""
data << " #{new_resource.args}" if new_resource.args
@@ -73,9 +72,7 @@ class Chef
end
end
- action :remove do
- description "Remove an item that was previously setup to run at login"
-
+ action :remove, description: "Remove an item that was previously configured to run at login." do
registry_key registry_path do
values [{
name: new_resource.program_name,
diff --git a/lib/chef/resource/windows_certificate.rb b/lib/chef/resource/windows_certificate.rb
index 2c8c7c72ff..528b0c53f6 100644
--- a/lib/chef/resource/windows_certificate.rb
+++ b/lib/chef/resource/windows_certificate.rb
@@ -19,6 +19,7 @@
require_relative "../util/path_helper"
require_relative "../resource"
+require_relative "../exceptions"
module Win32
autoload :Certstore, "win32-certstore" if Chef::Platform.windows?
end
@@ -62,44 +63,49 @@ class Chef
DOC
property :source, String,
- description: "The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete) if it differs from the resource block's name.",
+ description: "The source file (for `create` and `acl_add`), thumbprint (for `delete`, `export`, and `acl_add`), or subject (for `delete` or `export`) if it differs from the resource block's name.",
name_property: true
property :pfx_password, String,
- description: "The password to access the source if it is a pfx file."
+ description: "The password to access the object with if it is a PFX file."
property :private_key_acl, Array,
- description: "An array of 'domain\account' entries to be granted read-only access to the certificate's private key. Not idempotent."
+ description: "An array of 'domain\\account' entries to be granted read-only access to the certificate's private key. Not idempotent."
property :store_name, String,
description: "The certificate store to manipulate.",
default: "MY", equal_to: ["TRUSTEDPUBLISHER", "TrustedPublisher", "CLIENTAUTHISSUER", "REMOTE DESKTOP", "ROOT", "TRUSTEDDEVICES", "WEBHOSTING", "CA", "AUTHROOT", "TRUSTEDPEOPLE", "MY", "SMARTCARDROOT", "TRUST", "DISALLOWED"]
property :user_store, [TrueClass, FalseClass],
- description: "Use the user store of the local machine store if set to false.",
+ description: "Use the `CurrentUser` store instead of the default `LocalMachine` store. Note: Prior to #{ChefUtils::Dist::Infra::CLIENT}. 16.10 this property was ignored.",
default: false
- property :cert_path, String,
- description: "The path to the certificate."
+ deprecated_property_alias :cert_path, :output_path, "The cert_path property was renamed output_path in the 17.0 release of #{ChefUtils::Dist::Infra::CLIENT}. Please update your cookbooks to use the new property name."
# lazy used to set default value of sensitive to true if password is set
property :sensitive, [TrueClass, FalseClass],
description: "Ensure that sensitive resource data is not logged by the #{ChefUtils::Dist::Infra::CLIENT}.",
default: lazy { pfx_password ? true : false }, skip_docs: true
- action :create do
- description "Creates or updates a certificate."
+ property :exportable, [TrueClass, FalseClass],
+ description: "Ensure that imported pfx certificate is exportable. Please provide 'true' if you want the certificate to be exportable.",
+ default: false,
+ introduced: "16.8"
- # Extension of the certificate
- ext = ::File.extname(new_resource.source)
+ property :output_path, String,
+ description: "A path on the node where a certificate object (PFX, PEM, CER, KEY, etc) can be exported to.",
+ introduced: "17.0"
+
+ action :create, description: "Creates or updates a certificate." do
+ ext = get_file_extension(new_resource.source)
# PFX certificates contains private keys and we import them with some other approach
- import_certificates(fetch_cert_object(ext), (ext == ".pfx"))
+ # import_certificates(fetch_cert_object(ext), (ext == ".pfx"))
+ import_certificates(fetch_cert_object_from_file(ext), (ext == ".pfx"))
end
# acl_add is a modify-if-exists operation : not idempotent
- action :acl_add do
- description "Adds read-only entries to a certificate's private key ACL."
+ action :acl_add, description: "Adds read-only entries to a certificate's private key ACL." do
if ::File.exist?(new_resource.source)
hash = "$cert.GetCertHashString()"
@@ -114,7 +120,7 @@ class Chef
code_script << acl_script(hash)
guard_script << cert_exists_script(hash)
- powershell_script "setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}" do
+ powershell_script "setting the acls on #{new_resource.source} in #{ps_cert_location}\\#{new_resource.store_name}" do
convert_boolean_return true
code code_script
only_if guard_script
@@ -122,9 +128,9 @@ class Chef
end
end
- action :delete do
- description "Deletes a certificate."
+ action :delete, description: "Deletes a certificate." do
cert_obj = fetch_cert
+
if cert_obj
converge_by("Deleting certificate #{new_resource.source} from Store #{new_resource.store_name}") do
delete_cert
@@ -134,20 +140,27 @@ class Chef
end
end
- action :fetch do
- description "Fetches a certificate."
+ action :fetch, description: "Fetches a certificate." do
+ unless new_resource.output_path
+ raise Chef::Exceptions::ResourceNotFound, "You must include an output_path parameter when calling the fetch action"
+ end
+
+ if ::File.extname(new_resource.output_path) == ".pfx"
+ powershell_exec!(pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: ps_cert_location, store_name: new_resource.store_name, output_path: new_resource.output_path, password: new_resource.pfx_password ))
+ else
+ cert_obj = fetch_cert
+ end
- cert_obj = fetch_cert
if cert_obj
- show_or_store_cert(cert_obj)
+ converge_by("Fetching certificate #{new_resource.source} from Store \\#{ps_cert_location}\\#{new_resource.store_name}") do
+ export_cert(cert_obj, output_path: new_resource.output_path, store_name: new_resource.store_name , store_location: ps_cert_location, pfx_password: new_resource.pfx_password)
+ end
else
Chef::Log.debug("Certificate not found")
end
end
- action :verify do
- description ""
-
+ action :verify, description: "Verifies a certificate and logs the result." do
out = verify_cert
if !!out == out
out = out ? "Certificate is valid" : "Certificate not valid"
@@ -156,24 +169,102 @@ class Chef
end
action_class do
+ @local_pfx_path = ""
+
+ CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
+ CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
+
def add_cert(cert_obj)
- store = ::Win32::Certstore.open(new_resource.store_name)
+ store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
store.add(cert_obj)
end
- def add_pfx_cert
- store = ::Win32::Certstore.open(new_resource.store_name)
- store.add_pfx(new_resource.source, new_resource.pfx_password)
+ def add_pfx_cert(path)
+ exportable = new_resource.exportable ? 1 : 0
+ store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
+ store.add_pfx(path, new_resource.pfx_password, exportable)
end
def delete_cert
- store = ::Win32::Certstore.open(new_resource.store_name)
- store.delete(new_resource.source)
+ store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
+ store.delete(resolve_thumbprint(new_resource.source))
end
def fetch_cert
- store = ::Win32::Certstore.open(new_resource.store_name)
- store.get(new_resource.source)
+ store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
+ if new_resource.output_path && ::File.extname(new_resource.output_path) == ".key"
+ fetch_key
+
+ else
+ store.get(resolve_thumbprint(new_resource.source), store_name: new_resource.store_name, store_location: native_cert_location)
+ end
+ end
+
+ def fetch_key
+ require "openssl" unless defined?(OpenSSL)
+ file_name = ::File.basename(new_resource.output_path, ::File.extname(new_resource.output_path))
+ directory = ::File.dirname(new_resource.output_path)
+ pfx_file = file_name + ".pfx"
+ new_pfx_output_path = ::File.join(Chef::FileCache.create_cache_path("pfx_files"), pfx_file)
+ powershell_exec(pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: ps_cert_location, store_name: new_resource.store_name, output_path: new_pfx_output_path, password: new_resource.pfx_password ))
+ pkcs12 = OpenSSL::PKCS12.new(::File.binread(new_pfx_output_path), new_resource.pfx_password)
+ f = ::File.open(new_resource.output_path, "w")
+ f.write(pkcs12.key.to_s)
+ f.flush
+ f.close
+ end
+
+ def get_file_extension(file_name)
+ if is_file?(file_name)
+ ::File.extname(file_name)
+ elsif is_url?(file_name)
+ require "open-uri" unless defined?(OpenURI)
+ uri = URI.parse(file_name)
+ output_file = ::File.basename(uri.path)
+ ::File.extname(output_file)
+ end
+ end
+
+ def get_file_name(path_name)
+ if is_file?(path_name)
+ ::File.extname(path_name)
+ elsif is_url?(path_name)
+ require "open-uri" unless defined?(OpenURI)
+ uri = URI.parse(path_name)
+ ::File.basename(uri.path)
+ end
+ end
+
+ def is_url?(source)
+ require "uri" unless defined?(URI)
+ uri = URI.parse(source)
+ uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
+ end
+
+ def is_file?(source)
+ ::File.file?(source)
+ end
+
+ def is_file?(source)
+ ::File.file?(source)
+ end
+
+ # Thumbprints should be exactly 40 Hex characters
+ def valid_thumbprint?(string)
+ string.match?(/[0-9A-Fa-f]/) && string.length == 40
+ end
+
+ def get_thumbprint(store_name, location, source)
+ <<-GETTHUMBPRINTCODE
+ $content = Get-ChildItem -Path Cert:\\#{location}\\#{store_name} | Where-Object {$_.Subject -Match "#{source}"} | Select-Object Thumbprint
+ $content.thumbprint
+ GETTHUMBPRINTCODE
+ end
+
+ def resolve_thumbprint(thumbprint)
+ return thumbprint if valid_thumbprint?(thumbprint)
+
+ powershell_exec!(get_thumbprint(new_resource.store_name, ps_cert_location, new_resource.source)).result
end
# Checks whether a certificate with the given thumbprint
@@ -181,56 +272,38 @@ class Chef
# If the certificate is not present, verify_cert returns a String: "Certificate not found"
# But if it is present but expired, it returns a Boolean: false
# Otherwise, it returns a Boolean: true
- def verify_cert(thumbprint = new_resource.source)
- store = ::Win32::Certstore.open(new_resource.store_name)
- store.valid?(thumbprint)
- end
+ # updated this method to accept either a subject name or a thumbprint - 1/29/2021
- def show_or_store_cert(cert_obj)
- if new_resource.cert_path
- export_cert(cert_obj, new_resource.cert_path)
- if ::File.size(new_resource.cert_path) > 0
- Chef::Log.info("Certificate export in #{new_resource.cert_path}")
- else
- ::File.delete(new_resource.cert_path)
- end
+ def verify_cert(thumbprint = new_resource.source)
+ store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
+ if new_resource.pfx_password.nil?
+ store.valid?(resolve_thumbprint(thumbprint), store_location: native_cert_location, store_name: new_resource.store_name )
else
- Chef::Log.info(cert_obj.display)
+ store.valid?(resolve_thumbprint(thumbprint), store_location: native_cert_location, store_name: new_resource.store_name)
end
end
- def export_cert(cert_obj, cert_path)
- out_file = ::File.new(cert_path, "w+")
- case ::File.extname(cert_path)
- when ".pem"
- out_file.puts(cert_obj.to_pem)
- when ".der"
- out_file.puts(cert_obj.to_der)
- when ".cer"
- cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
- out_file.puts(cert_out)
- when ".crt"
- cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout
- out_file.puts(cert_out)
- when ".pfx"
- cert_out = shell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout
- out_file.puts(cert_out)
- when ".p7b"
- cert_out = shell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
- out_file.puts(cert_out)
- else
- Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b")
- end
- out_file.close
+ # this array structure is solving 2 problems. The first is that we need to have support for both the CurrentUser AND LocalMachine stores
+ # Secondly, we need to pass the proper constant name for each store to win32-certstore but also pass the short name to powershell scripts used here
+ def ps_cert_location
+ new_resource.user_store ? "CurrentUser" : "LocalMachine"
+ end
+
+ def pfx_ps_cmd(thumbprint, store_location: "LocalMachine", store_name: "My", output_path:, password: )
+ <<-CMD
+ $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText
+ $cert = Get-ChildItem -path cert:\\#{store_location}\\#{store_name} -Recurse | Where { $_.Thumbprint -eq "#{thumbprint.upcase}" }
+ Export-PfxCertificate -Cert $cert -FilePath "#{output_path}" -Password $my_pwd
+ CMD
end
- def cert_location
- @location ||= new_resource.user_store ? "CurrentUser" : "LocalMachine"
+ def native_cert_location
+ new_resource.user_store ? CERT_SYSTEM_STORE_CURRENT_USER : CERT_SYSTEM_STORE_LOCAL_MACHINE
end
def cert_script(persist)
cert_script = "$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2"
- file = Chef::Util::PathHelper.cleanpath(new_resource.source)
+ file = Chef::Util::PathHelper.cleanpath(new_resource.source, ps_cert_location)
cert_script << " \"#{file}\""
if ::File.extname(file.downcase) == ".pfx"
cert_script << ", \"#{new_resource.pfx_password}\""
@@ -246,14 +319,14 @@ class Chef
def cert_exists_script(hash)
<<-EOH
$hash = #{hash}
- Test-Path "Cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
+ Test-Path "Cert:\\#{ps_cert_location}\\#{new_resource.store_name}\\$hash"
EOH
end
def within_store_script
inner_script = yield "$store"
<<-EOH
- $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{cert_location})
+ $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{ps_cert_location})
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
#{inner_script}
$store.Close()
@@ -267,7 +340,7 @@ class Chef
# and from https://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx
set_acl_script = <<-EOH
$hash = #{hash}
- $storeCert = Get-ChildItem "cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
+ $storeCert = Get-ChildItem "cert:\\#{ps_cert_location}\\#{new_resource.store_name}\\$hash"
if ($storeCert -eq $null) { throw 'no key exists.' }
$keyname = $storeCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
if ($keyname -eq $null) { throw 'no private key exists.' }
@@ -302,12 +375,50 @@ class Chef
#
# @raise [OpenSSL::PKCS12::PKCS12Error] When incorrect password is provided for PFX certificate
#
- def fetch_cert_object(ext)
- contents = if binary_cert?
- ::File.binread(new_resource.source)
- else
- ::File.read(new_resource.source)
- end
+
+ def fetch_cert_object_from_file(ext)
+ if is_file?(new_resource.source)
+ begin
+ ::File.exist?(new_resource.source)
+ contents = ::File.binread(new_resource.source)
+ rescue => exception
+ message = "Unable to load the certificate object from the specified local path : #{new_resource.source}\n"
+ message << exception.message
+ raise Chef::Exceptions::FileNotFound, message
+ end
+ elsif is_url?(new_resource.source)
+ require "uri" unless defined?(URI)
+ uri = URI(new_resource.source)
+ state = uri.is_a?(URI::HTTP) && !uri.host.nil? ? true : false
+ if state
+ begin
+ output_file_name = get_file_name(new_resource.source)
+ unless Dir.exist?(Chef::Config[:file_cache_path])
+ Dir.mkdir(Chef::Config[:file_cache_path])
+ end
+ local_path = ::File.join(Chef::Config[:file_cache_path], output_file_name)
+ @local_pfx_path = local_path
+ ::File.open(local_path, "wb") do |file|
+ file.write URI.open(new_resource.source).read
+ end
+ rescue => exception
+ message = "Not Able to Download Certificate Object at the URL specified : #{new_resource.source}\n"
+ message << exception.message
+ raise Chef::Exceptions::FileNotFound, message
+ end
+
+ contents = ::File.binread(local_path)
+
+ else
+ message = "Not Able to Download Certificate Object at the URL specified : #{new_resource.source}\n"
+ message << exception.message
+ raise Chef::Exceptions::InvalidRemoteFileURI, message
+ end
+ else
+ message = "You passed an invalid file or url to import. Please check the spelling and try again."
+ message << exception.message
+ raise Chef::Exceptions::ArgumentError, message
+ end
case ext
when ".pfx"
@@ -324,10 +435,42 @@ class Chef
end
end
- # @return [Boolean] Whether the certificate file is binary encoded or not
- #
- def binary_cert?
- shell_out!("file -b --mime-encoding #{new_resource.source}").stdout.strip == "binary"
+ def export_cert(cert_obj, output_path:, store_name:, store_location:, pfx_password:)
+ # Delete the cert if it exists. This is non-destructive in that it only removes the file and not the entire path.
+ # We want to ensure we're not randomly loading an old stinky cert.
+ if ::File.exists?(output_path)
+ ::File.delete(output_path)
+ end
+
+ unless ::File.directory?(::File.dirname(output_path))
+ FileUtils.mkdir_p(::File.dirname(output_path))
+ end
+
+ out_file = ::File.new(output_path, "w+")
+
+ case ::File.extname(output_path)
+ when ".pem"
+ out_file.puts(cert_obj)
+ when ".der"
+ out_file.puts(cert_obj.to_der)
+ when ".cer"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
+ out_file.puts(cert_out)
+ when ".crt"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj} -outform CRT").stdout
+ out_file.puts(cert_out)
+ when ".pfx"
+ pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: store_location, store_name: store_name, output_path: output_path, password: pfx_password )
+ when ".p7b"
+ cert_out = shell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
+ out_file.puts(cert_out)
+ when ".key"
+ out_file.puts(cert_obj)
+ else
+ Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, and .p7b")
+ end
+
+ out_file.close
end
# Imports the certificate object into cert store
@@ -336,18 +479,35 @@ class Chef
#
# @param is_pfx [Boolean] true if we want to import a PFX certificate
#
- def import_certificates(cert_objs, is_pfx)
+ def import_certificates(cert_objs, is_pfx, store_name: new_resource.store_name, store_location: native_cert_location)
[cert_objs].flatten.each do |cert_obj|
- thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s # Fetch its thumbprint
- # Need to check if return value is Boolean:true
- # If not then the given certificate should be added in certstore
- if verify_cert(thumbprint) == true
- Chef::Log.debug("Certificate is already present")
- else
- converge_by("Adding certificate #{new_resource.source} into Store #{new_resource.store_name}") do
- if is_pfx
- add_pfx_cert
+ # thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s
+ # pkcs = OpenSSL::PKCS12.new(cert_obj, new_resource.pfx_password)
+ # cert = OpenSSL::X509::Certificate.new(pkcs.certificate.to_pem)
+ thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s
+ if is_pfx
+ if verify_cert(thumbprint) == true
+ Chef::Log.debug("Certificate is already present")
+ else
+ if is_file?(new_resource.source)
+ converge_by("Creating a PFX #{new_resource.source} for Store #{new_resource.store_name}") do
+ add_pfx_cert(new_resource.source)
+ end
+ elsif is_url?(new_resource.source)
+ converge_by("Creating a PFX #{@local_pfx_path} for Store #{new_resource.store_name}") do
+ add_pfx_cert(@local_pfx_path)
+ end
else
+ message = "You passed an invalid file or url to import. Please check the spelling and try again."
+ message << exception.message
+ raise Chef::Exceptions::ArgumentError, message
+ end
+ end
+ else
+ if verify_cert(thumbprint) == true
+ Chef::Log.debug("Certificate is already present")
+ else
+ converge_by("Creating a certificate #{new_resource.source} for Store #{new_resource.store_name}") do
add_cert(cert_obj)
end
end
diff --git a/lib/chef/resource/windows_defender.rb b/lib/chef/resource/windows_defender.rb
new file mode 100644
index 0000000000..23894ac4a5
--- /dev/null
+++ b/lib/chef/resource/windows_defender.rb
@@ -0,0 +1,163 @@
+#
+# Copyright:: Chef Software, Inc.
+#
+# 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 "../resource"
+
+class Chef
+ class Resource
+ class WindowsDefender < Chef::Resource
+ unified_mode true
+ provides :windows_defender
+
+ description "Use the **windows_defender** resource to enable or disable the Microsoft Windows Defender service."
+ introduced "17.3"
+ examples <<~DOC
+ **Configure Windows Defender AV settings**:
+
+ ```ruby
+ windows_defender 'Configure Defender' do
+ realtime_protection true
+ intrusion_protection_system true
+ lock_ui true
+ scan_archives true
+ scan_scripts true
+ scan_email true
+ scan_removable_drives true
+ scan_network_files false
+ scan_mapped_drives false
+ action :enable
+ end
+ ```
+
+ **Disable Windows Defender AV**:
+
+ ```ruby
+ windows_defender 'Disable Defender' do
+ action :disable
+ end
+ ```
+ DOC
+
+ # DisableIOAVProtection
+ property :realtime_protection, [true, false],
+ default: true,
+ description: "Enable realtime scanning of downloaded files and attachments."
+
+ # DisableIntrusionPreventionSystem
+ property :intrusion_protection_system, [true, false],
+ default: true,
+ description: "Enable network protection against exploitation of known vulnerabilities."
+
+ # UILockdown
+ property :lock_ui, [true, false],
+ description: "Lock the UI to prevent users from changing Windows Defender settings.",
+ default: false
+
+ # DisableArchiveScanning
+ property :scan_archives, [true, false],
+ default: true,
+ description: "Scan file archives such as .zip or .gz archives."
+
+ # DisableScriptScanning
+ property :scan_scripts, [true, false],
+ default: false,
+ description: "Scan scripts in malware scans."
+
+ # DisableEmailScanning
+ property :scan_email, [true, false],
+ default: false,
+ description: "Scan e-mails for malware."
+
+ # DisableRemovableDriveScanning
+ property :scan_removable_drives, [true, false],
+ default: false,
+ description: "Scan content of removable drives."
+
+ # DisableScanningNetworkFiles
+ property :scan_network_files, [true, false],
+ default: false,
+ description: "Scan files on a network."
+
+ # DisableScanningMappedNetworkDrivesForFullScan
+ property :scan_mapped_drives, [true, false],
+ default: true,
+ description: "Scan files on mapped network drives."
+
+ load_current_value do
+ values = powershell_exec!("Get-MPpreference").result
+
+ lock_ui values["UILockdown"]
+ realtime_protection !values["DisableIOAVProtection"]
+ intrusion_protection_system !values["DisableIntrusionPreventionSystem"]
+ scan_archives !values["DisableArchiveScanning"]
+ scan_scripts !values["DisableScriptScanning"]
+ scan_email !values["DisableEmailScanning"]
+ scan_removable_drives !values["DisableRemovableDriveScanning"]
+ scan_network_files !values["DisableScanningNetworkFiles"]
+ scan_mapped_drives !values["DisableScanningMappedNetworkDrivesForFullScan"]
+ end
+
+ action :enable do
+ windows_service "Windows Defender" do
+ service_name "WinDefend"
+ action %i{start enable}
+ startup_type :automatic
+ end
+
+ converge_if_changed do
+ powershell_exec!(set_mppreference_cmd)
+ end
+ end
+
+ action :disable do
+ windows_service "Windows Defender" do
+ service_name "WinDefend"
+ action %i{disable stop}
+ end
+ end
+
+ action_class do
+ require "chef/mixin/powershell_type_coercions"
+ include Chef::Mixin::PowershellTypeCoercions
+
+ PROPERTY_TO_PS_MAP = {
+ realtime_protection: "DisableIOAVProtection",
+ intrusion_protection_system: "DisableIntrusionPreventionSystem",
+ scan_archives: "DisableArchiveScanning",
+ scan_scripts: "DisableScriptScanning",
+ scan_email: "DisableEmailScanning",
+ scan_removable_drives: "DisableRemovableDriveScanning",
+ scan_network_files: "DisableScanningNetworkFiles",
+ scan_mapped_drives: "DisableScanningMappedNetworkDrivesForFullScan",
+ }.freeze
+
+ def set_mppreference_cmd
+ cmd = "Set-MpPreference -Force"
+ cmd << " -UILockdown #{type_coercion(new_resource.lock_ui)}"
+
+ # the values are the opposite in Set-MpPreference and our properties so we have to iterate
+ # over the list and negate the provided values so it makes sense with the cmdlet flag's expected value
+ PROPERTY_TO_PS_MAP.each do |prop, flag|
+ next if new_resource.send(prop).nil? || current_resource.send(prop) == new_resource.send(prop)
+
+ cmd << " -#{flag} #{type_coercion(!new_resource.send(prop))}"
+ end
+ cmd
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_defender_exclusion.rb b/lib/chef/resource/windows_defender_exclusion.rb
new file mode 100644
index 0000000000..ad9afd77e2
--- /dev/null
+++ b/lib/chef/resource/windows_defender_exclusion.rb
@@ -0,0 +1,125 @@
+#
+# Copyright:: Chef Software, Inc.
+#
+# 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 "../resource"
+
+class Chef
+ class Resource
+ class WindowsDefenderExclusion < Chef::Resource
+
+ provides :windows_defender_exclusion
+
+ description "Use the **windows_defender_exclusion** resource to exclude paths, processes, or file types from Windows Defender realtime protection scanning."
+ introduced "17.3"
+ examples <<~DOC
+ **Add excluded items to Windows Defender scans**:
+
+ ```ruby
+ windows_defender_exclusion 'Add to things to be excluded from scanning' do
+ paths 'c:\\foo\\bar, d:\\bar\\baz'
+ extensions 'png, foo, ppt, doc'
+ process_paths 'c:\\windows\\system32'
+ action :add
+ end
+ ```
+
+ **Remove excluded items from Windows Defender scans**:
+
+ ```ruby
+ windows_defender_exclusion 'Remove things from the list to be excluded' do
+ process_paths 'c:\\windows\\system32'
+ action :remove
+ end
+ ```
+ DOC
+ unified_mode true
+
+ property :paths, [String, Array], default: [],
+ coerce: proc { |x| to_consistent_path_array(x) },
+ description: "File or directory paths to exclude from scanning."
+
+ property :extensions, [String, Array], default: [],
+ coerce: proc { |x| to_consistent_path_array(x) },
+ description: "File extensions to exclude from scanning."
+
+ property :process_paths, [String, Array], default: [],
+ coerce: proc { |x| to_consistent_path_array(x) },
+ description: "Paths to executables to exclude from scanning."
+
+ def to_consistent_path_array(x)
+ fixed = x.dup || []
+ fixed = fixed.split(/\s*,\s*/) if fixed.is_a?(String)
+ fixed.map!(&:downcase)
+ fixed.map! { |v| v.gsub(%r{/}, "\\") }
+ fixed
+ end
+
+ load_current_value do |new_resource|
+ Chef::Log.debug("Running 'Get-MpPreference | Select-Object ExclusionExtension,ExclusionPath,ExclusionProcess' to get Windows Defender State")
+
+ values = powershell_exec!("Get-MPpreference | Select-Object ExclusionExtension,ExclusionPath,ExclusionProcess").result
+
+ values.transform_values! { |x| Array(x) }
+
+ paths new_resource.paths & values["ExclusionPath"]
+ extensions new_resource.extensions & values["ExclusionExtension"]
+ process_paths new_resource.process_paths & values["ExclusionProcess"]
+ end
+
+ action :add do
+ converge_if_changed do
+ powershell_exec!(add_cmd)
+ end
+ end
+
+ action :remove do
+ converge_if_changed do
+ powershell_exec!(remove_cmd)
+ end
+ end
+
+ action_class do
+ MAPPING = {
+ paths: "ExclusionPath",
+ extensions: "ExclusionExtension",
+ process_paths: "ExclusionProcess",
+ }.freeze
+
+ def add_cmd
+ cmd = "Add-MpPreference -Force"
+
+ MAPPING.each do |prop, flag|
+ to_add = new_resource.send(prop) - current_resource.send(prop)
+ cmd << " -#{flag} #{to_add.join(",")}" unless to_add.empty?
+ end
+
+ cmd
+ end
+
+ def remove_cmd
+ cmd = "Remove-MpPreference -Force"
+
+ MAPPING.each do |prop, flag|
+ to_add = new_resource.send(prop) & current_resource.send(prop)
+ cmd << " -#{flag} #{to_add.join(",")}" unless to_add.empty?
+ end
+
+ cmd
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/windows_dfs_folder.rb b/lib/chef/resource/windows_dfs_folder.rb
index 31f6814bcf..b9ef4525f9 100644
--- a/lib/chef/resource/windows_dfs_folder.rb
+++ b/lib/chef/resource/windows_dfs_folder.rb
@@ -42,9 +42,7 @@ class Chef
property :description, String,
description: "Description for the share."
- action :create do
- description "Creates the folder in dfs namespace."
-
+ action :create, description: "Creates the folder in dfs namespace." do
raise "target_path is required for install" unless property_is_set?(:target_path)
raise "description is required for install" unless property_is_set?(:description)
@@ -62,9 +60,7 @@ class Chef
end
end
- action :delete do
- description "Deletes the folder in the dfs namespace."
-
+ action :delete, description: "Deletes the folder in the dfs namespace." do
powershell_script "Delete DFS Namespace" do
code <<-EOH
Remove-DfsnFolder -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}\\#{new_resource.folder_path}' -Force
diff --git a/lib/chef/resource/windows_dfs_namespace.rb b/lib/chef/resource/windows_dfs_namespace.rb
index ddd8a0ee26..2fdfd8ebb6 100644
--- a/lib/chef/resource/windows_dfs_namespace.rb
+++ b/lib/chef/resource/windows_dfs_namespace.rb
@@ -52,9 +52,7 @@ class Chef
description: "The root from which to create the DFS tree. Defaults to C:\\DFSRoots.",
default: 'C:\\DFSRoots'
- action :create do
- description "Creates the dfs namespace on the server."
-
+ action :create, description: "Creates the dfs namespace on the server." do
directory file_path do
action :create
recursive true
@@ -84,9 +82,7 @@ class Chef
end
end
- action :delete do
- description "Deletes a DFS Namespace including the directory on disk."
-
+ action :delete, description: "Deletes a DFS Namespace including the directory on disk." do
powershell_script "Delete DFS Namespace" do
code <<-EOH
Remove-DfsnRoot -Path '\\\\#{ENV["COMPUTERNAME"]}\\#{new_resource.namespace_name}' -Force
diff --git a/lib/chef/resource/windows_dfs_server.rb b/lib/chef/resource/windows_dfs_server.rb
index fc161f8189..e93185c245 100644
--- a/lib/chef/resource/windows_dfs_server.rb
+++ b/lib/chef/resource/windows_dfs_server.rb
@@ -65,9 +65,7 @@ class Chef
sync_interval_secs results["SyncIntervalSec"]
end
- action :configure do
- description "Configure DFS settings."
-
+ action :configure, description: "Configure DFS settings" do
converge_if_changed do
dfs_cmd = "Set-DfsnServerConfiguration -ComputerName '#{ENV["COMPUTERNAME"]}' -UseFqdn $#{new_resource.use_fqdn} -LdapTimeoutSec #{new_resource.ldap_timeout_secs} -SyncIntervalSec #{new_resource.sync_interval_secs}"
dfs_cmd << " -EnableSiteCostedReferrals $#{new_resource.enable_site_costed_referrals}" if new_resource.enable_site_costed_referrals != current_resource.enable_site_costed_referrals
diff --git a/lib/chef/resource/windows_dns_record.rb b/lib/chef/resource/windows_dns_record.rb
index 329e1a3857..8b908f09e6 100644
--- a/lib/chef/resource/windows_dns_record.rb
+++ b/lib/chef/resource/windows_dns_record.rb
@@ -49,9 +49,7 @@ class Chef
default: "localhost",
introduced: "16.3"
- action :create do
- description "Creates and updates the DNS entry."
-
+ action :create, description: "Creates and updates the DNS entry." do
windows_feature "RSAT-DNS-Server" do
not_if new_resource.dns_server.casecmp?("localhost")
end
@@ -61,9 +59,7 @@ class Chef
run_dsc_resource "Present"
end
- action :delete do
- description "Deletes a DNS entry."
-
+ action :delete, description: "Deletes a DNS entry." do
windows_feature "RSAT-DNS-Server" do
not_if new_resource.dns_server.casecmp?("localhost")
end
diff --git a/lib/chef/resource/windows_dns_zone.rb b/lib/chef/resource/windows_dns_zone.rb
index 09555c880c..eb7e42c3ce 100644
--- a/lib/chef/resource/windows_dns_zone.rb
+++ b/lib/chef/resource/windows_dns_zone.rb
@@ -40,17 +40,13 @@ class Chef
description: "The type of DNS server, Domain or Standalone.",
default: "Domain", equal_to: %w{Domain Standalone}
- action :create do
- description "Creates and updates a DNS Zone."
-
+ action :create, description: "Creates and updates a DNS Zone." do
powershell_package "xDnsServer"
run_dsc_resource "Present"
end
- action :delete do
- description "Deletes a DNS Zone."
-
+ action :delete, description: "Deletes a DNS Zone." do
powershell_package "xDnsServer"
run_dsc_resource "Absent"
diff --git a/lib/chef/resource/windows_env.rb b/lib/chef/resource/windows_env.rb
index ab65465ed6..c8385ecc1b 100644
--- a/lib/chef/resource/windows_env.rb
+++ b/lib/chef/resource/windows_env.rb
@@ -19,6 +19,7 @@
require_relative "../resource"
require_relative "../mixin/windows_env_helper"
+require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
@@ -28,7 +29,7 @@ class Chef
provides :windows_env
provides :env # backwards compat with the pre-Chef 14 resource name
- description "Use the **windows_env** resource to manage environment keys in Microsoft Windows. After an environment key is set, Microsoft Windows must be restarted before the environment key will be available to the Task Scheduler."
+ description "Use the **windows_env** resource to manage environment keys in Microsoft Windows. After an environment key is set, Microsoft Windows must be restarted before the environment key will be available to the Task Scheduler.\n\nThis resource was previously called the **env** resource; its name was updated in #{ChefUtils::Dist::Infra::PRODUCT} 14.0 to reflect the fact that only Windows is supported. Existing cookbooks using `env` will continue to function, but should be updated to use the new name. Note: On UNIX-based systems, the best way to manipulate environment keys is with the `ENV` variable in Ruby; however, this approach does not have the same permanent effect as using the windows_env resource."
examples <<~DOC
**Set an environment variable**:
@@ -185,14 +186,14 @@ class Chef
if environment_variables && environment_variables.length > 0
environment_variables.each do |env|
@env_obj = env.wmi_ole_object
- return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0
+ return @env_obj if @env_obj.username.split("\\").last.casecmp(new_resource.user) == 0
end
end
@env_obj = nil
end
end
- action :create do
+ action :create, description: "Create an environment variable. If an environment variable already exists (but does not match), update that environment variable to match." do
if key_exists?
if requires_modify_or_create?
modify_env
@@ -206,7 +207,7 @@ class Chef
end
end
- action :delete do
+ action :delete, description: "Delete an environment variable." do
if ( ENV[new_resource.key_name] || key_exists? ) && !delete_element
delete_env
logger.info("#{new_resource} deleted")
@@ -214,7 +215,7 @@ class Chef
end
end
- action :modify do
+ action :modify, description: "Modify an existing environment variable. This prepends the new value to the existing value, using the delimiter specified by the `delim` property." do
if key_exists?
if requires_modify_or_create?
modify_env
diff --git a/lib/chef/resource/windows_feature.rb b/lib/chef/resource/windows_feature.rb
index 760a7fe3f1..ae71b299fe 100644
--- a/lib/chef/resource/windows_feature.rb
+++ b/lib/chef/resource/windows_feature.rb
@@ -108,21 +108,15 @@ class Chef
default: 600,
desired_state: false
- action :install do
- description "Install a Windows role/feature"
-
+ action :install, description: "Install a Windows role or feature." do
run_default_subresource :install
end
- action :remove do
- description "Remove a Windows role/feature"
-
+ action :remove, description: "Remove a Windows role or feature." do
run_default_subresource :remove
end
- action :delete do
- description "Remove a Windows role/feature from the image"
-
+ action :delete, description: "Remove a Windows role or feature from the image." do
run_default_subresource :delete
end
diff --git a/lib/chef/resource/windows_feature_dism.rb b/lib/chef/resource/windows_feature_dism.rb
index c9e2f355dc..a2d91eaede 100644
--- a/lib/chef/resource/windows_feature_dism.rb
+++ b/lib/chef/resource/windows_feature_dism.rb
@@ -65,9 +65,7 @@ class Chef
x.map(&:downcase)
end
- action :install do
- description "Install a Windows role/feature using DISM"
-
+ action :install, description: "Install a Windows role/feature using DISM." do
reload_cached_dism_data unless node["dism_features_cache"]
fail_if_unavailable # fail if the features don't exist
@@ -91,9 +89,7 @@ class Chef
end
end
- action :remove do
- description "Remove a Windows role/feature using DISM"
-
+ action :remove, description: "Remove a Windows role or feature using DISM." do
reload_cached_dism_data unless node["dism_features_cache"]
logger.trace("Windows features needing removal: #{features_to_remove.empty? ? "none" : features_to_remove.join(",")}")
@@ -108,9 +104,7 @@ class Chef
end
end
- action :delete do
- description "Remove a Windows role/feature from the image using DISM"
-
+ action :delete, description: "Remove a Windows role or feature from the image using DISM." do
reload_cached_dism_data unless node["dism_features_cache"]
fail_if_unavailable # fail if the features don't exist
diff --git a/lib/chef/resource/windows_feature_powershell.rb b/lib/chef/resource/windows_feature_powershell.rb
index 735ed080ff..e9889eb954 100644
--- a/lib/chef/resource/windows_feature_powershell.rb
+++ b/lib/chef/resource/windows_feature_powershell.rb
@@ -87,7 +87,7 @@ class Chef
x.map(&:downcase)
end
- action :install do
+ action :install, description: "Install a Windows role or feature using PowerShell." do
reload_cached_powershell_data unless node["powershell_features_cache"]
fail_if_unavailable # fail if the features don't exist
fail_if_removed # fail if the features are in removed state
@@ -108,7 +108,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a Windows role or feature using PowerShell." do
reload_cached_powershell_data unless node["powershell_features_cache"]
Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? "none" : features_to_remove.join(",")}")
@@ -123,7 +123,7 @@ class Chef
end
end
- action :delete do
+ action :delete, description: "Delete a Windows role or feature from the image using PowerShell." do
reload_cached_powershell_data unless node["powershell_features_cache"]
fail_if_unavailable # fail if the features don't exist
diff --git a/lib/chef/resource/windows_firewall_profile.rb b/lib/chef/resource/windows_firewall_profile.rb
index ada9729699..573f4b2cfa 100644
--- a/lib/chef/resource/windows_firewall_profile.rb
+++ b/lib/chef/resource/windows_firewall_profile.rb
@@ -81,8 +81,8 @@ class Chef
property :allow_unicast_response, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Allow unicast responses to multicast and broadcast messages"
property :display_notification, [true, false, String], equal_to: [true, false, "NotConfigured"], description: "Display a notification when firewall blocks certain activity"
- load_current_value do |desired|
- ps_get_net_fw_profile = load_firewall_state(desired.profile)
+ load_current_value do |new_resource|
+ ps_get_net_fw_profile = load_firewall_state(new_resource.profile)
output = powershell_exec(ps_get_net_fw_profile)
if output.result.empty?
current_value_does_not_exist!
@@ -121,7 +121,7 @@ class Chef
end
end
- action :enable do
+ action :enable, description: "Enable and optionally configure a Windows Firewall profile." do
converge_if_changed :default_inbound_action, :default_outbound_action, :allow_inbound_rules, :allow_local_firewall_rules,
:allow_local_ipsec_rules, :allow_user_apps, :allow_user_ports, :allow_unicast_response, :display_notification do
fw_cmd = firewall_command(new_resource.profile)
@@ -135,7 +135,7 @@ class Chef
end
end
- action :disable do
+ action :disable, description: "Disable a Windows Firewall profile." do
if firewall_enabled?(new_resource.profile)
converge_by "Disable the #{new_resource.profile} Firewall Profile" do
cmd = "Set-NetFirewallProfile -Profile #{new_resource.profile} -Enabled \"False\""
diff --git a/lib/chef/resource/windows_firewall_rule.rb b/lib/chef/resource/windows_firewall_rule.rb
index a6f0614362..e397a94670 100644
--- a/lib/chef/resource/windows_firewall_rule.rb
+++ b/lib/chef/resource/windows_firewall_rule.rb
@@ -39,6 +39,19 @@ class Chef
end
```
+ **Configuring multiple remote-address ports on a rule**:
+
+ ```ruby
+ windows_firewall_rule 'MyRule' do
+ description 'Testing out remote address arrays'
+ enabled false
+ local_port 1434
+ remote_address %w(10.17.3.101 172.7.7.53)
+ protocol 'TCP'
+ action :create
+ end
+ ```
+
**Allow protocol ICMPv6 with ICMP Type**:
```ruby
@@ -97,8 +110,9 @@ class Chef
coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map(&:to_s) },
description: "The local port the firewall rule applies to."
- property :remote_address, String,
- description: "The remote address the firewall rule applies to."
+ property :remote_address, [String, Array],
+ coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map(&:to_s) },
+ description: "The remote address(es) the firewall rule applies to."
property :remote_port, [String, Integer, Array],
# split various formats of comma separated lists and provide a sorted array of strings to match PS output
@@ -172,7 +186,7 @@ class Chef
group state["group"]
local_address state["local_address"]
local_port Array(state["local_port"]).sort
- remote_address state["remote_address"]
+ remote_address Array(state["remote_address"]).sort
remote_port Array(state["remote_port"]).sort
direction state["direction"]
protocol state["protocol"]
@@ -185,8 +199,7 @@ class Chef
enabled state["enabled"]
end
- action :create do
- description "Create a Windows firewall entry."
+ action :create, description: "Create a Windows firewall entry." do
if current_resource
converge_if_changed :rule_name, :description, :displayname, :local_address, :local_port, :remote_address,
:remote_port, :direction, :protocol, :icmp_type, :firewall_action, :profile, :program, :service,
@@ -207,9 +220,7 @@ class Chef
end
end
- action :delete do
- description "Delete an existing Windows firewall entry."
-
+ action :delete, description: "Delete an existing Windows firewall entry." do
if current_resource
converge_by("delete firewall rule #{new_resource.rule_name}") do
powershell_exec!("Remove-NetFirewallRule -Name '#{new_resource.rule_name}'")
@@ -230,7 +241,7 @@ class Chef
cmd << " -Description '#{new_resource.description}'" if new_resource.description
cmd << " -LocalAddress '#{new_resource.local_address}'" if new_resource.local_address
cmd << " -LocalPort '#{new_resource.local_port.join("', '")}'" if new_resource.local_port
- cmd << " -RemoteAddress '#{new_resource.remote_address}'" if new_resource.remote_address
+ cmd << " -RemoteAddress '#{new_resource.remote_address.join("', '")}'" if new_resource.remote_address
cmd << " -RemotePort '#{new_resource.remote_port.join("', '")}'" if new_resource.remote_port
cmd << " -Direction '#{new_resource.direction}'" if new_resource.direction
cmd << " -Protocol '#{new_resource.protocol}'" if new_resource.protocol
diff --git a/lib/chef/resource/windows_font.rb b/lib/chef/resource/windows_font.rb
index c9128aa4b0..2a8aaa3feb 100644
--- a/lib/chef/resource/windows_font.rb
+++ b/lib/chef/resource/windows_font.rb
@@ -43,13 +43,11 @@ class Chef
property :source, String,
description: "A local filesystem path or URI that is used to source the font file.",
- coerce: proc { |x| /^.:.*/.match?(x) ? x.tr('\\', "/").gsub("//", "/") : x }
-
- action :install do
- description "Install a font to the system fonts directory."
+ coerce: proc { |x| /^.:.*/.match?(x) ? x.tr("\\", "/").gsub("//", "/") : x }
+ action :install, description: "Install a font to the system fonts directory." do
if font_exists?
- logger.trace("Not installing font: #{new_resource.font_name} as font already installed.")
+ logger.debug("Not installing font: #{new_resource.font_name} as font already installed.")
else
retrieve_cookbook_font
install_font
diff --git a/lib/chef/resource/windows_pagefile.rb b/lib/chef/resource/windows_pagefile.rb
index 4dfaae3be3..6fb63f2f3b 100644
--- a/lib/chef/resource/windows_pagefile.rb
+++ b/lib/chef/resource/windows_pagefile.rb
@@ -39,16 +39,26 @@ class Chef
```ruby
windows_pagefile 'Delete the pagefile' do
- path 'C:\pagefile.sys'
+ path 'C'
action :delete
end
```
+ **Switch to system managed pagefiles**:
+
+ ```ruby
+ windows_pagefile 'Change the pagefile to System Managed' do
+ path 'E:\'
+ system_managed true
+ action :set
+ end
+ ```
+
**Create a pagefile with an initial and maximum size**:
```ruby
- windows_pagefile 'create the pagefile' do
- path 'C:\pagefile.sys'
+ windows_pagefile 'create the pagefile with these sizes' do
+ path 'f:\'
initial_size 100
maximum_size 200
end
@@ -56,7 +66,7 @@ class Chef
DOC
property :path, String,
- coerce: proc { |x| x.tr("/", '\\') },
+ coerce: proc { |x| x.tr("/", "\\") },
description: "An optional property to set the pagefile name if it differs from the resource block's name.",
name_property: true
@@ -64,8 +74,7 @@ class Chef
description: "Configures whether the system manages the pagefile size."
property :automatic_managed, [TrueClass, FalseClass],
- description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties.",
- default: false
+ description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties."
property :initial_size, Integer,
description: "Initial size of the pagefile in megabytes."
@@ -73,25 +82,26 @@ class Chef
property :maximum_size, Integer,
description: "Maximum size of the pagefile in megabytes."
- action :set do
- description "Configures the default pagefile, creating if it doesn't exist."
-
- pagefile = new_resource.path
- initial_size = new_resource.initial_size
- maximum_size = new_resource.maximum_size
- system_managed = new_resource.system_managed
+ action :set, description: "Configures the default pagefile, creating if it doesn't exist." do
automatic_managed = new_resource.automatic_managed
if automatic_managed
set_automatic_managed unless automatic_managed?
- else
+ elsif automatic_managed == false
unset_automatic_managed if automatic_managed?
+ else
+ pagefile = clarify_pagefile_name
+ initial_size = new_resource.initial_size
+ maximum_size = new_resource.maximum_size
+ system_managed = new_resource.system_managed
- # Check that the resource is not just trying to unset automatic managed, if it is do nothing more
- if (initial_size && maximum_size) || system_managed
- validate_name
- create(pagefile) unless exists?(pagefile)
+ # the method below is designed to raise an exception if the drive you are trying to create a pagefile for doesn't exist.
+ # PowerShell will happily let you create a pagefile called h:\pagefile.sys even though you don't have an H:\ drive.
+
+ pagefile_drive_exist?(pagefile)
+ create(pagefile) unless exists?(pagefile)
+ if (initial_size && maximum_size) || system_managed
if system_managed
set_system_managed(pagefile) unless max_and_min_set?(pagefile, 0, 0)
else
@@ -103,23 +113,33 @@ class Chef
end
end
- action :delete do
- description "Deletes the specified pagefile."
-
- validate_name
- delete(new_resource.path) if exists?(new_resource.path)
+ action :delete, description: "Deletes the specified pagefile." do
+ pagefile = clarify_pagefile_name
+ delete(pagefile) if exists?(pagefile)
end
action_class do
private
- # make sure the provided name property matches the appropriate format
- # we do this here and not in the property itself because if automatic_managed
- # is set then this validation is not necessary / doesn't make sense at all
- def validate_name
- return if /^.:.*.sys/.match?(new_resource.path)
+ # We are adding support for a number of possibilities for how users will express the drive and location they want the pagefile written to.
+ def clarify_pagefile_name
+ case new_resource.path
+ # user enters C, C:, C:\, C:\\
+ when /^[a-zA-Z]/
+ new_resource.path[0] + ":\\pagefile.sys"
+ # user enters C:\pagefile.sys OR c:\foo\bar\pagefile.sys as the path
+ when /^[a-zA-Z]:.*.sys/
+ new_resource.path
+ else
+ raise "#{new_resource.path} does not match the format DRIVE:\\path\\pagefile.sys for pagefiles. Example: C:\\pagefile.sys"
+ end
+ end
- raise "#{new_resource.path} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys"
+ # raise an exception if the target drive location is invalid
+ def pagefile_drive_exist?(pagefile)
+ if ::Dir.exist?(pagefile[0] + ":\\") == false
+ raise "You are trying to create a pagefile on a drive that does not exist!"
+ end
end
# See if the pagefile exists
@@ -128,9 +148,11 @@ class Chef
# @return [Boolean]
def exists?(pagefile)
@exists ||= begin
- logger.trace("Checking if #{pagefile} exists by running: wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list")
- cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
- cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i)
+ logger.trace("Checking if #{pagefile} exists by running: Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($pagefile)} ")
+ cmd = "$page_file_name = '#{pagefile}';"
+ cmd << "$pagefile = Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($page_file_name)};"
+ cmd << "if ([string]::IsNullOrEmpty($pagefile)) { return $false } else { return $true }"
+ powershell_exec!(cmd).result
end
end
@@ -141,11 +163,14 @@ class Chef
# @param [String] max the minimum size of the pagefile
# @return [Boolean]
def max_and_min_set?(pagefile, min, max)
- @max_and_min_set ||= begin
- logger.trace("Checking if #{pagefile} min: #{min} and max #{max} are set")
- cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0])
- cmd.stderr.empty? && (cmd.stdout =~ /InitialSize=#{min}/i) && (cmd.stdout =~ /MaximumSize=#{max}/i)
- end
+ logger.trace("Checking if #{pagefile} has max and initial disk size values set")
+ cmd = "$page_file = '#{pagefile}';"
+ cmd << "$driveLetter = $page_file.split(':')[0];"
+ cmd << "$page_file_settings = Get-CimInstance -ClassName Win32_PageFileSetting -Filter \"SettingID='pagefile.sys @ $($driveLetter):'\" -Property * -ErrorAction Stop;"
+ cmd << "if ($page_file_settings.InitialSize -eq #{min} -and $page_file_settings.MaximumSize -eq #{max})"
+ cmd << "{ return $true }"
+ cmd << "else { return $false }"
+ powershell_exec!(cmd).result
end
# create a pagefile
@@ -153,9 +178,10 @@ class Chef
# @param [String] pagefile path to the pagefile
def create(pagefile)
converge_by("create pagefile #{pagefile}") do
- logger.trace("Running wmic.exe pagefileset create name=\"#{pagefile}\"")
- cmd = shell_out("wmic.exe pagefileset create name=\"#{pagefile}\"")
- check_for_errors(cmd.stderr)
+ logger.trace("Running New-CimInstance -ClassName Win32_PageFileSetting to create new pagefile : #{pagefile}")
+ powershell_exec! <<~ELM
+ New-CimInstance -ClassName Win32_PageFileSetting -Property @{Name = "#{pagefile}"}
+ ELM
end
end
@@ -164,9 +190,13 @@ class Chef
# @param [String] pagefile path to the pagefile
def delete(pagefile)
converge_by("remove pagefile #{pagefile}") do
- logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
- cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
- check_for_errors(cmd.stderr)
+ logger.trace("Running Remove-CimInstance for pagefile : #{pagefile}")
+ powershell_exec! <<~EOL
+ $page_file = "#{pagefile}"
+ $driveLetter = $page_file.split(':')[0]
+ $PageFile = (Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop)
+ $null = ($PageFile | Remove-CimInstance -ErrorAction SilentlyContinue)
+ EOL
end
end
@@ -176,26 +206,31 @@ class Chef
def automatic_managed?
@automatic_managed ||= begin
logger.trace("Checking if pagefiles are automatically managed")
- cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" get AutomaticManagedPagefile /format:list")
- cmd.stderr.empty? && (cmd.stdout =~ /AutomaticManagedPagefile=TRUE/i)
+ cmd = "$sys = Get-CimInstance Win32_ComputerSystem -Property *;"
+ cmd << "return $sys.AutomaticManagedPagefile"
+ powershell_exec!(cmd).result
end
end
# turn on automatic management of all pagefiles by Windows
def set_automatic_managed
- converge_by("set pagefile to Automatic Managed") do
- logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
- cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
- check_for_errors(cmd.stderr)
+ converge_by("Set pagefile to Automatic Managed") do
+ logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru")
+ powershell_exec! <<~EOH
+ $sys = Get-CimInstance Win32_ComputerSystem -Property *
+ Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru
+ EOH
end
end
# turn off automatic management of all pagefiles by Windows
def unset_automatic_managed
- converge_by("set pagefile to User Managed") do
- logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
- cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
- check_for_errors(cmd.stderr)
+ converge_by("Turn off Automatically Managed on pagefiles") do
+ logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru")
+ powershell_exec! <<~EOH
+ $sys = Get-CimInstance Win32_ComputerSystem -Property *
+ Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru
+ EOH
end
end
@@ -206,9 +241,14 @@ class Chef
# @param [String] max the minimum size of the pagefile
def set_custom_size(pagefile, min, max)
converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do
- logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}")
- cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", returns: [0])
- check_for_errors(cmd.stderr)
+ logger.trace("Set-CimInstance -Property @{InitialSize = #{min} MaximumSize = #{max}")
+ powershell_exec! <<~EOD
+ $page_file = "#{pagefile}"
+ $driveLetter = $page_file.split(':')[0]
+ Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{
+ InitialSize = #{min}
+ MaximumSize = #{max}}
+ EOD
end
end
@@ -217,21 +257,16 @@ class Chef
# @param [String] pagefile path to the pagefile
def set_system_managed(pagefile)
converge_by("set #{pagefile} to System Managed") do
- logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0")
- cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0])
- check_for_errors(cmd.stderr)
+ logger.trace("Running ")
+ powershell_exec! <<~EOM
+ $page_file = "#{pagefile}"
+ $driveLetter = $page_file.split(':')[0]
+ Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{
+ InitialSize = 0
+ MaximumSize = 0}
+ EOM
end
end
-
- def get_setting_id(pagefile)
- split_path = pagefile.split('\\')
- "#{split_path[1]} @ #{split_path[0]}"
- end
-
- # raise if there's an error on stderr on a shellout
- def check_for_errors(stderr)
- raise stderr.chomp unless stderr.empty?
- end
end
end
end
diff --git a/lib/chef/resource/windows_path.rb b/lib/chef/resource/windows_path.rb
index 870ffdef3f..625ac01fd0 100644
--- a/lib/chef/resource/windows_path.rb
+++ b/lib/chef/resource/windows_path.rb
@@ -64,7 +64,7 @@ class Chef
end
end
- action :add do
+ action :add, description: "Add an item to the system path." do
# The windows Env provider does not correctly expand variables in
# the PATH environment variable. Ruby expects these to be expanded.
#
@@ -72,11 +72,11 @@ class Chef
env "path" do
action :modify
delim ::File::PATH_SEPARATOR
- value path.tr("/", '\\')
+ value path.tr("/", "\\")
end
end
- action :remove do
+ action :remove, description: "Remove an item from the system path." do
# The windows Env provider does not correctly expand variables in
# the PATH environment variable. Ruby expects these to be expanded.
#
@@ -84,7 +84,7 @@ class Chef
env "path" do
action :delete
delim ::File::PATH_SEPARATOR
- value path.tr("/", '\\')
+ value path.tr("/", "\\")
end
end
end
diff --git a/lib/chef/resource/windows_printer.rb b/lib/chef/resource/windows_printer.rb
index dea15ba112..24d746b648 100644
--- a/lib/chef/resource/windows_printer.rb
+++ b/lib/chef/resource/windows_printer.rb
@@ -1,6 +1,7 @@
#
# Author:: Doug Ireton (<doug@1strategy.com>)
# Copyright:: 2012-2018, Nordstrom, Inc.
+# Copyright:: Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,6 +22,10 @@ require_relative "../resource"
class Chef
class Resource
+ # @todo
+ # 1. Allow updating the printer properties
+ # 2. Fail with a warning if the port can't be found and create_port is false
+ # 3. Fail with helpful messaging if the printer driver can't be installed
class WindowsPrinter < Chef::Resource
unified_mode true
@@ -28,7 +33,7 @@ class Chef
provides(:windows_printer) { true }
- description "Use the **windows_printer** resource to setup Windows printers. Note that this doesn't currently install a printer driver. You must already have the driver installed on the system."
+ description "Use the **windows_printer** resource to setup Windows printers. This resource will automatically install the driver specified in the `driver_name` property and will automatically create a printer port using either the `ipv4_address` property or the `port_name property."
introduced "14.0"
examples <<~DOC
**Create a printer**:
@@ -49,6 +54,23 @@ class Chef
action :delete
end
```
+
+ **Create a printer port and a printer that uses that port (new in 17.3)**
+
+ ```ruby
+ windows_printer_port '10.4.64.39' do
+ port_name 'My awesome printer port'
+ snmp_enabled true
+ port_protocol 2
+ end
+
+ windows_printer 'HP LaserJet 5th Floor' do
+ driver_name 'HP LaserJet 4100 Series PCL6'
+ port_name 'My awesome printer port'
+ ipv4_address '10.4.64.38'
+ create_port false
+ end
+ ```
DOC
property :device_id, String,
@@ -83,82 +105,79 @@ class Chef
proc { |v| v.match(Resolv::IPv4::Regex) },
}
- PRINTERS_REG_KEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\\'.freeze unless defined?(PRINTERS_REG_KEY)
+ property :create_port, [TrueClass, FalseClass],
+ description: "Create a printer port for the printer. Set this to false and specify the `port_name` property if using the `windows_printer_port` resource to create the port instead.",
+ introduced: "17.3",
+ default: true, desired_state: false
- # @todo Set @current_resource printer properties from registry
- load_current_value do |desired|
- name desired.name
- end
+ property :port_name, String,
+ description: "The port name.",
+ default: lazy { |x| "IP_#{x.ipv4_address}" },
+ introduced: "17.3",
+ default_description: "The resource block name or the ipv4_address prepended with IP_."
- action :create do
- description "Create a new printer and a printer port if one doesn't already exist."
+ load_current_value do |new_resource|
+ printer_data = powershell_exec(%Q{Get-WmiObject -Class Win32_Printer -Filter "Name='#{new_resource.device_id}'"}).result
- if printer_exists?
- Chef::Log.info "#{@new_resource} already exists - nothing to do."
+ if printer_data.empty?
+ current_value_does_not_exist!
else
- converge_by("Create #{@new_resource}") do
- create_printer
+ device_id new_resource.device_id
+ comment printer_data["Comment"]
+ default printer_data["Default"]
+ location printer_data["Location"]
+ shared printer_data["Shared"]
+ share_name printer_data["ShareName"]
+ port_name printer_data["PortName"]
+
+ driver_data = powershell_exec(%Q{Get-PrinterDriver -Name="#{new_resource.driver_name}"}).result
+ unless driver_data.empty?
+ driver_name new_resource.driver_name
end
end
end
- action :delete do
- description "Delete an existing printer. Note this does not delete the associated printer port."
-
- if printer_exists?
- converge_by("Delete #{@new_resource}") do
- delete_printer
- end
+ action :create, description: "Create a new printer and printer port, if one doesn't already." do
+ if current_resource
+ Chef::Log.info "#{@new_resource} already exists - nothing to do."
else
- Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
- end
- end
+ # Create the printer port first unless the property is set to false
+ if new_resource.create_port
+ windows_printer_port new_resource.port_name do
+ ipv4_address new_resource.ipv4_address
+ port_name new_resource.port_name
+ end
+ end
- action_class do
- private
-
- # does the printer exist
- #
- # @param [String] name the name of the printer
- # @return [Boolean]
- def printer_exists?
- printer_reg_key = PRINTERS_REG_KEY + new_resource.name
- logger.trace "Checking to see if this reg key exists: '#{printer_reg_key}'"
- registry_key_exists?(printer_reg_key)
- end
+ converge_by("install driver #{new_resource.driver_name}") do
+ powershell_exec!("Add-PrinterDriver -Name '#{new_resource.driver_name}'")
+ end
- # creates the printer port and then the printer
- def create_printer
- # Create the printer port first
- windows_printer_port new_resource.ipv4_address
-
- port_name = "IP_#{new_resource.ipv4_address}"
-
- declare_resource(:powershell_script, "Creating printer: #{new_resource.device_id}") do
- code <<-EOH
-
- Set-WmiInstance -class Win32_Printer `
- -EnableAllPrivileges `
- -Argument @{ DeviceID = "#{new_resource.device_id}";
- Comment = "#{new_resource.comment}";
- Default = "$#{new_resource.default}";
- DriverName = "#{new_resource.driver_name}";
- Location = "#{new_resource.location}";
- PortName = "#{port_name}";
- Shared = "$#{new_resource.shared}";
- ShareName = "#{new_resource.share_name}";
- }
+ converge_by("create #{@new_resource.device_id}") do
+ powershell_exec! <<-EOH
+ Set-WmiInstance -class Win32_Printer `
+ -EnableAllPrivileges `
+ -Argument @{ DeviceID = "#{new_resource.device_id}";
+ Comment = "#{new_resource.comment}";
+ Default = "$#{new_resource.default}";
+ DriverName = "#{new_resource.driver_name}";
+ Location = "#{new_resource.location}";
+ PortName = "#{new_resource.port_name}";
+ Shared = "$#{new_resource.shared}";
+ ShareName = "#{new_resource.share_name}";
+ }
EOH
end
end
+ end
- def delete_printer
- declare_resource(:powershell_script, "Deleting printer: #{new_resource.device_id}") do
- code <<-EOH
- $printer = Get-WMIObject -class Win32_Printer -EnableAllPrivileges -Filter "name = '#{new_resource.device_id}'"
- $printer.Delete()
- EOH
+ action :delete, description: "Delete an existing printer. Note that this resource does not delete the associated printer port." do
+ if current_resource
+ converge_by("Delete #{new_resource.device_id}") do
+ powershell_exec!("Remove-Printer -Name '#{new_resource.device_id}'")
end
+ else
+ Chef::Log.info "#{new_resource.device_id} doesn't exist - can't delete."
end
end
end
diff --git a/lib/chef/resource/windows_printer_port.rb b/lib/chef/resource/windows_printer_port.rb
index 2a4eaa09b3..636ae9dd54 100644
--- a/lib/chef/resource/windows_printer_port.rb
+++ b/lib/chef/resource/windows_printer_port.rb
@@ -1,6 +1,7 @@
#
# Author:: Doug Ireton <doug@1strategy.com>
# Copyright:: 2012-2018, Nordstrom, Inc.
+# Copyright:: Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -68,14 +69,17 @@ class Chef
}
property :port_name, String,
- description: "The port name."
+ description: "The port name.",
+ default: lazy { |x| "IP_#{x.ipv4_address}" },
+ default_description: "The resource block name or the ipv4_address prepended with IP_."
property :port_number, Integer,
- description: "The port number.",
+ description: "The TCP port number.",
default: 9100
property :port_description, String,
- description: "The description of the port."
+ desired_state: false,
+ deprecated: true
property :snmp_enabled, [TrueClass, FalseClass],
description: "Determines if SNMP is enabled on the port.",
@@ -86,79 +90,58 @@ class Chef
validation_message: "port_protocol must be either 1 for RAW or 2 for LPR!",
default: 1, equal_to: [1, 2]
- PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY)
+ load_current_value do |new_resource|
+ port_data = powershell_exec(%Q{Get-WmiObject -Class Win32_TCPIPPrinterPort -Filter "Name='#{new_resource.port_name}'"}).result
- # @todo Set @current_resource port properties from registry
- load_current_value do |desired|
- name desired.name
- ipv4_address desired.ipv4_address
- port_name desired.port_name || "IP_#{desired.ipv4_address}"
- end
-
- action :create do
- description "Create the new printer port if it does not already exist."
-
- if port_exists?
- Chef::Log.info "#{@new_resource} already exists - nothing to do."
- else
- converge_by("Create #{@new_resource}") do
- create_printer_port
- end
- end
- end
-
- action :delete do
- description "Delete an existing printer port."
-
- if port_exists?
- converge_by("Delete #{@new_resource}") do
- delete_printer_port
- end
+ if port_data.empty?
+ current_value_does_not_exist!
else
- Chef::Log.info "#{@current_resource} doesn't exist - can't delete."
+ ipv4_address port_data["HostAddress"]
+ port_name port_data["Name"]
+ snmp_enabled port_data["SNMPEnabled"]
+ port_protocol port_data["Protocol"]
+ port_number port_data["PortNumber"]
end
end
- action_class do
- private
-
- def port_exists?
- name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
- port_reg_key = PORTS_REG_KEY + name
-
- logger.trace "Checking to see if this reg key exists: '#{port_reg_key}'"
- registry_key_exists?(port_reg_key)
- end
-
- def create_printer_port
- port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
-
- # create the printer port using PowerShell
- declare_resource(:powershell_script, "Creating printer port #{new_resource.port_name}") do
- code <<-EOH
-
- Set-WmiInstance -class Win32_TCPIPPrinterPort `
- -EnableAllPrivileges `
- -Argument @{ HostAddress = "#{new_resource.ipv4_address}";
- Name = "#{port_name}";
- Description = "#{new_resource.port_description}";
- PortNumber = "#{new_resource.port_number}";
- Protocol = "#{new_resource.port_protocol}";
- SNMPEnabled = "$#{new_resource.snmp_enabled}";
- }
+ action :create, description: "Create or update the printer port." do
+ converge_if_changed do
+ if current_resource
+ # update the printer port using PowerShell
+ powershell_exec! <<-EOH
+ Get-WmiObject Win32_TCPIPPrinterPort -EnableAllPrivileges -filter "Name='#{new_resource.port_name}'" |
+ ForEach-Object{
+ $_.HostAddress='#{new_resource.ipv4_address}'
+ $_.PortNumber='#{new_resource.port_number}'
+ $_.Protocol='#{new_resource.port_protocol}'
+ $_.SNMPEnabled='$#{new_resource.snmp_enabled}'
+ $_.Put()
+ }
+ EOH
+ else
+ # create the printer port using PowerShell
+ powershell_exec! <<-EOH
+ Set-WmiInstance -class Win32_TCPIPPrinterPort `
+ -EnableAllPrivileges `
+ -Argument @{ HostAddress = "#{new_resource.ipv4_address}";
+ Name = "#{new_resource.port_name}";
+ PortNumber = "#{new_resource.port_number}";
+ Protocol = "#{new_resource.port_protocol}";
+ SNMPEnabled = "$#{new_resource.snmp_enabled}";
+ }
EOH
end
- end
- def delete_printer_port
- port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}"
+ end
+ end
- declare_resource(:powershell_script, "Deleting printer port: #{new_resource.port_name}") do
- code <<-EOH
- $port = Get-WMIObject -class Win32_TCPIPPrinterPort -EnableAllPrivileges -Filter "name = '#{port_name}'"
- $port.Delete()
- EOH
+ action :delete, description: "Delete an existing printer port." do
+ if current_resource
+ converge_by("delete port #{new_resource.port_name}") do
+ powershell_exec!("Remove-PrinterPort -Name #{new_resource.port_name}")
end
+ else
+ Chef::Log.info "#{new_resource.port_name} doesn't exist - can't delete."
end
end
end
diff --git a/lib/chef/resource/windows_security_policy.rb b/lib/chef/resource/windows_security_policy.rb
index 1b0a285197..5c683492a3 100644
--- a/lib/chef/resource/windows_security_policy.rb
+++ b/lib/chef/resource/windows_security_policy.rb
@@ -17,6 +17,7 @@
# limitations under the License.
require_relative "../resource"
+require "tempfile" unless defined?(Tempfile)
class Chef
class Resource
@@ -27,6 +28,7 @@ class Chef
# The valid policy_names options found here
# https://github.com/ChrisAWalker/cSecurityOptions under 'AccountSettings'
+ # This needs to be revisited - the list at the link above is non-exhaustive and is missing a couple of items
policy_names = %w{LockoutDuration
MaximumPasswordAge
MinimumPasswordAge
@@ -35,6 +37,8 @@ class Chef
PasswordHistorySize
LockoutBadCount
ResetLockoutCount
+ AuditPolicyChange
+ LockoutDuration
RequireLogonToChangePassword
ForceLogoffWhenHourExpire
NewAdministratorName
@@ -43,7 +47,7 @@ class Chef
LSAAnonymousNameLookup
EnableAdminAccount
EnableGuestAccount
- }
+ }
description "Use the **windows_security_policy** resource to set a security policy on the Microsoft Windows platform."
introduced "16.0"
@@ -82,7 +86,56 @@ class Chef
property :secvalue, String, required: true,
description: "Policy value to be set for policy name."
- load_current_value do |desired|
+ load_current_value do |new_resource|
+ current_state = load_security_options
+
+ if new_resource.secoption == "ResetLockoutCount"
+ if new_resource.secvalue.to_i > current_state["LockoutDuration"].to_i
+ raise Chef::Exceptions::ValidationFailed, "The \"ResetLockoutCount\" value cannot be greater than the value currently set for \"LockoutDuration\""
+ end
+ end
+ if (new_resource.secoption == "ResetLockoutCount" || new_resource.secoption == "LockoutDuration") && current_state["LockoutBadCount"] == "0"
+ raise Chef::Exceptions::ValidationFailed, "#{new_resource.secoption} cannot be set unless the \"LockoutBadCount\" security policy has been set to a non-zero value"
+ end
+
+ secvalue current_state[new_resource.secoption.to_s]
+ end
+
+ action :set, description: "Set the Windows security policy" do
+ converge_if_changed :secvalue do
+ security_option = new_resource.secoption
+ security_value = new_resource.secvalue
+
+ file = Tempfile.new(["#{security_option}", ".inf"])
+ case security_option
+ when "LockoutBadCount"
+ cmd = "net accounts /LockoutThreshold:#{security_value}"
+ when "ResetLockoutCount"
+ cmd = "net accounts /LockoutWindow:#{security_value}"
+ when "LockoutDuration"
+ cmd = "net accounts /LockoutDuration:#{security_value}"
+ when "NewAdministratorName", "NewGuestName"
+ policy_line = "#{security_option} = \"#{security_value}\""
+ file.write("[Unicode]\r\nUnicode=yes\r\n[System Access]\r\n#{policy_line}\r\n[Version]\r\nsignature=\"$CHICAGO$\"\r\nRevision=1\r\n")
+ file.close
+ file_path = file.path.tr("/", "\\")
+ cmd = "C:\\Windows\\System32\\secedit /configure /db C:\\windows\\security\\new.sdb /cfg #{file_path} /areas SECURITYPOLICY"
+ else
+ policy_line = "#{security_option} = #{security_value}"
+ file.write("[Unicode]\r\nUnicode=yes\r\n[System Access]\r\n#{policy_line}\r\n[Version]\r\nsignature=\"$CHICAGO$\"\r\nRevision=1\r\n")
+ file.close
+ file_path = file.path.tr("/", "\\")
+ cmd = "C:\\Windows\\System32\\secedit /configure /db C:\\windows\\security\\new.sdb /cfg #{file_path} /areas SECURITYPOLICY"
+ end
+ shell_out!(cmd)
+ file.unlink
+ end
+ end
+
+ private
+
+ # Loads powershell to get current state on security options
+ def load_security_options
powershell_code = <<-CODE
C:\\Windows\\System32\\secedit /export /cfg $env:TEMP\\secopts_export.inf | Out-Null
# cspell:disable-next-line
@@ -108,44 +161,7 @@ class Chef
LockoutBadCount = $security_options_hash.LockoutBadCount
})
CODE
- output = powershell_exec(powershell_code)
- current_value_does_not_exist! if output.result.empty?
- state = output.result
-
- if desired.secoption == "ResetLockoutCount" || desired.secoption == "LockoutDuration"
- if state["LockoutBadCount"] == "0"
- raise Chef::Exceptions::ValidationFailed.new "#{desired.secoption} cannot be set unless the \"LockoutBadCount\" security policy has been set to a non-zero value"
- else
- secvalue state[desired.secoption.to_s]
- end
- else
- secvalue state[desired.secoption.to_s]
- end
- end
-
- action :set do
- converge_if_changed :secvalue do
- security_option = new_resource.secoption
- security_value = new_resource.secvalue
-
- cmd = <<-EOH
- $security_option = "#{security_option}"
- C:\\Windows\\System32\\secedit /export /cfg $env:TEMP\\#{security_option}_Export.inf
- if ( ($security_option -match "NewGuestName") -Or ($security_option -match "NewAdministratorName") )
- {
- $#{security_option}_Remediation = (Get-Content $env:TEMP\\#{security_option}_Export.inf) | Foreach-Object { $_ -replace '#{security_option}\\s*=\\s*\\"\\w*\\"', '#{security_option} = "#{security_value}"' } | Set-Content $env:TEMP\\#{security_option}_Export.inf
- C:\\Windows\\System32\\secedit /configure /db $env:windir\\security\\new.sdb /cfg $env:TEMP\\#{security_option}_Export.inf /areas SECURITYPOLICY
- }
- else
- {
- $#{security_option}_Remediation = (Get-Content $env:TEMP\\#{security_option}_Export.inf) | Foreach-Object { $_ -replace "#{security_option}\\s*=\\s*\\d*", "#{security_option} = #{security_value}" } | Set-Content $env:TEMP\\#{security_option}_Export.inf
- C:\\Windows\\System32\\secedit /configure /db $env:windir\\security\\new.sdb /cfg $env:TEMP\\#{security_option}_Export.inf /areas SECURITYPOLICY
- }
- Remove-Item $env:TEMP\\#{security_option}_Export.inf -force
- EOH
-
- powershell_exec!(cmd)
- end
+ powershell_exec(powershell_code).result
end
end
end
diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb
index fe1e976747..32b6b9f4a0 100644
--- a/lib/chef/resource/windows_share.rb
+++ b/lib/chef/resource/windows_share.rb
@@ -70,17 +70,17 @@ class Chef
# Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list.
property :full_users, Array,
description: "The users that should have 'Full control' permissions on the share in domain\\username format.",
- default: lazy { [] }, coerce: proc { |u| u.sort }
+ default: [], coerce: proc { |u| u.sort }
# Specifies which users are granted modify permission to access the share
property :change_users, Array,
description: "The users that should have 'modify' permission on the share in domain\\username format.",
- default: lazy { [] }, coerce: proc { |u| u.sort }
+ default: [], coerce: proc { |u| u.sort }
# Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list.
property :read_users, Array,
description: "The users that should have 'read' permission on the share in domain\\username format.",
- default: lazy { [] }, coerce: proc { |u| u.sort }
+ default: [], coerce: proc { |u| u.sort }
# Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary.
property :temporary, [TrueClass, FalseClass],
@@ -118,11 +118,11 @@ class Chef
# Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items.
# property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted)
- load_current_value do |desired|
+ load_current_value do |new_resource|
# this command selects individual objects because EncryptData & CachingMode have underlying
# types that get converted to their Integer values by ConvertTo-Json & we need to make sure
# those get written out as strings
- share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData"
+ share_state_cmd = "Get-SmbShare -Name '#{new_resource.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData"
Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'")
ps_results = powershell_exec(share_state_cmd)
@@ -146,14 +146,14 @@ class Chef
encrypt_data results["EncryptData"]
# folder_enumeration_mode results['FolderEnumerationMode']
- perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight}
+ perm_state_cmd = %{Get-SmbShareAccess -Name "#{new_resource.share_name}" | Select-Object AccountName,AccessControlType,AccessRight}
Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'")
ps_perm_results = powershell_exec(perm_state_cmd)
# we raise here instead of warning like above because we'd only get here if the above Get-SmbShare
# command was successful and that continuing would leave us with 1/2 known state
- raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error?
+ raise "Could not determine #{new_resource.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error?
Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.result}")
@@ -192,9 +192,7 @@ class Chef
name
end
- action :create do
- description "Create and modify Windows shares."
-
+ action :create, description: "Create or modify a Windows share." do
# we do this here instead of requiring the property because :delete doesn't need path set
raise "No path property set" unless new_resource.path
@@ -218,9 +216,7 @@ class Chef
end
end
- action :delete do
- description "Delete an existing Windows share."
-
+ action :delete, description: "Delete an existing Windows share." do
if current_resource.nil?
Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do")
else
@@ -275,14 +271,11 @@ class Chef
# users/groups will have their permissions updated with the same command that
# sets it, but removes must be performed with Revoke-SmbShareAccess
def users_to_revoke
- @users_to_revoke ||= begin
- # if the resource doesn't exist then nothing needs to be revoked
- if current_resource.nil?
- []
- else # if it exists then calculate the current to new resource diffs
- (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users)
- end
- end
+ @users_to_revoke ||= if current_resource.nil?
+ []
+ else # if it exists then calculate the current to new resource diffs
+ (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users)
+ end
end
# update existing permissions on a share
diff --git a/lib/chef/resource/windows_shortcut.rb b/lib/chef/resource/windows_shortcut.rb
index f2264445ba..d432f41bca 100644
--- a/lib/chef/resource/windows_shortcut.rb
+++ b/lib/chef/resource/windows_shortcut.rb
@@ -57,11 +57,11 @@ class Chef
property :iconlocation, String,
description: "Icon to use for the shortcut. Accepts the format of `path, index`, where index is the icon file to use. See Microsoft's [documentation](https://msdn.microsoft.com/en-us/library/3s9bx7at.aspx) for details"
- load_current_value do |desired|
+ load_current_value do |new_resource|
require "win32ole" if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
- link = WIN32OLE.new("WScript.Shell").CreateShortcut(desired.shortcut_name)
- name desired.shortcut_name
+ link = WIN32OLE.new("WScript.Shell").CreateShortcut(new_resource.shortcut_name)
+ name new_resource.shortcut_name
target(link.TargetPath)
arguments(link.Arguments)
description(link.Description)
@@ -69,9 +69,7 @@ class Chef
iconlocation(link.IconLocation)
end
- action :create do
- description "Create or modify a Windows shortcut."
-
+ action :create, description: "Create or modify a Windows shortcut." do
converge_if_changed do
converge_by "creating shortcut #{new_resource.shortcut_name}" do
link = WIN32OLE.new("WScript.Shell").CreateShortcut(new_resource.shortcut_name)
diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb
index 29bade29ce..b1154a1f08 100644
--- a/lib/chef/resource/windows_task.rb
+++ b/lib/chef/resource/windows_task.rb
@@ -21,6 +21,7 @@ require_relative "../resource"
require_relative "../win32/security" if ChefUtils.windows_ruby?
autoload :ISO8601, "iso8601" if ChefUtils.windows_ruby?
require_relative "../util/path_helper"
+require_relative "../util/backup"
require "win32/taskscheduler" if ChefUtils.windows_ruby?
class Chef
@@ -48,7 +49,7 @@ class Chef
**Create a scheduled task to run every 2 days**:
- ``` ruby
+ ```ruby
windows_task 'chef-client' do
command 'chef-client'
run_level :highest
@@ -236,6 +237,10 @@ class Chef
introduced: "14.15", default: false,
description: "To start the task at any time after its scheduled time has passed."
+ property :backup, [Integer, FalseClass],
+ introduced: "17.0", default: 5,
+ description: "Number of backups to keep of the task when modified/deleted. Set to false to disable backups."
+
attr_accessor :exists, :task, :command_arguments
VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * }.freeze
@@ -544,7 +549,7 @@ class Chef
if @current_resource.exists
task.get_task(new_resource.task_name)
@current_resource.task = task
- pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
+ pathed_task_name = new_resource.task_name.start_with?("\\") ? new_resource.task_name : "\\#{new_resource.task_name}"
@current_resource.task_name(pathed_task_name)
end
@current_resource
@@ -564,6 +569,7 @@ class Chef
def update_task(task)
converge_by("#{new_resource} task updated") do
+ do_backup
task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
task.application_name = new_resource.command if new_resource.command
task.parameters = new_resource.command_arguments if new_resource.command_arguments
@@ -948,6 +954,11 @@ class Chef
def get_day(date)
Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
end
+
+ def do_backup
+ file = "C:/Windows/System32/Tasks/#{new_resource.task_name}"
+ Chef::Util::Backup.new(new_resource, file).backup!
+ end
end
action :create do
@@ -998,7 +1009,7 @@ class Chef
end
end
else
- logger.warn "#{new_resource} task does not exist - nothing to do"
+ logger.debug "#{new_resource} task does not exist - nothing to do"
end
end
@@ -1006,11 +1017,12 @@ class Chef
if current_resource.exists
logger.trace "#{new_resource} task exists"
converge_by("delete scheduled task #{new_resource}") do
+ do_backup
ts = ::Win32::TaskScheduler.new
ts.delete(current_resource.task_name)
end
else
- logger.warn "#{new_resource} task does not exist - nothing to do"
+ logger.debug "#{new_resource} task does not exist - nothing to do"
end
end
@@ -1018,14 +1030,14 @@ class Chef
if current_resource.exists
logger.trace "#{new_resource} task exists"
if current_resource.task.status != "running"
- logger.trace "#{new_resource} is not running - nothing to do"
+ logger.debug "#{new_resource} is not running - nothing to do"
else
converge_by("#{new_resource} task ended") do
current_resource.task.stop
end
end
else
- logger.warn "#{new_resource} task does not exist - nothing to do"
+ logger.debug "#{new_resource} task does not exist - nothing to do"
end
end
@@ -1038,7 +1050,7 @@ class Chef
run_schtasks "CHANGE", "ENABLE" => ""
end
else
- logger.trace "#{new_resource} already enabled - nothing to do"
+ logger.debug "#{new_resource} already enabled - nothing to do"
end
else
logger.fatal "#{new_resource} task does not exist - nothing to do"
diff --git a/lib/chef/resource/windows_uac.rb b/lib/chef/resource/windows_uac.rb
index db5d5fd173..330a6432bc 100644
--- a/lib/chef/resource/windows_uac.rb
+++ b/lib/chef/resource/windows_uac.rb
@@ -29,7 +29,7 @@ class Chef
examples <<~DOC
**Disable UAC prompts for the admin**:
- ``` ruby
+ ```ruby
windows_uac 'Disable UAC prompts for the admin' do
enable_uac true
prompt_on_secure_desktop false
@@ -39,7 +39,7 @@ class Chef
**Disable UAC entirely**:
- ``` ruby
+ ```ruby
windows_uac 'Disable UAC entirely' do
enable_uac false
end
@@ -72,9 +72,7 @@ class Chef
equal_to: %i{auto_deny secure_prompt_for_creds prompt_for_creds},
default: :prompt_for_creds
- action :configure do
- description 'Configures UAC by setting registry keys at \'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\''
-
+ action :configure, description: "Configures UAC by setting registry keys at `HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System`." do
registry_key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' do
values [{ name: "EnableLUA", type: :dword, data: bool_to_reg(new_resource.enable_uac) },
{ name: "ValidateAdminCodeSignatures", type: :dword, data: bool_to_reg(new_resource.require_signed_binaries) },
diff --git a/lib/chef/resource/windows_user_privilege.rb b/lib/chef/resource/windows_user_privilege.rb
index 971338303d..36dd730f64 100644
--- a/lib/chef/resource/windows_user_privilege.rb
+++ b/lib/chef/resource/windows_user_privilege.rb
@@ -147,7 +147,7 @@ class Chef
end
end
- action :add do
+ action :add, description: "Add a user privilege." do
([*new_resource.privilege] - [*current_resource.privilege]).each do |user_right|
converge_by("adding user '#{new_resource.principal}' privilege #{user_right}") do
Chef::ReservedNames::Win32::Security.add_account_right(new_resource.principal, user_right)
@@ -155,7 +155,7 @@ class Chef
end
end
- action :set do
+ action :set, description: "Set the privileges that are listed in the `privilege` property for only the users listed in the `users` property." do
if new_resource.users.nil? || new_resource.users.empty?
raise Chef::Exceptions::ValidationFailed, "Users are required property with set action."
end
@@ -190,7 +190,7 @@ class Chef
end
end
- action :clear do
+ action :clear, description: "Clear all user privileges" do
new_resource.privilege.each do |privilege|
accounts = Chef::ReservedNames::Win32::Security.get_account_with_user_rights(privilege)
@@ -204,7 +204,7 @@ class Chef
end
end
- action :remove do
+ action :remove, description: "Remove a user privilege" do
curr_res_privilege = current_resource.privilege
missing_res_privileges = (new_resource.privilege - curr_res_privilege)
diff --git a/lib/chef/resource/windows_workgroup.rb b/lib/chef/resource/windows_workgroup.rb
index 3c49f7cb3e..acb3bee542 100644
--- a/lib/chef/resource/windows_workgroup.rb
+++ b/lib/chef/resource/windows_workgroup.rb
@@ -30,13 +30,13 @@ class Chef
examples <<~DOC
**Join a workgroup**:
- ``` ruby
+ ```ruby
windows_workgroup 'myworkgroup'
```
**Join a workgroup using a specific user**:
- ``` ruby
+ ```ruby
windows_workgroup 'myworkgroup' do
user 'Administrator'
password 'passw0rd'
@@ -87,8 +87,7 @@ class Chef
property :sensitive, [TrueClass, FalseClass],
default: true, desired_state: false
- action :join do
- description "Update the workgroup."
+ action :join, description: "Update the workgroup." do
unless workgroup_member?
converge_by("join workstation workgroup #{new_resource.workgroup_name}") do
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index f7c4517c6d..53e02bf609 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -35,32 +35,32 @@ class Chef
examples <<~DOC
**Install an exact version**:
- ``` ruby
+ ```ruby
yum_package 'netpbm = 10.35.58-8.el8'
```
**Install a minimum version**:
- ``` ruby
+ ```ruby
yum_package 'netpbm >= 10.35.58-8.el8'
```
**Install a minimum version using the default action**:
- ``` ruby
+ ```ruby
yum_package 'netpbm'
```
**Install a version without worrying about the exact release**:
- ``` ruby
+ ```ruby
yum_package 'netpbm-10.35*'
```
**To install a package**:
- ``` ruby
+ ```ruby
yum_package 'netpbm' do
action :install
end
@@ -68,13 +68,13 @@ class Chef
**To install a partial minimum version**:
- ``` ruby
+ ```ruby
yum_package 'netpbm >= 10'
```
**To install a specific architecture**:
- ``` ruby
+ ```ruby
yum_package 'netpbm' do
arch 'i386'
end
@@ -82,13 +82,13 @@ class Chef
or:
- ``` ruby
+ ```ruby
yum_package 'netpbm.x86_64'
```
**To install a specific version-release**
- ``` ruby
+ ```ruby
yum_package 'netpbm' do
version '10.35.58-8.el8'
end
@@ -101,7 +101,7 @@ class Chef
to dump the in-memory Yum cache, and then use the repository immediately
to ensure that the correct package is installed:
- ``` ruby
+ ```ruby
cookbook_file '/etc/yum.repos.d/custom.repo' do
source 'custom'
mode '0755'
diff --git a/lib/chef/resource/yum_repository.rb b/lib/chef/resource/yum_repository.rb
index 94d1a284fc..b5ad2688eb 100644
--- a/lib/chef/resource/yum_repository.rb
+++ b/lib/chef/resource/yum_repository.rb
@@ -50,6 +50,11 @@ class Chef
# http://linux.die.net/man/5/yum.conf as well as
# http://dnf.readthedocs.io/en/latest/conf_ref.html
+ property :reposdir, String,
+ description: "The directory where the Yum repository files should be stored",
+ default: "/etc/yum.repos.d/",
+ introduced: "16.9"
+
property :baseurl, [String, Array],
description: "URL to the directory where the Yum repository's `repodata` directory lives. Can be an `http://`, `https://` or a `ftp://` URLs. You can specify multiple URLs in one `baseurl` statement."
diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb
index 5901090abd..0c4fa47a88 100644
--- a/lib/chef/resource/zypper_package.rb
+++ b/lib/chef/resource/zypper_package.rb
@@ -30,7 +30,7 @@ class Chef
examples <<~DOC
**Install a package using package manager:**
- ``` ruby
+ ```ruby
zypper_package 'name of package' do
action :install
end
@@ -38,7 +38,7 @@ class Chef
**Install a package using local file:**
- ``` ruby
+ ```ruby
zypper_package 'jwhois' do
action :install
source '/path/to/jwhois.rpm'
@@ -47,10 +47,10 @@ class Chef
**Install without using recommend packages as a dependency:**
- ``` ruby
+ ```ruby
package 'apache2' do
options '--no-recommends'
- end
+ end
```
DOC
diff --git a/lib/chef/resource/zypper_repository.rb b/lib/chef/resource/zypper_repository.rb
index 05856cc9bc..228329d2ea 100644
--- a/lib/chef/resource/zypper_repository.rb
+++ b/lib/chef/resource/zypper_repository.rb
@@ -24,21 +24,37 @@ class Chef
unified_mode true
provides(:zypper_repository) { true }
- provides(:zypper_repo) { true }
+ provides(:zypper_repo) { true } # legacy cookbook compatibility
description "Use the **zypper_repository** resource to create Zypper package repositories on SUSE Enterprise Linux and openSUSE systems. This resource maintains full compatibility with the **zypper_repository** resource in the existing **zypper** cookbook."
introduced "13.3"
examples <<~DOC
**Add the Apache repo on openSUSE Leap 15**:
- ``` ruby
+ ```ruby
zypper_repository 'apache' do
baseurl 'http://download.opensuse.org/repositories/Apache'
- path '/openSUSE_Leap_15.0'
- type 'rpm-md'
+ path '/openSUSE_Leap_15.2'
+ type 'rpm-md'
priority '100'
end
```
+
+ **Remove the repo named 'apache'**:
+
+ ```ruby
+ zypper_repository 'apache' do
+ action :delete
+ end
+ ```
+
+ **Refresh the repo named 'apache'**:
+
+ ```ruby
+ zypper_repository 'apache' do
+ action :refresh
+ end
+ ```
DOC
property :repo_name, String,
@@ -66,8 +82,10 @@ class Chef
description: "Determines whether or not to perform a GPG signature check on the repository.",
default: true
- property :gpgkey, String,
- description: "The location of the repository key to be imported."
+ property :gpgkey, [String, Array],
+ description: "The location of the repository key(s) to be imported.",
+ coerce: proc { |v| Array(v) },
+ default: []
property :baseurl, String,
description: "The base URL for the Zypper repository, such as `http://download.opensuse.org`."
@@ -95,10 +113,12 @@ class Chef
default: true
property :source, String,
- description: "The name of the template for the repository file. Only necessary if you're not using the built in template."
+ description: "The name of the template for the repository file. Only necessary if you're using a custom template for the repository file."
property :cookbook, String,
- description: "The cookbook to source the repository template file from. Only necessary if you're not using the built in template.",
+ description: "The cookbook to source the repository template file from. Only necessary if you're using a custom template for the repository file.",
+ default: lazy { cookbook_name },
+ default_description: "The cookbook containing the resource",
desired_state: false
property :gpgautoimportkeys, [TrueClass, FalseClass],
diff --git a/lib/chef/resource_builder.rb b/lib/chef/resource_builder.rb
index 9f2cd657f9..13dc39ad2a 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -29,9 +29,10 @@ class Chef
attr_reader :recipe_name
attr_reader :enclosing_provider
attr_reader :resource
+ attr_reader :new_resource
# FIXME (ruby-2.1 syntax): most of these are mandatory
- def initialize(type: nil, name: nil, created_at: nil, params: nil, run_context: nil, cookbook_name: nil, recipe_name: nil, enclosing_provider: nil)
+ def initialize(type: nil, name: nil, created_at: nil, params: nil, run_context: nil, cookbook_name: nil, recipe_name: nil, enclosing_provider: nil, new_resource: nil)
@type = type
@name = name
@created_at = created_at
@@ -40,6 +41,7 @@ class Chef
@cookbook_name = cookbook_name
@recipe_name = recipe_name
@enclosing_provider = enclosing_provider
+ @new_resource = new_resource
end
def build(&block)
@@ -64,7 +66,11 @@ class Chef
if block_given?
resource.resource_initializing = true
begin
- resource.instance_eval(&block)
+ if new_resource.nil?
+ resource.instance_exec(&block)
+ else
+ resource.instance_exec(new_resource, &block)
+ end
ensure
resource.resource_initializing = false
end
diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb
index db298afb63..26521010bd 100644
--- a/lib/chef/resource_collection/resource_set.rb
+++ b/lib/chef/resource_collection/resource_set.rb
@@ -131,7 +131,7 @@ class Chef
else
raise Chef::Exceptions::InvalidResourceSpecification,
"The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
- "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
+ "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
end
end
diff --git a/lib/chef/resource_inspector.rb b/lib/chef/resource_inspector.rb
index 6d320f4202..95ae110170 100644
--- a/lib/chef/resource_inspector.rb
+++ b/lib/chef/resource_inspector.rb
@@ -41,7 +41,11 @@ class Chef
data[:description] = resource.description
# data[:deprecated] = resource.deprecated || false
data[:default_action] = resource.default_action
- data[:actions] = resource.allowed_actions
+ data[:actions] = {}
+ resource.allowed_actions.each do |action|
+ data[:actions][action] = resource.action_description(action)
+ end
+
data[:examples] = resource.examples
data[:introduced] = resource.introduced
data[:preview] = resource.preview_resource
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 4051ac2f49..c6a7be133f 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -135,7 +135,6 @@ class Chef
def action_collection_registration(action_collection)
@action_collection = action_collection
- action_collection.register(self) if reporting_enabled?
end
def post_reporting_data
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 843d5610b8..3fabc18920 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -65,6 +65,7 @@ require_relative "resource/homebrew_package"
require_relative "resource/homebrew_tap"
require_relative "resource/homebrew_update"
require_relative "resource/ifconfig"
+require_relative "resource/inspec_waiver_file_entry"
require_relative "resource/kernel_module"
require_relative "resource/ksh"
require_relative "resource/launchd"
@@ -125,7 +126,6 @@ require_relative "resource/smartos_package"
require_relative "resource/template"
require_relative "resource/user"
require_relative "resource/user/aix_user"
-require_relative "resource/user/dscl_user"
require_relative "resource/user/linux_user"
require_relative "resource/user/mac_user"
require_relative "resource/user/pw_user"
@@ -148,6 +148,8 @@ require_relative "resource/windows_ad_join"
require_relative "resource/windows_audit_policy"
require_relative "resource/windows_auto_run"
require_relative "resource/windows_certificate"
+require_relative "resource/windows_defender"
+require_relative "resource/windows_defender_exclusion"
require_relative "resource/windows_dfs_folder"
require_relative "resource/windows_dfs_namespace"
require_relative "resource/windows_dfs_server"
@@ -170,4 +172,4 @@ require_relative "resource/windows_uac"
require_relative "resource/windows_workgroup"
require_relative "resource/timezone"
require_relative "resource/windows_user_privilege"
-require_relative "resource/windows_security_policy" \ No newline at end of file
+require_relative "resource/windows_security_policy"
diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb
index 1f83b7ea5a..8382983ca0 100644
--- a/lib/chef/run_lock.rb
+++ b/lib/chef/run_lock.rb
@@ -173,7 +173,7 @@ class Chef
# Mutex name is case-sensitive contrary to other things in
# windows. "\" is the only invalid character.
def acquire_win32_mutex
- @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.tr('\\', "/").downcase}")
+ @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.tr("\\", "/").downcase}")
mutex.test
end
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index 4405843a9b..089ab0627a 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -70,7 +70,7 @@ class Chef
end
end
- # Actually run the action for releases
+ # Run the action on the resource.
resource.run_action(action, notification_type, notifying_resource)
# Execute any immediate and queue up any delayed notifications
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index a425129fa8..89456e86b5 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -25,6 +25,7 @@ require "pp" unless defined?(PP)
require "etc" unless defined?(Etc)
require "mixlib/cli" unless defined?(Mixlib::CLI)
require "chef-utils/dist" unless defined?(ChefUtils::Dist)
+require "chef-config/mixin/dot_d"
require_relative "../chef"
require_relative "version"
@@ -211,6 +212,7 @@ module Shell
class Options
include Mixlib::CLI
+ include ChefConfig::Mixin::DotD
def self.footer(text = nil)
@footer = text if text
@@ -341,15 +343,44 @@ module Shell
# We have to nuke ARGV to make sure irb's option parser never sees it.
# otherwise, IRB complains about command line switches it doesn't recognize.
ARGV.clear
+
+ # This code should not exist.
+ # We should be using Application::Client and then calling load_config_file
+ # which does all this properly. However this will do for now.
config[:config_file] = config_file_for_shell_mode(environment)
config_msg = config[:config_file] || "none (standalone session)"
puts "loading configuration: #{config_msg}"
- Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exist?(config[:config_file]) && File.readable?(config[:config_file])
+
+ # load the config (if we have one)
+ unless config[:config_file].nil?
+ if File.exist?(config[:config_file]) && File.readable?(config[:config_file])
+ Chef::Config.from_file(config[:config_file])
+ end
+
+ # even if we couldn't load that, we need to tell Chef::Config what
+ # the file was so it sets conf dir and d_dir and such properly
+ Chef::Config[:config_file] = config[:config_file]
+
+ # now attempt to load any relevant dot-dirs
+ load_dot_d(Chef::Config[:client_d_dir]) if Chef::Config[:client_d_dir]
+ end
+
+ # finally merge command-line options in
Chef::Config.merge!(config)
end
private
+ # shamelessly lifted from application.rb
+ def apply_config(config_content, config_file_path)
+ Chef::Config.from_string(config_content, config_file_path)
+ rescue Exception => error
+ logger.fatal("Configuration error #{error.class}: #{error.message}")
+ filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
+ filtered_trace.each { |line| logger.fatal(" " + line ) }
+ raise Chef::Exceptions::ConfigurationError.new("Aborting due to error in '#{config_file_path}': #{error}")
+ end
+
def config_file_for_shell_mode(environment)
dot_chef_dir = Chef::Util::PathHelper.home(".chef")
if config[:config_file]
diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb
index b884658e98..e23a4aac42 100644
--- a/lib/chef/shell/ext.rb
+++ b/lib/chef/shell/ext.rb
@@ -198,9 +198,9 @@ module Shell
prints a detailed explanation of the command if available, or the
description if no explanation is available.
E
- def help(commmand = nil)
- if commmand
- explain_command(commmand)
+ def help(command = nil)
+ if command
+ explain_command(command)
else
puts help_banner
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index e578cc2131..4ebcb3a463 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -36,7 +36,6 @@ require_relative "server_api"
# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
-
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
index 945f0197df..3720f5efe7 100644
--- a/lib/chef/user_v1.rb
+++ b/lib/chef/user_v1.rb
@@ -145,11 +145,11 @@ class Chef
payload = {
username: @username,
display_name: @display_name,
- first_name: @first_name,
- last_name: @last_name,
email: @email,
- password: @password,
}
+ payload[:first_name] = @first_name unless @first_name.nil?
+ payload[:last_name] = @last_name unless @last_name.nil?
+ payload[:password] = @password unless @password.nil?
payload[:public_key] = @public_key unless @public_key.nil?
payload[:create_key] = @create_key unless @create_key.nil?
payload[:middle_name] = @middle_name unless @middle_name.nil?
@@ -258,7 +258,6 @@ class Chef
end
# Class Methods
-
def self.from_hash(user_hash)
user = Chef::UserV1.new
user.username user_hash["username"]
diff --git a/lib/chef/util/dsc/configuration_generator.rb b/lib/chef/util/dsc/configuration_generator.rb
index 7e78e1ecab..8ca6249a9d 100644
--- a/lib/chef/util/dsc/configuration_generator.rb
+++ b/lib/chef/util/dsc/configuration_generator.rb
@@ -88,6 +88,7 @@ class Chef::Util::DSC
when FalseClass
switch_present = false
when TrueClass
+ # nothing
when String
switch_argument = escape_string_parameter_value(switch_value)
else
@@ -105,7 +106,7 @@ class Chef::Util::DSC
# The name may not be null or empty, and should start with a letter.
def validate_configuration_name!(configuration_name)
if !!(configuration_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false
- raise ArgumentError, 'Configuration `#{configuration_name}` is not a valid PowerShell cmdlet name'
+ raise ArgumentError, "Configuration `#{configuration_name}` is not a valid PowerShell cmdlet name"
end
end
diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb
index 091d4aa426..c0f9c72da8 100644
--- a/lib/chef/util/dsc/local_configuration_manager.rb
+++ b/lib/chef/util/dsc/local_configuration_manager.rb
@@ -103,7 +103,7 @@ class Chef::Util::DSC
end
def whatif_not_supported?(dsc_exception_output)
- !! (dsc_exception_output.gsub(/[\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i)
+ !! (dsc_exception_output.gsub(/\n+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i)
end
def dsc_module_import_failure?(command_output)
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 2ed9051e3d..045900bec4 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -23,7 +23,7 @@ require_relative "version_string"
class Chef
CHEF_ROOT = File.expand_path("..", __dir__)
- VERSION = Chef::VersionString.new("16.7.68")
+ VERSION = Chef::VersionString.new("17.3.0")
end
#
diff --git a/lib/chef/version_string.rb b/lib/chef/version_string.rb
index 8da5df570a..c98d4c9a75 100644
--- a/lib/chef/version_string.rb
+++ b/lib/chef/version_string.rb
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require "chef-utils/version_string"
+require "chef-utils/version_string" unless defined?(ChefUtils::VersionString)
class Chef
VersionString = ChefUtils::VersionString
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index 957823220d..893c819da3 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -43,6 +43,8 @@ class Chef
host.ffi_convention :stdcall
+ win64 = ENV["PROCESSOR_ARCHITECTURE"] == "AMD64"
+
# Windows-specific type defs (ms-help://MS.MSDNQTR.v90.en/winprog/winprog/windows_data_types.htm):
host.typedef :ushort, :ATOM # Atom ~= Symbol: Atom table stores strings and corresponding identifiers. Application
# places a string in an atom table and receives a 16-bit integer, called an atom, that
@@ -120,10 +122,15 @@ class Chef
host.typedef :int32, :LONG32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int64, :LONG64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
host.typedef :int64, :LONGLONG # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
- host.typedef :long, :LONG_PTR # Signed long type for pointer precision. Use when casting a pointer to a long to
# perform pointer arithmetic. BaseTsd.h:
# if defined(_WIN64) host.typedef __int64 LONG_PTR; #else host.typedef long LONG_PTR;
- host.typedef :long, :LPARAM # Message parameter. WinDef.h as follows: #host.typedef LONG_PTR LPARAM;
+ if win64
+ host.typedef :int64, :LONG_PTR # Signed long type for pointer precision. Use when casting a pointer to a long to
+ host.typedef :int64, :LPARAM # Message parameter. WinDef.h as follows: #host.typedef LONG_PTR LPARAM;
+ else
+ host.typedef :long, :LONG_PTR # Signed long type for pointer precision. Use when casting a pointer to a long to
+ host.typedef :long, :LPARAM # Message parameter. WinDef.h as follows: #host.typedef LONG_PTR LPARAM;
+ end
host.typedef :pointer, :LPBOOL # Pointer to a BOOL. WinDef.h as follows: #host.typedef BOOL far *LPBOOL;
host.typedef :pointer, :LPBYTE # Pointer to a BYTE. WinDef.h as follows: #host.typedef BYTE far *LPBYTE;
host.typedef :pointer, :LPCOLORREF # Pointer to a COLORREF value. WinDef.h as follows: #host.typedef DWORD *LPCOLORREF;
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 4b5f8ede41..4f7f2b2d52 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -21,9 +21,11 @@ require_relative "api"
require_relative "../mixin/wide_string"
if RUBY_PLATFORM.match?(/mswin|mingw32|windows/)
- Win32.autoload :Registry, File.expand_path("../monkey_patches/win32/registry", __dir__)
- require_relative "api/registry"
require "win32/api"
+ module Win32
+ autoload :Registry, File.expand_path("../monkey_patches/win32/registry", __dir__)
+ end
+ require_relative "api/registry"
end
class Chef
diff --git a/omnibus/Gemfile b/omnibus/Gemfile
index 6fca425e62..052ac6bfd9 100644
--- a/omnibus/Gemfile
+++ b/omnibus/Gemfile
@@ -1,7 +1,7 @@
source "https://rubygems.org"
-gem "omnibus", git: "https://github.com/chef/omnibus", branch: "master"
-gem "omnibus-software", git: "https://github.com/chef/omnibus-software", branch: "master"
+gem "omnibus", github: ENV.fetch("OMNIBUS_GITHUB_REPO", "chef/omnibus"), branch: ENV.fetch("OMNIBUS_GITHUB_BRANCH", "master")
+gem "omnibus-software", github: ENV.fetch("OMNIBUS_SOFTWARE_GITHUB_REPO", "chef/omnibus-software"), branch: ENV.fetch("OMNIBUS_SOFTWARE_GITHUB_BRANCH", "master")
gem "artifactory"
gem "pedump"
@@ -22,9 +22,3 @@ group :development do
gem "kitchen-vagrant", ">= 1.3.1"
gem "winrm-fs", "~> 1.0"
end
-
-instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"]
-
-# If you want to load debugging tools into the bundle exec sandbox,
-# add these additional dependencies into Gemfile.local
-eval_gemfile(__FILE__ + ".local") if File.exist?(__FILE__ + ".local")
diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock
index 82adf554b5..360a1fdce5 100644
--- a/omnibus/Gemfile.lock
+++ b/omnibus/Gemfile.lock
@@ -1,9 +1,17 @@
GIT
- remote: https://github.com/chef/omnibus
- revision: e8951da8c2b52524116a87af3cf632d9c799a435
+ remote: https://github.com/chef/omnibus-software.git
+ revision: e86bf087a5c80a16eef90fae62dca9623b37ea2f
branch: master
specs:
- omnibus (8.0.9)
+ omnibus-software (4.0.0)
+ omnibus (>= 8.0.0)
+
+GIT
+ remote: https://github.com/chef/omnibus.git
+ revision: 87db446ee0b9872a8a588bedc660c351991e8bda
+ branch: master
+ specs:
+ omnibus (8.1.10)
aws-sdk-s3 (~> 1)
chef-cleanroom (~> 1.0)
chef-utils (>= 15.4)
@@ -11,52 +19,44 @@ GIT
license_scout (~> 1.0)
mixlib-shellout (>= 2.0, < 4.0)
mixlib-versioning
- ohai (>= 15)
+ ohai (>= 15, < 17)
pedump
ruby-progressbar (~> 1.7)
thor (>= 0.18, < 2.0)
-GIT
- remote: https://github.com/chef/omnibus-software
- revision: 01f76620796c00bea448f0355bb177647b3a64a2
- branch: master
- specs:
- omnibus-software (4.0.0)
- omnibus (>= 8.0.0)
-
GEM
remote: https://rubygems.org/
specs:
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
- awesome_print (1.8.0)
- aws-eventstream (1.1.0)
- aws-partitions (1.401.0)
- aws-sdk-core (3.109.3)
+ awesome_print (1.9.2)
+ aws-eventstream (1.1.1)
+ aws-partitions (1.465.0)
+ aws-sdk-core (3.114.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.39.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-kms (1.43.0)
+ aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.86.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-s3 (1.96.0)
+ aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.2)
+ aws-sigv4 (1.2.3)
aws-eventstream (~> 1, >= 1.0.2)
- bcrypt_pbkdf (1.1.0.rc1)
- bcrypt_pbkdf (1.1.0.rc1-x64-mingw32)
- bcrypt_pbkdf (1.1.0.rc1-x86-mingw32)
- berkshelf (7.1.0)
+ bcrypt_pbkdf (1.1.0)
+ bcrypt_pbkdf (1.1.0-x64-mingw32)
+ bcrypt_pbkdf (1.1.0-x86-mingw32)
+ berkshelf (7.2.0)
chef (>= 15.7.32)
chef-config
cleanroom (~> 1.0)
concurrent-ruby (~> 1.0)
minitar (>= 0.6)
- mixlib-archive (>= 0.4, < 2.0)
+ mixlib-archive (>= 1.1.4, < 2.0)
mixlib-config (>= 2.2.5)
mixlib-shellout (>= 2.0, < 4.0)
octokit (~> 4.0)
@@ -64,12 +64,12 @@ GEM
solve (~> 4.0)
thor (>= 0.20)
builder (3.2.4)
- chef (16.7.61)
+ chef (16.13.16)
addressable
- bcrypt_pbkdf (= 1.1.0.rc1)
+ bcrypt_pbkdf (~> 1.1)
bundler (>= 1.10)
- chef-config (= 16.7.61)
- chef-utils (= 16.7.61)
+ chef-config (= 16.13.16)
+ chef-utils (= 16.13.16)
chef-vault
chef-zero (>= 14.0.11)
diff-lcs (>= 1.2.4, < 1.4.0)
@@ -80,6 +80,7 @@ GEM
ffi-yajl (~> 2.2)
highline (>= 1.6.9, < 3)
iniparse (~> 1.4)
+ inspec-core (~> 4.23)
license-acceptance (>= 1.0.5, < 3)
mixlib-archive (>= 0.4, < 2.0)
mixlib-authentication (>= 2.1, < 4)
@@ -87,7 +88,7 @@ GEM
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
- net-ssh (>= 4.2, < 7)
+ net-ssh (>= 5.1, < 7)
net-ssh-multi (~> 1.2, >= 1.2.1)
ohai (~> 16.0)
pastel
@@ -99,13 +100,13 @@ GEM
tty-prompt (~> 0.21)
tty-screen (~> 0.6)
tty-table (~> 0.11)
- uuidtools (~> 2.1.5)
- chef (16.7.61-universal-mingw32)
+ uuidtools (>= 2.1.5, < 3.0)
+ chef (16.13.16-universal-mingw32)
addressable
- bcrypt_pbkdf (= 1.1.0.rc1)
+ bcrypt_pbkdf (~> 1.1)
bundler (>= 1.10)
- chef-config (= 16.7.61)
- chef-utils (= 16.7.61)
+ chef-config (= 16.13.16)
+ chef-utils (= 16.13.16)
chef-vault
chef-zero (>= 14.0.11)
diff-lcs (>= 1.2.4, < 1.4.0)
@@ -116,6 +117,7 @@ GEM
ffi-yajl (~> 2.2)
highline (>= 1.6.9, < 3)
iniparse (~> 1.4)
+ inspec-core (~> 4.23)
iso8601 (>= 0.12.1, < 0.14)
license-acceptance (>= 1.0.5, < 3)
mixlib-archive (>= 0.4, < 2.0)
@@ -124,7 +126,7 @@ GEM
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
- net-ssh (>= 4.2, < 7)
+ net-ssh (>= 5.1, < 7)
net-ssh-multi (~> 1.2, >= 1.2.1)
ohai (~> 16.0)
pastel
@@ -136,9 +138,9 @@ GEM
tty-prompt (~> 0.21)
tty-screen (~> 0.6)
tty-table (~> 0.11)
- uuidtools (~> 2.1.5)
+ uuidtools (>= 2.1.5, < 3.0)
win32-api (~> 1.5.3)
- win32-certstore (~> 0.3)
+ win32-certstore (~> 0.5.0)
win32-event (~> 0.6.1)
win32-eventlog (= 0.6.3)
win32-mmap (~> 0.4.1)
@@ -148,40 +150,57 @@ GEM
win32-taskscheduler (~> 2.0)
wmi-lite (~> 1.0)
chef-cleanroom (1.0.2)
- chef-config (16.7.61)
+ chef-config (16.13.16)
addressable
- chef-utils (= 16.7.61)
+ chef-utils (= 16.13.16)
fuzzyurl
mixlib-config (>= 2.2.12, < 4.0)
mixlib-shellout (>= 2.0, < 4.0)
tomlrb (~> 1.2)
- chef-utils (16.7.61)
+ chef-telemetry (1.0.29)
+ chef-config
+ concurrent-ruby (~> 1.0)
+ chef-utils (16.13.16)
chef-vault (4.1.0)
- chef-zero (15.0.3)
+ chef-zero (15.0.4)
ffi-yajl (~> 2.2)
hashie (>= 2.0, < 5.0)
mixlib-log (>= 2.0, < 4.0)
rack (~> 2.0, >= 2.0.6)
uuidtools (~> 2.1)
+ webrick
citrus (3.0.2)
cleanroom (1.0.0)
- concurrent-ruby (1.1.7)
+ coderay (1.1.3)
+ concurrent-ruby (1.1.9)
diff-lcs (1.3)
ed25519 (1.2.4)
erubi (1.10.0)
erubis (2.7.0)
- faraday (1.1.0)
+ faraday (1.4.2)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
multipart-post (>= 1.2, < 3)
- ruby2_keywords
- ffi (1.13.1)
- ffi (1.13.1-x64-mingw32)
- ffi (1.13.1-x86-mingw32)
- ffi-libarchive (1.0.4)
+ ruby2_keywords (>= 0.0.4)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.1.0)
+ faraday_middleware (1.0.0)
+ faraday (~> 1.0)
+ ffi (1.15.1)
+ ffi (1.15.1-x64-mingw32)
+ ffi (1.15.1-x86-mingw32)
+ ffi-libarchive (1.0.17)
ffi (~> 1.0)
ffi-win32-extensions (1.0.4)
ffi
- ffi-yajl (2.3.4)
- libyajl2 (~> 1.2)
+ ffi-yajl (2.4.0)
+ libyajl2 (>= 1.2)
fuzzyurl (0.9.0)
gssapi (1.3.1)
ffi (>= 1.0.1)
@@ -191,20 +210,43 @@ GEM
highline (2.0.3)
httpclient (2.8.3)
iniparse (1.5.0)
+ inspec-core (4.37.23)
+ addressable (~> 2.4)
+ chef-telemetry (~> 1.0, >= 1.0.8)
+ faraday (>= 0.9.0, < 1.5)
+ faraday_middleware (~> 1.0)
+ hashie (>= 3.4, < 5.0)
+ license-acceptance (>= 0.2.13, < 3.0)
+ method_source (>= 0.8, < 2.0)
+ mixlib-log (~> 3.0)
+ multipart-post (~> 2.0)
+ parallel (~> 1.9)
+ parslet (>= 1.5, < 2.0)
+ pry (~> 0.13)
+ rspec (>= 3.9, < 3.11)
+ rspec-its (~> 1.2)
+ rubyzip (>= 1.2.2, < 3.0)
+ semverse (~> 3.0)
+ sslshake (~> 1.2)
+ thor (>= 0.20, < 2.0)
+ tomlrb (>= 1.2, < 2.1)
+ train-core (~> 3.0)
+ tty-prompt (~> 0.17)
+ tty-table (~> 0.10)
iostruct (0.0.4)
ipaddress (0.8.3)
iso8601 (0.13.0)
jmespath (1.4.0)
- json (2.3.1)
- kitchen-vagrant (1.7.2)
+ json (2.5.1)
+ kitchen-vagrant (1.8.0)
test-kitchen (>= 1.4, < 3)
- libyajl2 (1.2.0)
- license-acceptance (2.1.2)
+ libyajl2 (2.1.0)
+ license-acceptance (2.1.13)
pastel (~> 0.7)
- tomlrb (~> 1.2)
+ tomlrb (>= 1.2, < 3.0)
tty-box (~> 0.6)
tty-prompt (~> 0.20)
- license_scout (1.2.3)
+ license_scout (1.2.10)
ffi-yajl (~> 2.2)
mixlib-shellout (>= 2.2, < 4.0)
toml-rb (>= 1, < 3)
@@ -212,23 +254,24 @@ GEM
logging (2.3.0)
little-plugger (~> 1.1)
multi_json (~> 1.14)
+ method_source (1.0.0)
minitar (0.9)
- mixlib-archive (1.0.7)
+ mixlib-archive (1.1.7)
mixlib-log
- mixlib-archive (1.0.7-universal-mingw32)
+ mixlib-archive (1.1.7-universal-mingw32)
mixlib-log
- mixlib-authentication (3.0.7)
+ mixlib-authentication (3.0.10)
mixlib-cli (2.1.8)
mixlib-config (3.0.9)
tomlrb
- mixlib-install (3.12.5)
+ mixlib-install (3.12.11)
mixlib-shellout
mixlib-versioning
thor
mixlib-log (3.0.9)
- mixlib-shellout (3.2.2)
+ mixlib-shellout (3.2.5)
chef-utils
- mixlib-shellout (3.2.2-universal-mingw32)
+ mixlib-shellout (3.2.5-universal-mingw32)
chef-utils
ffi-win32-extensions (~> 1.0.3)
win32-process (~> 0.9)
@@ -248,10 +291,10 @@ GEM
net-ssh (>= 2.6.5)
net-ssh-gateway (>= 1.2.0)
nori (2.6.0)
- octokit (4.19.0)
+ octokit (4.21.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
- ohai (16.7.37)
+ ohai (16.13.0)
chef-config (>= 12.8, < 17)
chef-utils (>= 16.0, < 17)
ffi (~> 1.9)
@@ -264,23 +307,44 @@ GEM
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
+ parallel (1.20.1)
+ parslet (1.8.2)
pastel (0.8.0)
tty-color (~> 0.5)
- pedump (0.6.1)
+ pedump (0.6.2)
awesome_print
iostruct (>= 0.0.4)
multipart-post (>= 2.0.0)
rainbow
zhexdump (>= 0.0.2)
- plist (3.5.0)
+ plist (3.6.0)
proxifier (1.0.3)
+ pry (0.14.1)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
public_suffix (4.0.6)
rack (2.2.3)
rainbow (3.0.0)
retryable (3.0.5)
- ruby-progressbar (1.10.1)
- ruby2_keywords (0.0.2)
- rubyntlm (0.6.2)
+ rspec (3.10.0)
+ rspec-core (~> 3.10.0)
+ rspec-expectations (~> 3.10.0)
+ rspec-mocks (~> 3.10.0)
+ rspec-core (3.10.1)
+ rspec-support (~> 3.10.0)
+ rspec-expectations (3.10.1)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-its (1.3.0)
+ rspec-core (>= 3.0.0)
+ rspec-expectations (>= 3.0.0)
+ rspec-mocks (3.10.2)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-support (3.10.2)
+ ruby-progressbar (1.11.0)
+ ruby2_keywords (0.0.4)
+ rubyntlm (0.6.3)
rubyzip (2.3.0)
sawyer (0.8.2)
addressable (>= 2.3.5)
@@ -289,15 +353,17 @@ GEM
solve (4.0.4)
molinillo (~> 0.6)
semverse (>= 1.1, < 4.0)
- strings (0.2.0)
+ sslshake (1.3.1)
+ strings (0.2.1)
strings-ansi (~> 0.2)
- unicode-display_width (~> 1.5)
+ unicode-display_width (>= 1.5, < 3.0)
unicode_utils (~> 1.4)
strings-ansi (0.2.0)
structured_warnings (0.4.0)
syslog-logger (1.6.8)
- test-kitchen (2.7.2)
+ test-kitchen (2.11.2)
bcrypt_pbkdf (~> 1.0)
+ chef-utils (>= 16.4.35)
ed25519 (~> 1.2)
license-acceptance (>= 1.0.11, < 3.0)
mixlib-install (~> 3.6)
@@ -309,31 +375,31 @@ GEM
winrm (~> 2.0)
winrm-elevated (~> 1.0)
winrm-fs (~> 1.1)
- thor (1.0.1)
+ thor (1.1.0)
toml-rb (2.0.1)
citrus (~> 3.0, > 3.0)
tomlrb (1.3.0)
- train-core (3.3.27)
+ train-core (3.7.2)
addressable (~> 2.5)
ffi (!= 1.13.0)
json (>= 1.8, < 3.0)
mixlib-shellout (>= 2.0, < 4.0)
net-scp (>= 1.2, < 4.0)
net-ssh (>= 2.9, < 7.0)
- train-winrm (0.2.11)
- winrm (~> 2.0)
+ train-winrm (0.2.12)
+ winrm (>= 2.3.6, < 3.0)
winrm-elevated (~> 1.2.2)
winrm-fs (~> 1.0)
- tty-box (0.6.0)
+ tty-box (0.7.0)
pastel (~> 0.8)
strings (~> 0.2.0)
tty-cursor (~> 0.7)
tty-color (0.6.0)
tty-cursor (0.7.1)
- tty-prompt (0.22.0)
+ tty-prompt (0.23.1)
pastel (~> 0.8)
tty-reader (~> 0.8)
- tty-reader (0.8.0)
+ tty-reader (0.9.0)
tty-cursor (~> 0.7)
tty-screen (~> 0.8)
wisper (~> 2.0)
@@ -342,11 +408,12 @@ GEM
pastel (~> 0.8)
strings (~> 0.2.0)
tty-screen (~> 0.8)
- unicode-display_width (1.7.0)
+ unicode-display_width (2.0.0)
unicode_utils (1.4.0)
- uuidtools (2.1.5)
+ uuidtools (2.2.0)
+ webrick (1.7.0)
win32-api (1.5.3-universal-mingw32)
- win32-certstore (0.4.1)
+ win32-certstore (0.5.3)
ffi
mixlib-shellout
win32-event (0.6.3)
@@ -367,7 +434,7 @@ GEM
win32-taskscheduler (2.0.4)
ffi
structured_warnings
- winrm (2.3.5)
+ winrm (2.3.6)
builder (>= 2.1.2)
erubi (~> 1.8)
gssapi (~> 1.2)
@@ -375,7 +442,7 @@ GEM
httpclient (~> 2.2, >= 2.2.0.2)
logging (>= 1.6.1, < 3.0)
nori (~> 2.0)
- rubyntlm (~> 0.6.0, >= 0.6.1)
+ rubyntlm (~> 0.6.0, >= 0.6.3)
winrm-elevated (1.2.3)
erubi (~> 1.8)
winrm (~> 2.0)
diff --git a/omnibus/config/software/more-ruby-cleanup.rb b/omnibus/config/software/more-ruby-cleanup.rb
index d7c3b6000f..b875c8a166 100644
--- a/omnibus/config/software/more-ruby-cleanup.rb
+++ b/omnibus/config/software/more-ruby-cleanup.rb
@@ -43,7 +43,7 @@ build do
block "Removing additional non-code files from installed gems" do
# find the embedded ruby gems dir and clean it up for globbing
- target_dir = "#{install_dir}/embedded/lib/ruby/gems/*/gems".tr('\\', "/")
+ target_dir = "#{install_dir}/embedded/lib/ruby/gems/*/gems".tr("\\", "/")
files = %w{
.rspec-tm
.sitearchdir.time
@@ -72,6 +72,8 @@ build do
Dir.glob("#{target_dir}/*/{#{files.join(",")}}").each do |f|
# chef stores the powershell dlls in the ext dir
next if File.basename(File.expand_path("..", f)).start_with?("chef-")
+ # ruby-prof has issues/bugs with needing the so in the ext dir
+ next if File.basename(File.expand_path("..", f)).start_with?("ruby-prof-")
puts "Deleting #{f}"
FileUtils.rm_rf(f)
@@ -80,9 +82,8 @@ build do
block "Removing Gemspec / Rakefile / Gemfile unless there's a bin dir / not a chef gem" do
# find the embedded ruby gems dir and clean it up for globbing
- target_dir = "#{install_dir}/embedded/lib/ruby/gems/*/gems".tr('\\', "/")
+ target_dir = "#{install_dir}/embedded/lib/ruby/gems/*/gems".tr("\\", "/")
files = %w{
- *.gemspec
Gemfile
Rakefile
tasks
@@ -101,7 +102,7 @@ build do
end
block "Removing spec dirs from non-Chef gems" do
- Dir.glob("#{install_dir}/embedded/lib/ruby/gems/*/gems/*/spec".tr('\\', "/")).each do |f|
+ Dir.glob("#{install_dir}/embedded/lib/ruby/gems/*/gems/*/spec".tr("\\", "/")).each do |f|
# if we're in a chef- gem then don't remove the specs
next if File.basename(File.expand_path("..", f)).start_with?("chef-")
diff --git a/omnibus/kitchen.yml b/omnibus/kitchen.yml
index bd2893ab83..a3db5b8499 100644
--- a/omnibus/kitchen.yml
+++ b/omnibus/kitchen.yml
@@ -38,7 +38,7 @@ platforms:
run_list: apt::default
- name: freebsd-11
run_list: freebsd::portsnap
- - name: ubuntu-16.04
+ - name: ubuntu-18.04
run_list: apt::default
# macOS
diff --git a/omnibus/omnibus-test.ps1 b/omnibus/omnibus-test.ps1
index aa23a6442a..7e3488f060 100644
--- a/omnibus/omnibus-test.ps1
+++ b/omnibus/omnibus-test.ps1
@@ -11,26 +11,9 @@ Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/v7
Start-Process msiexec.exe -Wait -ArgumentList "/package PowerShell.msi /quiet"
$env:path += ";C:\Program Files\PowerShell\7"
-$channel = "$Env:CHANNEL"
-If ([string]::IsNullOrEmpty($channel)) { $channel = "unstable" }
-
-$product = "$Env:PRODUCT"
-If ([string]::IsNullOrEmpty($product)) { $product = "chef" }
-
-$version = "$Env:VERSION"
-If ([string]::IsNullOrEmpty($version)) { $version = "latest" }
-
-Write-Output "--- Installing $channel $product $version"
-$package_file = $(C:\opscode\omnibus-toolchain\bin\install-omnibus-product.ps1 -Product "$product" -Channel "$channel" -Version "$version" | Select-Object -Last 1)
-
-Write-Output "--- Verifying omnibus package is signed"
-C:\opscode\omnibus-toolchain\bin\check-omnibus-package-signed.ps1 "$package_file"
-
-Write-Output "--- Running verification for $channel $product $version"
-
# We don't want to add the embedded bin dir to the main PATH as this
# could mask issues in our binstub shebangs.
-$embedded_bin_dir = "C:\opscode\$product\embedded\bin"
+$embedded_bin_dir = "C:\opscode\chef\embedded\bin"
# Set TEMP and TMP environment variables to a short path because buildkite-agent user's default path is so long it causes tests to fail
$Env:TEMP = "C:\cheftest"
@@ -54,13 +37,12 @@ Remove-Item Env:BUNDLER_VERSION -ErrorAction SilentlyContinue
ForEach ($b in
"chef-client",
- "knife",
"chef-solo",
"ohai"
) {
Write-Output "Checking for existence of binfile $b..."
- If (Test-Path -PathType Leaf -Path "C:\opscode\$product\bin\$b") {
+ If (Test-Path -PathType Leaf -Path "C:\opscode\chef\bin\$b") {
Write-Output "Found $b!"
}
Else {
@@ -69,7 +51,7 @@ ForEach ($b in
}
}
-$Env:PATH = "C:\opscode\$product\bin;$Env:PATH"
+$Env:PATH = "C:\opscode\chef\bin;$Env:PATH"
chef-client --version
@@ -79,7 +61,7 @@ chef-client --version
& $embedded_bin_dir\bundle.bat --version
& $embedded_bin_dir\rspec.bat --version
-$Env:PATH = "C:\opscode\$product\bin;C:\opscode\$product\embedded\bin;$Env:PATH"
+$Env:PATH = "C:\opscode\chef\bin;C:\opscode\chef\embedded\bin;$Env:PATH"
# Test against the vendored chef gem (cd into the output of "gem which chef")
$chefdir = gem which chef
diff --git a/omnibus/omnibus-test.sh b/omnibus/omnibus-test.sh
index 2c1313681c..7e01cbe60b 100644
--- a/omnibus/omnibus-test.sh
+++ b/omnibus/omnibus-test.sh
@@ -1,33 +1,6 @@
#!/bin/bash
set -ueo pipefail
-channel="${CHANNEL:-unstable}"
-product="${PRODUCT:-chef}"
-version="${VERSION:-latest}"
-
-export INSTALL_DIR="/opt/$product"
-
-echo "--- Installing $channel $product $version"
-package_file="$("/opt/$TOOLCHAIN/bin/install-omnibus-product" -c "$channel" -P "$product" -v "$version" | tail -1)"
-
-echo "--- Verifying omnibus package is signed"
-"/opt/$TOOLCHAIN/bin/check-omnibus-package-signed" "$package_file"
-
-sudo rm -f "$package_file"
-
-echo "--- Verifying ownership of package files"
-
-NONROOT_FILES="$(find "$INSTALL_DIR" ! -user 0 -print)"
-if [[ "$NONROOT_FILES" == "" ]]; then
- echo "Packages files are owned by root. Continuing verification."
-else
- echo "Exiting with an error because the following files are not owned by root:"
- echo "$NONROOT_FILES"
- exit 1
-fi
-
-echo "--- Running verification for $channel $product $version"
-
# Our tests hammer YUM pretty hard and the EL6 testers get corrupted
# after some period of time. Rebuilding the RPM database clears
# up the underlying corruption. We'll do this each test run just to
@@ -50,13 +23,12 @@ mkdir -p "$TMPDIR"
# Verify that we kill any orphaned test processes. Kill any orphaned rspec processes.
sudo kill -9 $(ps ax | grep 'rspec' | grep -v grep | awk '{ print $1 }') || true
-export PATH="/opt/$product/bin:$PATH"
-
-export BIN_DIR="/opt/$product/bin"
+export PATH="/opt/chef/bin:$PATH"
+export BIN_DIR="/opt/chef/bin"
# We don't want to add the embedded bin dir to the main PATH as this
# could mask issues in our binstub shebangs.
-export EMBEDDED_BIN_DIR="/opt/$product/embedded/bin"
+export EMBEDDED_BIN_DIR="/opt/chef/embedded/bin"
# If we are on Mac our symlinks are located under /usr/local/bin
# otherwise they are under /usr/bin
@@ -128,10 +100,10 @@ export FORCE_FFI_YAJL=ext
# most platforms provide "infocmp" by default via an "ncurses" package but SLES 12 provide it via "ncurses-devel" which
# isn't typically installed. omnibus-toolchain has "infocmp" built-in so we add omnibus-toolchain to the PATH to ensure
# tests will function properly.
-export PATH="/opt/$TOOLCHAIN/bin:/usr/local/bin:/opt/$TOOLCHAIN/embedded/bin:$PATH"
+export PATH="/opt/${TOOLCHAIN:-omnibus-toolchain}/bin:/usr/local/bin:/opt/${TOOLCHAIN:-omnibus-toolchain}/embedded/bin:$PATH"
# add chef's bin paths to PATH to ensure tests function properly
-export PATH="/opt/$product/bin:/opt/$product/embedded/bin:$PATH"
+export PATH="/opt/chef/bin:/opt/chef/embedded/bin:$PATH"
gem_list="$(gem which chef)"
lib_dir="$(dirname "$gem_list")"
diff --git a/omnibus/package-scripts/chef/postinst b/omnibus/package-scripts/chef/postinst
index 1500feac0c..61d1d0abe1 100755
--- a/omnibus/package-scripts/chef/postinst
+++ b/omnibus/package-scripts/chef/postinst
@@ -3,11 +3,10 @@
#
# - must run on /bin/sh on solaris 9
# - must run on /bin/sh on AIX 6.x
-# - if you think you are a bash wizard, you probably do not understand
-# this programming language. do not touch.
+# - this file is sh not bash so do not introduce bash-isms
# - if you are under 40, get peer review from your elders.
#
-# Install a full Opscode Client
+# Install Chef Infra Client
#
PROGNAME=`basename $0`
@@ -64,7 +63,7 @@ EOP
if [ "" != "$organization" ]; then
echo "validation_client_name '${organization}-validator'" >> ${CONFIG_DIR}/client.rb
fi
- chmod 644 ${CONFIG_DIR}/client.rb
+ chmod 640 ${CONFIG_DIR}/client.rb
fi
if [ "" != "$validation_key" ]; then
@@ -94,10 +93,25 @@ ln -sf $INSTALLER_DIR/bin/ohai $PREFIX/bin || error_exit "Cannot link ohai to $P
# must appear as the last real action in the script
ln -sf $INSTALLER_DIR/bin/chef-client $PREFIX/bin || error_exit "Cannot link chef-client to $PREFIX/bin"
-# Ensure all files/directories in $INSTALLER_DIR are owned by root. This
-# has been fixed on new installs but upgrades from old installs need to
-# be manually fixed.
-chown -Rh 0:0 $INSTALLER_DIR
+# make the base structure for chef to run
+# the sample client.rb is only written out of no chef config dir exists yet
+if ! [ -d $CONFIG_DIR ]; then
+ mkdir -p $CONFIG_DIR
+ cat >"$CONFIG_DIR/client.rb" <<EOF
+# The client.rb file specifies how Chef Infra Client is configured on a node
+# See https://docs.chef.io/config_rb_client/ for detailed configuration options
+#
+# Minimal example configuration:
+# node_name "THIS_NODE_NAME"
+# chef_server_url "https://CHEF.MYCOMPANY.COM/organizations/MY_CHEF_ORG"
+# chef_license "accept"
+EOF
+fi
+
+mkdir -p "$CONFIG_DIR/client.d"
+mkdir -p "$CONFIG_DIR/accepted_licenses"
+mkdir -p "$CONFIG_DIR/trusted_certs"
+mkdir -p "$CONFIG_DIR/ohai/plugins"
echo "Thank you for installing Chef Infra Client! For help getting started visit https://learn.chef.io"
diff --git a/omnibus/resources/chef/msi/localization-en-us.wxl.erb b/omnibus/resources/chef/msi/localization-en-us.wxl.erb
index e6b055984f..d71420b3ec 100644
--- a/omnibus/resources/chef/msi/localization-en-us.wxl.erb
+++ b/omnibus/resources/chef/msi/localization-en-us.wxl.erb
@@ -21,11 +21,8 @@
<String Id="SchTaskDescription">Schedule <%= friendly_name %> to run at a pre-defined time intervals.</String>
<!-- Service -->
<!-- Keep these in sync with the name and description in chef-service-manager -->
- <String Id="ServiceDisplayName"><%= friendly_name %> Service</String>
- <String Id="ServiceDescription">Runs <%= friendly_name %> on regular, configurable intervals.</String>
<String Id="FeatureMainName"><%= friendly_name %></String>
<String Id="FeatureSchTaskName"><%= friendly_name %> Scheduled Task</String>
- <String Id="FeatureServiceName"><%= friendly_name %> Service</String>
<String Id="FeaturePSModuleName"><%= friendly_name %> PowerShell wrappers</String>
<String Id="MinimumOSVersionMessage">This package requires minimum OS version: Windows 7/Windows Server 2008 R2 or greater.</String>
@@ -35,7 +32,6 @@
<String Id="CustomizeDlgTextMsg">Select an option to change between the Chef's unattended execution options.</String>
<String Id="CustomizeDlgTextTitle">Chef Unattended Execution Options</String>
<String Id="CustomizeDlgFirstRadioButtonText">Chef Infra Client Scheduled Task</String>
- <String Id="CustomizeDlgSecondRadioButtonText">Chef Infra Client Service</String>
<String Id="CustomizeDlgThirdRadioButtonText">None</String>
<String Id="CustomizeDlgOptionsMsg">The installer can configure the Chef Infra Client to run periodically as either a scheduled task or a service. Using a scheduled task is recommended. For more information, see https://docs.chef.io/windows/.</String>
diff --git a/omnibus/resources/chef/msi/source.wxs.erb b/omnibus/resources/chef/msi/source.wxs.erb
index 8b8deaabaa..0526f1bf42 100644
--- a/omnibus/resources/chef/msi/source.wxs.erb
+++ b/omnibus/resources/chef/msi/source.wxs.erb
@@ -19,7 +19,7 @@
Compressed="yes" InstallScope="perMachine" />
<!--
- Create property references for the well known SIDs of the
+ Create property references for the well known SIDs of the
accounts we want to restrict for the project location folder
-->
<PropertyRef Id="WIX_ACCOUNT_LOCALSYSTEM" />
@@ -29,8 +29,8 @@
<Media Id="1" Cabinet="ChefClient.cab" EmbedCab="yes" CompressionLevel="high" />
<!--
- Take advantage of Windows Installer 5.0 feature (if available) to disable
- checkpointing and other costings that take significant amounts of time
+ Take advantage of Windows Installer 5.0 feature (if available) to disable
+ checkpointing and other costings that take significant amounts of time
ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd408005(v=vs.85).aspx
-->
<Property Id="MSIFASTINSTALL" Value="7" />
@@ -90,12 +90,6 @@
Impersonate="no"
Return="ignore" />
- <CustomAction Id="RemoveChefClientService"
- Directory="TARGETDIR"
- ExeCommand="&quot;[SystemFolder]SC.EXE&quot; DELETE &quot;chef-client&quot;"
- Execute="deferred"
- Impersonate="no"
- Return="ignore" />
<InstallExecuteSequence>
<Custom Action="FastUnzip" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
@@ -106,12 +100,9 @@
</Custom>
<Custom Action="RemoveChefClientScheduledTask" Before="RemoveFiles">
- <![CDATA[(Installed AND (&NoneFeature=3 OR &ChefServiceFeature=3)) OR (REMOVE="ALL")]]>
+ <![CDATA[(Installed AND &NoneFeature=3) OR (REMOVE="ALL")]]>
</Custom>
- <Custom Action="RemoveChefClientService" Before="RemoveFiles">
- <![CDATA[Installed AND (&NoneFeature=3 OR &ChefSchTaskFeature=3) OR (REMOVE="ALL")]]>
- </Custom>
</InstallExecuteSequence>
<UI>
@@ -121,7 +112,6 @@
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="WindowsVolume">
- <!-- Service needs chef directory to be present. -->
<Directory Id="CONFIGLOCATION" Name="chef">
<Component Id="CONFIGLOCATIONDIR" Guid="{F66F6394-51A4-4C5D-908B-E55584473436}" >
<CreateFolder Directory="CONFIGLOCATION" />
@@ -154,19 +144,6 @@
</Directory>
<Directory Id="EMBEDDED" Name="embedded" >
<Directory Id="EMBEDDEDBIN" Name="bin" >
- <Component Id="ChefClientService" Guid="{69B2D8BE-4A47-4BE3-AEE8-83FAEB6E2FAF}" >
- <File Id="RubyExecutable" Source="$(var.ProjectSourceDir)\embedded\bin\ruby.exe" KeyPath="yes" />
- <ServiceInstall Name="chef-client" Type="ownProcess"
- Start="auto" Vital="yes" ErrorControl="ignore"
- Arguments="[PROJECTLOCATION]bin\chef-windows-service"
- DisplayName="!(loc.ServiceDisplayName)"
- Description="!(loc.ServiceDescription)">
- <ServiceDependency Id="Winmgmt" />
- <ServiceConfig DelayedAutoStart="yes" OnInstall="yes" />
- </ServiceInstall>
- <ServiceControl Id="ControlChefClientService" Name="chef-client"
- Remove="both" Stop="both" Wait="yes" />
- </Component>
<Component Id="ChefClientLog" Guid="{8e492d59-3a0c-43fd-b889-e35dfa33da91}">
<util:EventSource xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
Name="Chef" Log="Application"
@@ -211,9 +188,6 @@
<ComponentRef Id="ChefSchTask" />
</Feature>
- <Feature Id="ChefServiceFeature" Title="!(loc.FeatureServiceName)" Level="1000" AllowAdvertise="no" Display="hidden">
- <ComponentRef Id="ChefClientService" />
- </Feature>
<Feature Id="NoneFeature" Title="None" Level="1000" AllowAdvertise="no" Display="hidden">
<!-- Do Nothing -->
@@ -273,9 +247,9 @@
<Publish Dialog="CustomizeDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="1">Installed</Publish>
<Publish Dialog="CustomizeDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg" Order="2">NOT Installed</Publish>
- <Publish Dialog="CustomizeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg"><![CDATA[((NOT &ChefUnattendedExecutionOptions=3) AND NOT ((?ChefSchTask=3) OR (?ChefClientService=3) OR (?None=3)))]]></Publish>
+ <Publish Dialog="CustomizeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg"><![CDATA[((NOT &ChefUnattendedExecutionOptions=3) AND NOT ((?ChefSchTask=3) OR (?None=3)))]]></Publish>
- <Publish Dialog="CustomizeDlg" Control="Next" Event="NewDialog" Value="ChefUnattendedExecutionOptionsSelectionDlg"><![CDATA[((&ChefUnattendedExecutionOptions=3) OR (?ChefSchTask=3 OR ?ChefClientService=3 OR ?None=3))]]></Publish>
+ <Publish Dialog="CustomizeDlg" Control="Next" Event="NewDialog" Value="ChefUnattendedExecutionOptionsSelectionDlg"><![CDATA[((&ChefUnattendedExecutionOptions=3) OR (?ChefSchTask=3 OR ?None=3))]]></Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="CustomizeDlg" Order="1"><![CDATA[NOT &ChefUnattendedExecutionOptions=3]]> AND (NOT Installed OR WixUI_InstallMode = "Change")</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="ChefUnattendedExecutionOptionsSelectionDlg" Order="1"><![CDATA[&ChefUnattendedExecutionOptions=3]]> AND (NOT Installed OR WixUI_InstallMode = "Change")</Publish>
@@ -302,7 +276,6 @@
<Control Id="OptionsRadioGroup" Type="RadioButtonGroup" Property="CHEF_SERVICE_OPTIONS_RADIO_BUTTON_GROUP" Height="80" Width="140" X="35" Y="110">
<RadioButtonGroup Property="CHEF_SERVICE_OPTIONS_RADIO_BUTTON_GROUP">
<RadioButton Value="SchTask" Text="!(loc.CustomizeDlgFirstRadioButtonText)" Height="17" Width="140" X="0" Y="10" />
- <RadioButton Value="Service" Text="!(loc.CustomizeDlgSecondRadioButtonText)" Height="17" Width="140" X="0" Y="35" />
<RadioButton Value="None" Text="!(loc.CustomizeDlgThirdRadioButtonText)" Height="17" Width="140" X="0" Y="60" />
</RadioButtonGroup>
</Control>
@@ -315,7 +288,6 @@
</Control>
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="Next">
<Publish Event="AddLocal" Value="ChefSchTaskFeature">CHEF_SERVICE_OPTIONS_RADIO_BUTTON_GROUP = "SchTask"</Publish>
- <Publish Event="AddLocal" Value="ChefServiceFeature">CHEF_SERVICE_OPTIONS_RADIO_BUTTON_GROUP = "Service"</Publish>
<Publish Event="AddLocal" Value="NoneFeature">CHEF_SERVICE_OPTIONS_RADIO_BUTTON_GROUP = "None"</Publish>
<Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
</Control>
diff --git a/omnibus_overrides.rb b/omnibus_overrides.rb
index 2c608c0d42..32c2cc6e69 100644
--- a/omnibus_overrides.rb
+++ b/omnibus_overrides.rb
@@ -3,11 +3,9 @@
#
# NOTE: You MUST update omnibus-software when adding new versions of
# software here: bundle exec rake dependencies:update_omnibus_gemfile_lock
-override :rubygems, version: "3.1.4" # pin to what ships in the ruby version
-override :bundler, version: "2.1.4" # pin to what ships in the ruby version
-override "libarchive", version: "3.4.3"
+override "libarchive", version: "3.5.1"
override "libffi", version: "3.3"
-override "libiconv", version: "1.15"
+override "libiconv", version: "1.16"
override "liblzma", version: "5.2.5"
override "libtool", version: "2.4.2"
override "libxml2", version: "2.9.10"
@@ -15,19 +13,12 @@ override "libxslt", version: "1.1.34"
override "libyaml", version: "0.1.7"
override "makedepend", version: "1.0.5"
override "ncurses", version: "5.9"
-override "nokogiri", version: "1.10.10"
-override "openssl", version: "1.0.2w"
+override "nokogiri", version: "1.11.0"
+override "openssl", version: mac_os_x? ? "1.1.1k" : "1.0.2y"
override "pkg-config-lite", version: "0.28-1"
-override "ruby", version: "2.7.2"
+override "bundler", version: "2.2.15"
+override "ruby", version: "3.0.1"
override "ruby-windows-devkit-bash", version: "3.1.23-4-msys-1.0.18"
override "util-macros", version: "1.19.0"
override "xproto", version: "7.0.28"
override "zlib", version: "1.2.11"
-
-# We build both chef and ohai omnibus-software definitions which creates the
-# chef-client and ohai binstubs. Out of the box the ohai definition uses whatever
-# is in master, which won't match what's in the Gemfile.lock and used by the chef
-# definition. This pin will ensure that ohai and chef-client commands use the
-# same (released) version of ohai.
-gemfile_lock = File.join(File.expand_path(__dir__), "Gemfile.lock")
-override "ohai", version: "#{::File.readlines(gemfile_lock).find { |l| l =~ /^\s+ohai \((\d+\.\d+\.\d+)\)/ }; "v" + $1}" # rubocop: disable Layout/SpaceInsideStringInterpolation
diff --git a/post-bundle-install.rb b/post-bundle-install.rb
new file mode 100644
index 0000000000..3abefab725
--- /dev/null
+++ b/post-bundle-install.rb
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+
+gem_home = Gem.paths.home
+
+puts "fixing bundle installed gems in #{gem_home}"
+
+# Install gems from git repos. This makes the assumption that there is a <gem_name>.gemspec and
+# you can simply gem build + gem install the resulting gem, so nothing fancy. This does not use
+# rake install since we need --conservative --minimal-deps in order to not install duplicate gems.
+#
+Dir["#{gem_home}/bundler/gems/*"].each do |gempath|
+ matches = File.basename(gempath).match(/(.*)-[A-Fa-f0-9]{12}/)
+ next unless matches
+
+ gem_name = matches[1]
+ next unless gem_name
+
+ next if gem_name == "chef"
+
+ puts "re-installing #{gem_name}..."
+
+ # we can't use "command" or "bundle" or "gem" DSL methods here since those are lazy and we need to run commands immediately
+ # (this is like a shell_out inside of a ruby_block in core chef, you don't use an execute resource inside of a ruby_block or
+ # things get really weird and unexpected)
+ Dir.chdir(gempath) do
+ system("gem build #{gem_name}.gemspec") or raise "gem build failed"
+ system("gem install #{gem_name}*.gem --conservative --minimal-deps --no-document") or raise "gem install failed"
+ end
+end
diff --git a/scripts/bk_tests/bk_container_prep.sh b/scripts/bk_tests/bk_container_prep.sh
deleted file mode 100755
index 949f75c0d3..0000000000
--- a/scripts/bk_tests/bk_container_prep.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-# This script gets a container ready to run our various tests in BuildKite
-
-echo "--- preparing..."
-
-export FORCE_FFI_YAJL="ext"
-export CHEF_LICENSE="accept-no-persist"
-export BUNDLE_GEMFILE="/workdir/Gemfile"
-
-# make sure we have the network tools in place for various network specs
-if [ -f /etc/debian_version ]; then
- touch /etc/network/interfaces
-fi
-
-# make sure we have the omnibus_overrides specified version of rubygems / bundler
-echo "--- Install proper bundler"
-gem uninstall bundler -a -x || true
-gem install bundler -v $(grep :bundler omnibus_overrides.rb | cut -d'"' -f2)
-bundle --version
-rm -f .bundle/config
-
-echo "+++ Run tests"
diff --git a/scripts/bk_tests/bk_linux_exec.sh b/scripts/bk_tests/bk_linux_exec.sh
deleted file mode 100755
index 415f646a3c..0000000000
--- a/scripts/bk_tests/bk_linux_exec.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-
-# Enable IPv6 in docker
-echo "--- Enabling ipv6 on docker"
-sudo systemctl stop docker
-dockerd_config="/etc/docker/daemon.json"
-sudo echo "$(jq '. + {"ipv6": true, "fixed-cidr-v6": "2001:2019:6002::/80", "ip-forward": false}' $dockerd_config)" > $dockerd_config
-sudo systemctl start docker
-
-# Install C and C++
-echo "--- Installing package deps"
-sudo yum install -y gcc gcc-c++ openssl-devel readline-devel zlib-devel
-
-# Install omnibus-toolchain for git bundler and gem
-echo "--- Installing omnibus toolchain"
-curl -fsSL https://chef.io/chef/install.sh | sudo bash -s -- -P omnibus-toolchain
-
-# Set Environment Variables
-export BUNDLE_GEMFILE=$PWD/kitchen-tests/Gemfile
-export FORCE_FFI_YAJL=ext
-export CHEF_LICENSE="accept-silent"
-export PATH=$PATH:/opt/omnibus-toolchain/embedded/bin
-
-# Update Gems
-echo "--- Installing Gems"
-echo 'gem: --no-document' >> ~/.gemrc
-sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER )
-/opt/omnibus-toolchain/bin/bundle install --jobs=3 --retry=3 --path=../vendor/bundle
-
-echo "--- Config information"
-
-echo "!!!! RUBY VERSION !!!!"
-ruby --version
-echo "!!!! BUNDLE LOCATION !!!!"
-which bundle
-echo "!!!! DOCKER VERSION !!!!"
-docker version
-echo "!!!! DOCKER STATUS !!!!"
-sudo service docker status
-
-echo "+++ Running tests" \ No newline at end of file
diff --git a/scripts/bk_tests/bk_run_choco.ps1 b/scripts/bk_tests/bk_run_choco.ps1
deleted file mode 100644
index 49f9186701..0000000000
--- a/scripts/bk_tests/bk_run_choco.ps1
+++ /dev/null
@@ -1,9 +0,0 @@
-$CurrentDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$PrepScript = Join-Path $CurrentDirectory "bk_win_prep.ps1"
-Invoke-Expression $PrepScript
-
-choco --version
-
-echo "+++ bundle exec rspec chocolatey_package_spec"
-bundle exec rspec spec/functional/resource/chocolatey_package_spec.rb
-if (-not $?) { throw "Chef chocolatey functional tests failing." }
diff --git a/scripts/bk_tests/bk_win_functional.ps1 b/scripts/bk_tests/bk_win_functional.ps1
deleted file mode 100644
index 3cc38f0b37..0000000000
--- a/scripts/bk_tests/bk_win_functional.ps1
+++ /dev/null
@@ -1,43 +0,0 @@
-Write-Output "--- system details"
-$Properties = 'Caption', 'CSName', 'Version', 'BuildType', 'OSArchitecture'
-Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table -AutoSize
-
-# chocolatey functional tests fail so delete the chocolatey binary to avoid triggering them
-Remove-Item -Path C:\ProgramData\chocolatey\bin\choco.exe -ErrorAction SilentlyContinue
-
-$ErrorActionPreference = 'Stop'
-
-Write-Output "--- Enable Ruby 2.7"
-Write-Output "Add Uru to Environment PATH"
-$env:PATH = "C:\Program Files (x86)\Uru;" + $env:PATH
-[Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine)
-
-Write-Output "Register Installed Ruby Version 2.7 With Uru"
-Start-Process "C:\Program Files (x86)\Uru\uru_rt.exe" -ArgumentList 'admin add C:\ruby27\bin' -Wait
-uru 271
-if (-not $?) { throw "Can't Activate Ruby. Did Uru Registration Succeed?" }
-
-Write-Output "--- configure winrm"
-
-winrm quickconfig -q
-
-Write-Output "--- update bundler"
-
-ruby -v
-if (-not $?) { throw "Can't run Ruby. Is it installed?" }
-
-$env:BUNDLER_VERSION=$(findstr bundler omnibus_overrides.rb | %{ $_.split(" ")[3] })
-$env:BUNDLER_VERSION=($env:BUNDLER_VERSION -replace '"', "")
-Write-Output $env:BUNDLER_VERSION
-
-gem install bundler -v $env:BUNDLER_VERSION --force --no-document --quiet
-if (-not $?) { throw "Unable to update Bundler" }
-bundle --version
-
-Write-Output "--- bundle install"
-bundle install --jobs=3 --retry=3 --without omnibus_package
-if (-not $?) { throw "Unable to install gem dependencies" }
-
-Write-Output "+++ bundle exec rake spec:functional"
-bundle exec rake spec:functional
-if (-not $?) { throw "Chef functional specs failing." }
diff --git a/scripts/bk_tests/bk_win_prep.ps1 b/scripts/bk_tests/bk_win_prep.ps1
deleted file mode 100644
index 1a09378fee..0000000000
--- a/scripts/bk_tests/bk_win_prep.ps1
+++ /dev/null
@@ -1,20 +0,0 @@
-echo "--- system details"
-$Properties = 'Caption', 'CSName', 'Version', 'BuildType', 'OSArchitecture'
-Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table -AutoSize
-
-echo "--- update bundler"
-
-ruby -v
-if (-not $?) { throw "Can't run Ruby. Is it installed?" }
-
-$env:BUNDLER_VERSION=$(findstr bundler omnibus_overrides.rb | %{ $_.split(" ")[3] })
-$env:BUNDLER_VERSION=($env:BUNDLER_VERSION -replace '"', "")
-echo $env:BUNDLER_VERSION
-
-gem install bundler -v $env:BUNDLER_VERSION --force --no-document --quiet
-if (-not $?) { throw "Unable to update Bundler" }
-bundle --version
-
-echo "--- bundle install"
-bundle install --jobs=3 --retry=3 --without omnibus_package
-if (-not $?) { throw "Unable to install gem dependencies" }
diff --git a/scripts/ci/verify-plan.ps1 b/scripts/ci/verify-plan.ps1
deleted file mode 100644
index bf206fab9c..0000000000
--- a/scripts/ci/verify-plan.ps1
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env powershell
-
-#Requires -Version 5
-
-param(
- # The name of the plan that is to be built.
- [string]$Plan
-)
-
-$env:HAB_ORIGIN = 'ci'
-$Plan = 'chef-infra-client'
-
-Write-Host "--- :8ball: :windows: Verifying $Plan"
-
-powershell -File "./scripts/ci/ensure-minimum-viable-hab.ps1"
-if (-not $?) { throw "Could not ensure the minimum hab version required is installed." }
-
-Write-Host "--- :key: Generating fake origin key"
-hab origin key generate $env:HAB_ORIGIN
-
-$project_root = "$(git rev-parse --show-toplevel)"
-Set-Location $project_root
-
-Write-Host "--- :construction: Building $Plan"
-$env:DO_CHECK=$true; hab pkg build .
-if (-not $?) { throw "unable to build"}
-
-. results/last_build.ps1
-if (-not $?) { throw "unable to determine details about this build"}
-
-Write-Host "--- :hammer_and_wrench: Installing $pkg_ident"
-hab pkg install results/$pkg_artifact
-if (-not $?) { throw "unable to install this build"}
-
-Write-Host "--- :mag_right: Testing $Plan"
-powershell -File "./habitat/tests/test.ps1" -PackageIdentifier $pkg_ident
-if (-not $?) { throw "package didn't pass the test suite" }
diff --git a/spec/data/cookbooks/openldap/libraries/openldap.rb b/spec/data/cookbooks/openldap/libraries/openldap.rb
index 6a3f058f95..0b9389c688 100644
--- a/spec/data/cookbooks/openldap/libraries/openldap.rb
+++ b/spec/data/cookbooks/openldap/libraries/openldap.rb
@@ -1,4 +1,4 @@
-require_relative './openldap/version.rb'
+require_relative './openldap/version'
class OpenLDAP
end
diff --git a/spec/data/lwrp/resources/bar.rb b/spec/data/lwrp/resources/bar.rb
index 2ff35efd08..d36575917d 100644
--- a/spec/data/lwrp/resources/bar.rb
+++ b/spec/data/lwrp/resources/bar.rb
@@ -1,2 +1,4 @@
+unified_mode true
+
provides :lwrp_bar # This makes sure that we cover the case of lwrps using provides
actions :pass_buck, :prepare_eyes, :watch_paint_dry
diff --git a/spec/data/lwrp/resources/buck_passer.rb b/spec/data/lwrp/resources/buck_passer.rb
index 7335c0aae2..6f542dc423 100644
--- a/spec/data/lwrp/resources/buck_passer.rb
+++ b/spec/data/lwrp/resources/buck_passer.rb
@@ -1,3 +1,4 @@
+unified_mode true
provides :buck_passer
diff --git a/spec/data/lwrp/resources/buck_passer_2.rb b/spec/data/lwrp/resources/buck_passer_2.rb
index c7a1a279f3..c0ab7d7885 100644
--- a/spec/data/lwrp/resources/buck_passer_2.rb
+++ b/spec/data/lwrp/resources/buck_passer_2.rb
@@ -1,3 +1,4 @@
+unified_mode true
default_action :pass_buck
actions :pass_buck
diff --git a/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb b/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb
index 3a8ae2c19f..faece8b582 100644
--- a/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb
+++ b/spec/data/lwrp/resources/embedded_resource_accesses_providers_scope.rb
@@ -1,3 +1,4 @@
+unified_mode true
default_action :twiddle_thumbs
actions :twiddle_thumbs
diff --git a/spec/data/lwrp/resources/foo.rb b/spec/data/lwrp/resources/foo.rb
index 0ee83f0cd0..d4d2155122 100644
--- a/spec/data/lwrp/resources/foo.rb
+++ b/spec/data/lwrp/resources/foo.rb
@@ -1,3 +1,5 @@
+unified_mode true
+
actions :prepare_thumbs, :twiddle_thumbs
default_action :pass_buck
diff --git a/spec/data/lwrp/resources/inline_compiler.rb b/spec/data/lwrp/resources/inline_compiler.rb
index fe446ddf84..0e63e4ce43 100644
--- a/spec/data/lwrp/resources/inline_compiler.rb
+++ b/spec/data/lwrp/resources/inline_compiler.rb
@@ -1,3 +1,4 @@
+unified_mode true
default_action :test
actions :test, :no_updates
diff --git a/spec/data/lwrp/resources/monkey_name_printer.rb b/spec/data/lwrp/resources/monkey_name_printer.rb
index d70e2f34e3..60c3203dca 100644
--- a/spec/data/lwrp/resources/monkey_name_printer.rb
+++ b/spec/data/lwrp/resources/monkey_name_printer.rb
@@ -1,3 +1,4 @@
+unified_mode true
property :monkey
diff --git a/spec/data/lwrp/resources/paint_drying_watcher.rb b/spec/data/lwrp/resources/paint_drying_watcher.rb
index 519b7f83fd..1bff7c5b50 100644
--- a/spec/data/lwrp/resources/paint_drying_watcher.rb
+++ b/spec/data/lwrp/resources/paint_drying_watcher.rb
@@ -1,3 +1,4 @@
+unified_mode true
default_action :prepare_eyes
actions :prepare_eyes, :watch_paint_dry
diff --git a/spec/data/lwrp/resources/thumb_twiddler.rb b/spec/data/lwrp/resources/thumb_twiddler.rb
index 2b5d2d803e..f4ba71cebc 100644
--- a/spec/data/lwrp/resources/thumb_twiddler.rb
+++ b/spec/data/lwrp/resources/thumb_twiddler.rb
@@ -1,3 +1,4 @@
+unified_mode true
default_action :prepare_thumbs
actions :prepare_thumbs, :twiddle_thumbs
diff --git a/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb b/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
index dece7c4eab..ef6b694cb2 100644
--- a/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
+++ b/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
@@ -1 +1,3 @@
+unified_mode true
+
attribute :penguin, :kind_of => String, :default => node[:penguin_name]
diff --git a/spec/data/lwrp_const_scoping/resources/conflict.rb b/spec/data/lwrp_const_scoping/resources/conflict.rb
index e69de29bb2..059460afc0 100644
--- a/spec/data/lwrp_const_scoping/resources/conflict.rb
+++ b/spec/data/lwrp_const_scoping/resources/conflict.rb
@@ -0,0 +1 @@
+unified_mode true
diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb
index 2fc13d32fd..1d6be84e58 100644
--- a/spec/data/lwrp_override/resources/foo.rb
+++ b/spec/data/lwrp_override/resources/foo.rb
@@ -1,4 +1,5 @@
# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore
+unified_mode true
actions :never_execute
diff --git a/spec/data/rubygems.org/latest_specs.4.8.gz b/spec/data/rubygems.org/latest_specs.4.8.gz
new file mode 100644
index 0000000000..ab6a175f32
--- /dev/null
+++ b/spec/data/rubygems.org/latest_specs.4.8.gz
Binary files differ
diff --git a/spec/data/rubygems.org/nonexistent_gem b/spec/data/rubygems.org/nonexistent_gem
new file mode 100644
index 0000000000..0ba94359df
--- /dev/null
+++ b/spec/data/rubygems.org/nonexistent_gem
Binary files differ
diff --git a/spec/data/rubygems.org/nonexistent_gem-info b/spec/data/rubygems.org/nonexistent_gem-info
new file mode 100644
index 0000000000..7e88a0e205
--- /dev/null
+++ b/spec/data/rubygems.org/nonexistent_gem-info
@@ -0,0 +1 @@
+This gem could not be found \ No newline at end of file
diff --git a/spec/data/rubygems.org/sexp_processor b/spec/data/rubygems.org/sexp_processor
new file mode 100644
index 0000000000..37c6e97769
--- /dev/null
+++ b/spec/data/rubygems.org/sexp_processor
Binary files differ
diff --git a/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz b/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz
new file mode 100644
index 0000000000..38840f2682
--- /dev/null
+++ b/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz
Binary files differ
diff --git a/spec/data/rubygems.org/sexp_processor-info b/spec/data/rubygems.org/sexp_processor-info
new file mode 100644
index 0000000000..78add7f2fa
--- /dev/null
+++ b/spec/data/rubygems.org/sexp_processor-info
@@ -0,0 +1,49 @@
+---
+3.0.0 |checksum:ff9abf0d904ba57b9654352b396aa28cf6ad5315af99d8bbf664f5ff6efd3a5d
+3.0.1 |checksum:d012a759dc6950dcda667a359051c2e62e4bd19790aeed698a5e47d013ef3ae7
+3.0.2 |checksum:ba85e835493e6099c2f52937b77ce518d65af39409befdd9b43927c0a604ed87
+3.0.3 |checksum:a433b01d821f5e81200fbec77fc26a1ecb186ad7d8e40d19ed34ea295287170a
+3.0.4 |checksum:5f394545863d5abe5c3f53c3b883128f58900cd792504076a765b53c9a49f10a
+3.0.5 |checksum:8e06c84ed3a0159a0f2e6f7b10bbd056954ac2d33548318ccc3088556c8a8891
+3.0.6 |checksum:e6e0f30ff14b73b28f8e5803646aa6d2ed81b3b239e76815fd8160ea4121c650
+3.0.7 |checksum:7647c24bbebea0ef57a892953fc96349091924a399ee5d98f41da5d9db484816
+3.0.8 |checksum:6c8ff89feab635e332e115356451614a16d171485e34b6b30a6dea243caaaed7
+3.0.9 |checksum:b55c35100f5e1e191ff67eac8667aea9433d1492697c9434cdb35550cf6e4dd0
+3.0.10 |checksum:e168db8d4eccfc721685d939654698f1b419f018f45e38d17ab40033102987f4
+3.1.0 |checksum:3d9dd950ba0b235c4901d04e410c7e716feb491148bf0ca7daa0f510838b3bf7
+3.2.0 |checksum:5951f8d33ede2f68686c701142c6cc1004d6f525b0aa8e8279a1bf075542b0f0
+4.0.0 |checksum:36c185f8caecacb178500cabdc3e038862df640536c2e84ab763ae134462896f
+4.0.1 |checksum:efd33857c0f41a413ec3ea20251f43c4826fe8a11b01099335f4a3b6777eb727
+4.1.0 |checksum:d26879b9a0675ea156c82e26971149349a1474aac3da4d0d2a04cc18e6df73a3
+4.1.1 |checksum:15df4e54e0fab19e225862b36dff823d5b87d57ea998f2e47c52ce01de82b3d9
+4.1.2 |checksum:7c2ed2d62d0305f2c33cba2e99b288df0f3f4343c367b8ee8cad8c735abc8568
+4.1.3 |checksum:d02f1465c7f012f77a61abdaa841a5273a2277247bc143bfa11cf139a29dbdf8
+4.1.4 |checksum:f7798b1682dcf750dab5f4f8da548fee36f30864a4e4b0d8a63295d159357c3b
+4.1.5 |checksum:741c7dfe5e392ae39e22399546d25fe00ffdfc7a55e653e6a99b6770b1c0066c
+4.2.0 |checksum:3cde88e3d440f63af3cd48edca88bd98872622403740ecda78b7d27161367486
+4.2.1 |checksum:dfc3eef6ef13c5750c3faab782c4db6c74a7bcc5d03e56e4edeec21aab034185
+4.3.0 |checksum:7accb37900d1599c6f0f40be92bc62a5db4e5a7eb64f33a858cf83e798dd1ddc
+4.4.0 |checksum:71591ddbda99b5e12e4a46d377c87513850ca7aa4b1aa800ae02792cadee6be7
+4.4.1 |checksum:8a10333552216bf3d3846476cfe78dbc9b5724864e3f5016837724622d828f16
+4.4.2 |checksum:adba9d17de5957532223a1bf0e7bdba5ab849d6576e9210439a7d99e0cfa2595
+4.4.3 |checksum:b3eb96da1fe998f1c00665a9c645878518134cca7c35d39c4bb716e866f4cc57
+4.4.4 |checksum:43cab5a67ca409d62411f869ddb7a0a4de0988b489d3f1d610d9b6e521964fd5
+4.4.5 |checksum:af8713761f1b6604865830c54324e57c33e7cc05107ebdbee4e6d458f8f8fe7c
+4.5.0 |checksum:54d94dc52cf98a51548c8f3e77031a3347508b542b8cb066100ed3ac40c03081
+4.5.1 |checksum:1456a9be103bf1de0d34ff6980b77a5a72cf3d4b35bbd2182ac62506981a234c
+4.6.0 |checksum:e2498f90c75bd4c19d1739afadde8c03af26a881c8bd775f71d2f180de65b43f
+4.6.1 |checksum:e2e96c2ee3ea81e1dc7b4b4abee23b6e552e669cfe456ee69126a29a03373cd7
+4.7.0 |checksum:963a1f5b21c95595fb3cf1e8531784bf3d8fe30302cf6f271b08aefdc63e453f
+4.8.0 |checksum:5b9325f28b5be80ba8d43b7660f60ad67c9304fe8181dee89d3a348b13d2fada
+4.9.0 |checksum:333619bc71d563ee60f26fc5f3a7f57bd89ee3191177fdce87a014dcb1b8d3b0
+4.10.0b1 |checksum:8791e2006a2ddbf8dc96cfc19633de01af8cb8687703177a85aeb3f959974d5b,rubygems:> 1.3.1
+4.10.0 |checksum:b67a289ae4a3968d93dab0803d0ef5a262b6f94138ab98072e489d2aa8af4034
+4.10.1 |checksum:63d2297712eb1d6219ab1cb9207d9a239ac9ad20463c0b58ca865f0b46deb5ec
+4.11.0 |checksum:4c90ff17c492789fdd248369fa16ce65ef05576b3d9f593a49c6a0961dbcd5ee
+4.12.0 |checksum:671110574e96377a03b328bfb7f6339540443eca0b62913bf8fe38e9ebcb4470
+4.12.1 |checksum:f87cd92457a343b4e951e1f1ac3e8183f98de4640a32f6ceb44628332d21a088
+4.13.0 |checksum:47e86c22a2d7810897e3eae9669ab9afa220f5e6cea5ac1d47164650a9b857d3
+4.14.0 |checksum:99a20cc5e7b901f6b493a8ca5e13439b73b19671eaaca68a00216c4f66765edc
+4.14.1 |checksum:0fa8731445cf4a0c01570ec29aac4b50a0451ce66b1b31ad768f5035e3af7b90,ruby:~> 2.2
+4.15.0 |checksum:a5ec27d8055ad47444cfb7ce860bad8af2365772a82892f4a8a0d97e8e9e3b34,ruby:~> 2.2
+4.15.1 |checksum:9291a0f2247f50d15068ee6965b67cd7b678b0d273e18adf3c0b2ea4a890125c,ruby:< 3.1&>= 2.1
diff --git a/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb b/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
index aee6f4adc4..eaf68e9058 100644
--- a/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('circular-dep1-resource')
diff --git a/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb b/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
index efd4ef3e8c..63eb08d5b5 100644
--- a/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('circular-dep2-resource')
diff --git a/spec/data/run_context/cookbooks/dependency1/resources/resource.rb b/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
index 3bd0f170bc..d355f7ce48 100644
--- a/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('dependency1-resource')
diff --git a/spec/data/run_context/cookbooks/dependency2/resources/resource.rb b/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
index 73a0a34e53..5ec44d4564 100644
--- a/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('dependency2-resource')
diff --git a/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb b/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
index 067835e1c1..fd8fa73b5a 100644
--- a/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('no-default-attr-resource')
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
index 486e81d458..ffe7d099c9 100644
--- a/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
@@ -1 +1,3 @@
+unified_mode true
+
LibraryLoadOrder.record('test-with-circular-deps-resource')
diff --git a/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb b/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
index 54b152dd1c..203efabec5 100644
--- a/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
@@ -1 +1,2 @@
+unified_mode true
LibraryLoadOrder.record('test-with-deps-resource')
diff --git a/spec/data/run_context/cookbooks/test/resources/resource.rb b/spec/data/run_context/cookbooks/test/resources/resource.rb
index 67759217ca..54c99d2fb8 100644
--- a/spec/data/run_context/cookbooks/test/resources/resource.rb
+++ b/spec/data/run_context/cookbooks/test/resources/resource.rb
@@ -1 +1,3 @@
+unified_mode true
+
LibraryLoadOrder.record('test-resource')
diff --git a/spec/data/ssl/binary/chef-rspec-der.cert b/spec/data/ssl/binary/chef-rspec-der.cert
new file mode 100644
index 0000000000..e49df6252a
--- /dev/null
+++ b/spec/data/ssl/binary/chef-rspec-der.cert
Binary files differ
diff --git a/spec/data/ssl/binary/chef-rspec-der.key b/spec/data/ssl/binary/chef-rspec-der.key
new file mode 100644
index 0000000000..d8adadc5c9
--- /dev/null
+++ b/spec/data/ssl/binary/chef-rspec-der.key
Binary files differ
diff --git a/spec/data/trusted_certs_empty/README.md b/spec/data/trusted_certs_empty/README.md
deleted file mode 100644
index e7e52627f1..0000000000
--- a/spec/data/trusted_certs_empty/README.md
+++ /dev/null
@@ -1 +0,0 @@
-A directory with no certs. Used for testing directories with no certs during bootstrap.
diff --git a/spec/functional/dsl/registry_helper_spec.rb b/spec/functional/dsl/registry_helper_spec.rb
index a36114eb3a..9d043dd35f 100644
--- a/spec/functional/dsl/registry_helper_spec.rb
+++ b/spec/functional/dsl/registry_helper_spec.rb
@@ -21,7 +21,7 @@ require "spec_helper"
describe Chef::Resource::RegistryKey, :windows_only do
- before (:all) do
+ before(:all) do
::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root"
::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch"
::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg|
diff --git a/spec/functional/knife/configure_spec.rb b/spec/functional/knife/configure_spec.rb
deleted file mode 100644
index 8f2a5b4d6e..0000000000
--- a/spec/functional/knife/configure_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-require "chef/knife/configure"
-
-describe "knife configure" do
- let(:ohai) do
- OHAI_SYSTEM
- end
-
- it "loads the fqdn from Ohai" do
- knife_configure = Chef::Knife::Configure.new
- hostname_guess = ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] || "localhost"
- expect(knife_configure.guess_servername).to eql(hostname_guess)
- end
-end
diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb
deleted file mode 100644
index 650db0ede5..0000000000
--- a/spec/functional/knife/cookbook_delete_spec.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tiny_server"
-
-describe Chef::Knife::CookbookDelete do
- let(:server) { TinyServer::Manager.new }
- let(:api) { TinyServer::API.instance }
- let(:knife_stdout) { StringIO.new }
- let(:knife_stderr) { StringIO.new }
- let(:knife) do
- knife = Chef::Knife::CookbookDelete.new
- allow(knife.ui).to receive(:stdout).and_return(knife_stdout)
- allow(knife.ui).to receive(:stderr).and_return(knife_stderr)
- knife
- end
-
- before(:each) do
- server.start
- api.clear
-
- Chef::Config[:node_name] = nil
- Chef::Config[:client_key] = nil
- Chef::Config[:chef_server_url] = "http://localhost:9000"
- end
-
- after(:each) do
- server.stop
- end
-
- context "when the cookbook doesn't exist" do
- before do
- knife.name_args = %w{no-such-cookbook}
- api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({ "error" => "dear Tim, no. -Sent from my iPad" }))
- end
-
- it "logs an error and exits" do
- expect { knife.run }.to raise_error(SystemExit)
- expect(knife_stderr.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/)
- end
-
- end
-
- context "when there is only one version of a cookbook" do
- before do
- knife.name_args = %w{obsolete-cookbook}
- @cookbook_list = { "obsolete-cookbook" => { "versions" => ["version" => "1.0.0"] } }
- api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list))
- end
-
- it "asks for confirmation, then deletes the cookbook" do
- stdin, stdout = StringIO.new("y\n"), StringIO.new
- allow(knife.ui).to receive(:stdin).and_return(stdin)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
-
- cb100_deleted = false
- api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
-
- knife.run
-
- expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
- expect(cb100_deleted).to be_truthy
- end
-
- it "asks for confirmation before purging" do
- knife.config[:purge] = true
-
- stdin, stdout = StringIO.new("y\ny\n"), StringIO.new
- allow(knife.ui).to receive(:stdin).and_return(stdin)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
-
- cb100_deleted = false
- api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" }
-
- knife.run
-
- expect(stdout.string).to match(/#{Regexp.escape('Are you sure you want to purge files')}/)
- expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
- expect(cb100_deleted).to be_truthy
-
- end
-
- end
-
- context "when there are several versions of a cookbook" do
- before do
- knife.name_args = %w{obsolete-cookbook}
- versions = ["1.0.0", "1.1.0", "1.2.0"]
- with_version = lambda { |version| { "version" => version } }
- @cookbook_list = { "obsolete-cookbook" => { "versions" => versions.map(&with_version) } }
- api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list))
- end
-
- it "deletes all versions of a cookbook when given the '-a' flag" do
- knife.config[:all] = true
- knife.config[:yes] = true
- cb100_deleted = cb110_deleted = cb120_deleted = nil
- api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
- api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
- api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
- knife.run
-
- expect(cb100_deleted).to be_truthy
- expect(cb110_deleted).to be_truthy
- expect(cb120_deleted).to be_truthy
- end
-
- it "asks which version to delete and deletes that when not given the -a flag" do
- cb100_deleted = cb110_deleted = cb120_deleted = nil
- api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
- stdin, stdout = StringIO.new, StringIO.new
- allow(knife.ui).to receive(:stdin).and_return(stdin)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- stdin << "1\n"
- stdin.rewind
- knife.run
- expect(cb100_deleted).to be_truthy
- expect(stdout.string).to match(/Which version\(s\) do you want to delete\?/)
- end
-
- it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do
- cb100_deleted = cb110_deleted = cb120_deleted = nil
- api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
- api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
- api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
-
- stdin, stdout = StringIO.new("4\n"), StringIO.new
- allow(knife.ui).to receive(:stdin).and_return(stdin)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
-
- knife.run
-
- expect(cb100_deleted).to be_truthy
- expect(cb110_deleted).to be_truthy
- expect(cb120_deleted).to be_truthy
- end
-
- end
-
-end
diff --git a/spec/functional/knife/exec_spec.rb b/spec/functional/knife/exec_spec.rb
deleted file mode 100644
index 3905798317..0000000000
--- a/spec/functional/knife/exec_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tiny_server"
-
-describe Chef::Knife::Exec do
- before(:each) do
- @server = TinyServer::Manager.new # (:debug => true)
- @server.start
- end
-
- after(:each) do
- @server.stop
- end
-
- before(:each) do
- @knife = Chef::Knife::Exec.new
- @api = TinyServer::API.instance
- @api.clear
-
- Chef::Config[:node_name] = nil
- Chef::Config[:client_key] = nil
- Chef::Config[:chef_server_url] = "http://localhost:9000"
-
- $output = StringIO.new
- end
-
- it "executes a script in the context of the chef-shell main context" do
- @node = Chef::Node.new
- @node.name("ohai-world")
- response = { "rows" => [@node], "start" => 0, "total" => 1 }
- @api.get(%r{^/search/node}, 200, Chef::JSONCompat.to_json(response))
- code = "$output.puts nodes.all"
- @knife.config[:exec] = code
- @knife.run
- expect($output.string).to match(/node\[ohai-world\]/)
- end
-
-end
diff --git a/spec/functional/knife/rehash_spec.rb b/spec/functional/knife/rehash_spec.rb
deleted file mode 100644
index 8f59eec270..0000000000
--- a/spec/functional/knife/rehash_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-require "chef/knife/rehash"
-require "chef/knife/core/subcommand_loader"
-
-describe "knife rehash" do
- before do
- allow(Chef::Knife::SubcommandLoader).to receive(:load_commands)
- end
-
- after do
- # We need to clean up the generated manifest or else is messes with later tests
- FileUtils.rm_f(Chef::Knife::SubcommandLoader.plugin_manifest_path)
- end
-
- it "writes the loaded plugins to disc" do
- knife_rehash = Chef::Knife::Rehash.new
- knife_rehash.run
- expect(File.read(Chef::Knife::SubcommandLoader.plugin_manifest_path)).to match(/node_list.rb/)
- end
-end
diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
deleted file mode 100644
index 1d4aff15b5..0000000000
--- a/spec/functional/knife/ssh_spec.rb
+++ /dev/null
@@ -1,352 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tiny_server"
-
-describe Chef::Knife::Ssh do
-
- before(:each) do
- Chef::Knife::Ssh.load_deps
- @server = TinyServer::Manager.new
- @server.start
- end
-
- after(:each) do
- @server.stop
- end
-
- let(:ssh_config) { {} }
- before do
- allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config)
- end
-
- describe "identity file" do
- context "when knife[:ssh_identity_file] is set" do
- before do
- Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_identity_file" do
- @knife.run
- expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
- end
- end
-
- context "when knife[:ssh_identity_file] is set and frozen" do
- before do
- Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_identity_file" do
- @knife.run
- expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
- end
- end
-
- context "when -i is provided" do
- before do
- Chef::Config[:knife][:ssh_identity_file] = nil
- setup_knife(["-i ~/.ssh/aws.rsa", "*:*", "uptime"])
- end
-
- it "should use the value on the command line" do
- @knife.run
- expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
- end
-
- it "should override what is set in knife.rb" do
- Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa"
- @knife.merge_configs
- @knife.run
- expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa")
- end
- end
-
- context "when knife[:ssh_identity_file] is not provided]" do
- before do
- Chef::Config[:knife][:ssh_identity_file] = nil
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the default" do
- @knife.run
- expect(@knife.config[:ssh_identity_file]).to eq(nil)
- end
- end
- end
-
- describe "port" do
- context "when -p 31337 is provided" do
- before do
- setup_knife(["-p 31337", "*:*", "uptime"])
- end
-
- it "uses the ssh_port" do
- @knife.run
- expect(@knife.config[:ssh_port]).to eq("31337")
- end
- end
- end
-
- describe "user" do
- context "when knife[:ssh_user] is set" do
- before do
- Chef::Config[:knife][:ssh_user] = "ubuntu"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_user" do
- @knife.run
- expect(@knife.config[:ssh_user]).to eq("ubuntu")
- end
- end
-
- context "when knife[:ssh_user] is set and frozen" do
- before do
- Chef::Config[:knife][:ssh_user] = "ubuntu".freeze
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_user" do
- @knife.run
- expect(@knife.config[:ssh_user]).to eq("ubuntu")
- end
- end
-
- context "when -x is provided" do
- before do
- Chef::Config[:knife][:ssh_user] = nil
- setup_knife(["-x ubuntu", "*:*", "uptime"])
- end
-
- it "should use the value on the command line" do
- @knife.run
- expect(@knife.config[:ssh_user]).to eq("ubuntu")
- end
-
- it "should override what is set in knife.rb" do
- Chef::Config[:knife][:ssh_user] = "root"
- @knife.merge_configs
- @knife.run
- expect(@knife.config[:ssh_user]).to eq("ubuntu")
- end
- end
-
- context "when knife[:ssh_user] is not provided]" do
- before do
- Chef::Config[:knife][:ssh_user] = nil
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the default (current user)" do
- @knife.run
- expect(@knife.config[:ssh_user]).to eq(nil)
- end
- end
- end
-
- describe "attribute" do
- context "when knife[:ssh_attribute] is set" do
- before do
- Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_attribute" do
- @knife.run
- expect(@knife.get_ssh_attribute({ "target" => "ec2.public_hostname" })).to eq("ec2.public_hostname")
- end
- end
-
- context "when knife[:ssh_attribute] is not provided" do
- before do
- Chef::Config[:knife][:ssh_attribute] = nil
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the default" do
- @knife.run
- expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn")
- end
- end
-
- context "when -a ec2.public_public_hostname is provided" do
- before do
- Chef::Config[:knife][:ssh_attribute] = nil
- setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"])
- end
-
- it "should use the value on the command line" do
- @knife.run
- expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname")
- end
-
- it "should override what is set in knife.rb" do
- # This is the setting imported from knife.rb
- Chef::Config[:knife][:ssh_attribute] = "fqdn"
- @knife.merge_configs
- # Then we run knife with the -a flag, which sets the above variable
- setup_knife(["-a", "ec2.public_hostname", "*:*", "uptime"])
- @knife.run
- expect(@knife.config[:ssh_attribute]).to eq("ec2.public_hostname")
- end
- end
- end
-
- describe "prefix" do
- context "when knife[:prefix_attribute] is set" do
- before do
- Chef::Config[:knife][:prefix_attribute] = "name"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the prefix_attribute" do
- @knife.run
- expect(@knife.get_prefix_attribute({ "prefix" => "name" })).to eq("name")
- end
- end
-
- context "when knife[:prefix_attribute] is not provided" do
- before do
- Chef::Config[:knife][:prefix_attribute] = nil
- setup_knife(["*:*", "uptime"])
- end
-
- it "falls back to nil" do
- @knife.run
- expect(@knife.get_prefix_attribute({})).to eq(nil)
- end
- end
-
- context "when --prefix-attribute ec2.public_public_hostname is provided" do
- before do
- Chef::Config[:knife][:prefix_attribute] = nil
- setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"])
- end
-
- it "should use the value on the command line" do
- @knife.run
- expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname")
- end
-
- it "should override what is set in knife.rb" do
- # This is the setting imported from knife.rb
- Chef::Config[:knife][:prefix_attribute] = "fqdn"
- @knife.merge_configs
- # Then we run knife with the -b flag, which sets the above variable
- setup_knife(["--prefix-attribute", "ec2.public_hostname", "*:*", "uptime"])
- @knife.run
- expect(@knife.config[:prefix_attribute]).to eq("ec2.public_hostname")
- end
- end
- end
-
- describe "gateway" do
- context "when knife[:ssh_gateway] is set" do
- before do
- Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_gateway" do
- expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true })
- @knife.run
- expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname")
- end
- end
-
- context "when -G user@ec2.public_hostname is provided" do
- before do
- Chef::Config[:knife][:ssh_gateway] = nil
- setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"])
- end
-
- it "uses the ssh_gateway" do
- expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true })
- @knife.run
- expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname")
- end
- end
-
- context "when knife[:ssh_gateway_identity] is set" do
- before do
- Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
- Chef::Config[:knife][:ssh_gateway_identity] = "~/.ssh/aws-gateway.rsa"
- setup_knife(["*:*", "uptime"])
- end
-
- it "uses the ssh_gateway_identity file" do
- expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true })
- @knife.run
- expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa")
- end
- end
-
- context "when -ssh-gateway-identity is provided and knife[:ssh_gateway] is set" do
- before do
- Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname"
- Chef::Config[:knife][:ssh_gateway_identity] = nil
- setup_knife(["--ssh-gateway-identity", "~/.ssh/aws-gateway.rsa", "*:*", "uptime"])
- end
-
- it "uses the ssh_gateway_identity file" do
- expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", { append_all_supported_algorithms: true, keys: File.expand_path("#{ENV["HOME"]}/.ssh/aws-gateway.rsa").squeeze("/"), keys_only: true })
- @knife.run
- expect(@knife.config[:ssh_gateway_identity]).to eq("~/.ssh/aws-gateway.rsa")
- end
- end
-
- context "when the gateway requires a password" do
- before do
- Chef::Config[:knife][:ssh_gateway] = nil
- setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"])
- allow(@knife.session).to receive(:via) do |host, user, options|
- raise Net::SSH::AuthenticationFailed unless options[:password]
- end
- end
-
- it "should prompt the user for a password" do
- expect(@knife.ui).to receive(:ask).with("Enter the password for user@ec2.public_hostname: ", echo: false).and_return("password")
- @knife.run
- end
- end
- end
-
- def setup_knife(params = [])
- @knife = Chef::Knife::Ssh.new(params)
- # We explicitly avoid running #configure_chef, which would read a knife.rb
- # if available, but #merge_configs (which is called by #configure_chef) is
- # necessary to have default options merged in.
- @knife.merge_configs
- allow(@knife).to receive(:ssh_command) { 0 }
- @api = TinyServer::API.instance
- @api.clear
-
- Chef::Config[:node_name] = nil
- Chef::Config[:client_key] = nil
- Chef::Config[:chef_server_url] = "http://localhost:9000"
-
- @api.post("/search/node?q=*:*&start=0&rows=1000", 200) do
- %({"total":1, "start":0, "rows":[{"data": {"fqdn":"the.fqdn", "target": "the_public_hostname"}}]})
- end
- end
-
-end
diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb
index c568d40a8d..1d54fbc816 100755
--- a/spec/functional/resource/aixinit_service_spec.rb
+++ b/spec/functional/resource/aixinit_service_spec.rb
@@ -34,7 +34,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
expect(File.exist?("#{Dir.tmpdir}/#{file_name}")).to be_falsey
end
- def valide_symlinks(expected_output, run_level = nil, status = nil, priority = nil)
+ def valid_symlinks(expected_output, run_level = nil, status = nil, priority = nil)
directory = []
if priority.is_a? Hash
priority.each do |level, o|
@@ -129,7 +129,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
context "when the service doesn't set a priority" do
it "creates symlink with status S" do
new_resource.run_action(:enable)
- valide_symlinks(["/etc/rc.d/rc2.d/Schefinittest"], 2, "S")
+ valid_symlinks(["/etc/rc.d/rc2.d/Schefinittest"], 2, "S")
end
end
@@ -140,7 +140,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
it "creates a symlink with status S and a priority" do
new_resource.run_action(:enable)
- valide_symlinks(["/etc/rc.d/rc2.d/S75chefinittest"], 2, "S", 75)
+ valid_symlinks(["/etc/rc.d/rc2.d/S75chefinittest"], 2, "S", 75)
end
end
@@ -152,7 +152,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
it "create symlink with status start (S) or stop (K) and a priority " do
new_resource.run_action(:enable)
- valide_symlinks(["/etc/rc.d/rc2.d/S20chefinittest", "/etc/rc.d/rc3.d/K10chefinittest"], 2, "S", new_resource.priority)
+ valid_symlinks(["/etc/rc.d/rc2.d/S20chefinittest", "/etc/rc.d/rc3.d/K10chefinittest"], 2, "S", new_resource.priority)
end
end
end
@@ -170,7 +170,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
it "creates symlink with status K" do
new_resource.run_action(:disable)
- valide_symlinks(["/etc/rc.d/rc2.d/Kchefinittest"], 2, "K")
+ valid_symlinks(["/etc/rc.d/rc2.d/Kchefinittest"], 2, "K")
end
end
@@ -186,7 +186,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
it "creates a symlink with status K and a priority" do
new_resource.run_action(:disable)
- valide_symlinks(["/etc/rc.d/rc2.d/K25chefinittest"], 2, "K", 25)
+ valid_symlinks(["/etc/rc.d/rc2.d/K25chefinittest"], 2, "K", 25)
end
end
@@ -203,7 +203,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
it "create symlink with status stop (K) and a priority " do
new_resource.run_action(:disable)
- valide_symlinks(["/etc/rc.d/rc2.d/K80chefinittest"], 2, "K", 80)
+ valid_symlinks(["/etc/rc.d/rc2.d/K80chefinittest"], 2, "K", 80)
end
end
end
diff --git a/spec/functional/resource/apt_package_spec.rb b/spec/functional/resource/apt_package_spec.rb
index 9f10e27731..8e888ce54a 100644
--- a/spec/functional/resource/apt_package_spec.rb
+++ b/spec/functional/resource/apt_package_spec.rb
@@ -186,7 +186,7 @@ describe Chef::Resource::AptPackage, metadata do
it "raises a reasonable error for action :install" do
expect do
package_resource.run_action(:install)
- end.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+ end.to raise_error(Chef::Exceptions::Package)
end
end
diff --git a/spec/functional/resource/chocolatey_package_spec.rb b/spec/functional/resource/chocolatey_package_spec.rb
index e55c1a453c..b5f4c07831 100644
--- a/spec/functional/resource/chocolatey_package_spec.rb
+++ b/spec/functional/resource/chocolatey_package_spec.rb
@@ -41,6 +41,19 @@ describe Chef::Resource::ChocolateyPackage, :windows_only, :choco_installed do
provider
end
+ # This bit of magic ensures that we pass a mixed-case Path var in the env to chocolatey and not PATH
+ # (both ENV["PATH"] and ENV["Path"] are the same thing in ruby-on-windows, and the first created key
+ # is the one that is actually passed to a subprocess, and choco demands it be Path)
+ #
+ # This is not a no-op.
+ #
+ # I don't know how to tell what state we were in to begin with, so we cannot restore. Nothing else
+ # seems to care.
+ #
+ before(:all) do
+ ENV["Path"] = ENV.delete("Path")
+ end
+
context "installing a package" do
after { remove_package }
diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb
index fa53eb08a1..e247dc53cd 100644
--- a/spec/functional/resource/cron_spec.rb
+++ b/spec/functional/resource/cron_spec.rb
@@ -19,7 +19,7 @@
require "spec_helper"
require "chef/mixin/shell_out"
-describe Chef::Resource::Cron, :requires_root, :unix_only do
+describe Chef::Resource::Cron, :requires_root, :unix_only, :not_macos_gte_11 do
include Chef::Mixin::ShellOut
diff --git a/spec/functional/resource/dnf_package_spec.rb b/spec/functional/resource/dnf_package_spec.rb
index eef0d6296b..9e03db5123 100644
--- a/spec/functional/resource/dnf_package_spec.rb
+++ b/spec/functional/resource/dnf_package_spec.rb
@@ -18,9 +18,10 @@
require "spec_helper"
require "chef/mixin/shell_out"
-# run this test only for following platforms.
-exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf"))
+# test on any fedora-ish platform with dnf
+exclude_test = !(%w{rhel amazon fedora}.include?(OHAI_SYSTEM[:platform_family]) && File.exist?("/usr/bin/dnf"))
describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
+ include RecipeDSLHelper
include Chef::Mixin::ShellOut
# NOTE: every single test here either needs to explicitly call flush_cache or needs to explicitly
@@ -30,7 +31,7 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
def flush_cache
# needed on at least fc23/fc24 sometimes to deal with the dnf cache getting out of sync with the rpm db
FileUtils.rm_f "/var/cache/dnf/@System.solv"
- Chef::Resource::DnfPackage.new("shouldnt-matter", run_context).run_action(:flush_cache)
+ Chef::Provider::Package::Dnf::PythonHelper.instance.reap
end
def preinstall(*rpms)
@@ -40,11 +41,17 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
flush_cache
end
+ def expect_matching_installed_version(version)
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(version)
+ end
+
before(:all) do
shell_out!("dnf -y install dnf-plugins-core")
end
before(:each) do
+ # force errors to fail and not retry
+ ENV["DNF_HELPER_NO_RETRIES"] = "true"
File.open("/etc/yum.repos.d/chef-dnf-localtesting.repo", "w+") do |f|
f.write <<~EOF
[chef-dnf-localtesting]
@@ -54,6 +61,7 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
gpgcheck=0
EOF
end
+ # ensure we don't have any stray chef_rpms installed
shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e")
# next line is useful cleanup if you happen to have been testing both yum + dnf func tests on the same box and
# have some yum garbage left around
@@ -65,132 +73,291 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
end
- let(:run_context) do
- Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
- end
-
- let(:package_name) { "chef_rpm" }
- let(:dnf_package) { Chef::Resource::DnfPackage.new(package_name, run_context) }
+ let(:default_options) { "--nogpgcheck --disablerepo=* --enablerepo=chef-dnf-localtesting" }
def pkg_arch
- ohai[:kernel][:machine]
+ OHAI_SYSTEM[:kernel][:machine]
end
describe ":install" do
context "vanilla use case" do
- let(:package_name) { "chef_rpm" }
-
it "installs if the package is not installed" do
flush_cache
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not install if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "does not install twice" do
flush_cache
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not install if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "does not install if the i686 package is installed", :intel_64bit do
skip "FIXME: do nothing, or install the #{pkg_arch} version?"
preinstall("chef_rpm-1.10-1.i686.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
end
it "does not install if the prior version i686 package is installed", :intel_64bit do
skip "FIXME: do nothing, or install the #{pkg_arch} version?"
preinstall("chef_rpm-1.2-1.i686.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$")
end
end
+ context "expanded idempotency checks with version variants" do
+ %w{1.10 1* 1.10-1 1*-1 1.10-* 1*-* 0:1.10 0:1* 0:1.10-1 0:1*-1 *:1.10-* *:1*-*}.each do |vstring|
+ it "installs the rpm when #{vstring} is in the package_name" do
+ flush_cache
+ dnf_package "chef_rpm-#{vstring}" do
+ options default_options
+ action :install
+ end.should_be_updated
+ end
+
+ it "is idempotent when #{vstring} is in the package_name" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm-#{vstring}" do
+ options default_options
+ action :install
+ end.should_not_be_updated
+ end
+
+ it "installs the rpm when #{vstring} is in the version property" do
+ flush_cache
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :install
+ end.should_be_updated
+ end
+
+ it "is idempotent when #{vstring} is in the version property" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :install
+ end.should_not_be_updated
+ end
+
+ it "upgrades the rpm when #{vstring} is in the package_name" do
+ flush_cache
+ dnf_package "chef_rpm-#{vstring}" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ end
+
+ it "is idempotent when #{vstring} is in the package_name" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm-#{vstring}" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "upgrades the rpm when #{vstring} is in the version property" do
+ flush_cache
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :upgrade
+ end.should_be_updated
+ end
+
+ it "is idempotent when #{vstring} is in the version property" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :upgrade
+ end.should_not_be_updated
+ end
+ end
+
+ %w{1.2 1* 1.2-1 1*-1 1.2-* 1*-* 0:1.2 0:1* 0:1.2-1 0:1*-1 *:1.2-* *:1*-*}.each do |vstring|
+ it "is idempotent when #{vstring} is in the version property and there is a candidate version" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :install
+ end.should_not_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ end
+ end
+
+ %w{1.2 1.2-1 1.2-* 0:1.2 0:1.2-1 *:1.2-*}.each do |vstring|
+ it "is idempotent when #{vstring} is in the version property on upgrade and it doesn't match the candidate version" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :upgrade
+ end.should_not_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ end
+ end
+
+ %w{1* 1*-1 1*-* 0:1* 0:1*-1 *:1*-*}.each do |vstring|
+ it "upgrades when #{vstring} is in the version property on upgrade and it matches the candidate version" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version vstring
+ action :upgrade
+ end.should_not_be_updated
+ end
+ end
+ end
+
context "with versions or globs in the name" do
it "works with a version" do
flush_cache
- dnf_package.package_name("chef_rpm-1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "works with an older version" do
flush_cache
- dnf_package.package_name("chef_rpm-1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "works with an evra" do
flush_cache
- dnf_package.package_name("chef_rpm-0:1.2-1.#{pkg_arch}")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-0:1.2-1.#{pkg_arch}" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-0:1.2-1.#{pkg_arch}" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "works with version and release" do
flush_cache
- dnf_package.package_name("chef_rpm-1.2-1")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.2-1" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.2-1" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "works with a version glob" do
flush_cache
- dnf_package.package_name("chef_rpm-1*")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1*" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1*" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "works with a name glob + version glob" do
flush_cache
- dnf_package.package_name("chef_rp*-1*")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rp*-1*" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rp*-1*" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "upgrades when the installed version does not match the version string" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm-1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}")
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "downgrades when the installed version is higher than the package_name version" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm-1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
end
@@ -198,56 +365,92 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
context "with version property" do
it "matches the full version" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10"
+ action :install
+ end.should_not_be_updated
end
it "matches with a glob" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("1*")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1*"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1*"
+ action :install
+ end.should_not_be_updated
end
it "matches the vr" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("1.10-1")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_not_be_updated
end
it "matches the evr" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("0:1.10-1")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "0:1.10-1"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "0:1.10-1"
+ action :install
+ end.should_not_be_updated
end
it "matches with a vr glob", :rhel_gte_8 do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("1.10-1*")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1*"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1*"
+ action :install
+ end.should_not_be_updated
end
it "matches with an evr glob", :rhel_gte_8 do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.version("0:1.10-1*")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "0:1.10-1*"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "0:1.10-1*"
+ action :install
+ end.should_not_be_updated
end
end
@@ -255,233 +458,345 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
it "downgrades the package when allow_downgrade" do
flush_cache
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm")
- dnf_package.version("1.2-1")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2-1"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2-1"
+ action :install
+ end.should_not_be_updated
end
end
context "with arches", :intel_64bit do
it "installs with 64-bit arch in the name" do
flush_cache
- dnf_package.package_name("chef_rpm.#{pkg_arch}")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "installs with 32-bit arch in the name" do
flush_cache
- dnf_package.package_name("chef_rpm.i686")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "installs with 64-bit arch in the property" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.arch((pkg_arch).to_s)
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ arch pkg_arch
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ arch pkg_arch
+ action :install
+ end.should_not_be_updated
end
it "installs with 32-bit arch in the property" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.arch("i686")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ arch "i686"
+ action :install
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ dnf_package "chef_rpm" do
+ options default_options
+ arch "i686"
+ action :install
+ end.should_not_be_updated
+ end
+
+ it "installs when the 32-bit arch is in the name and the version is in the property" do
+ flush_cache
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_not_be_updated
+ end
+
+ it "installs when the 64-bit arch is in the name and the version is in the property" do
+ flush_cache
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ version "1.10-1"
+ action :install
+ end.should_not_be_updated
end
end
context "with constraints" do
it "with nothing installed, it installs the latest version" do
flush_cache
- dnf_package.package_name("chef_rpm >= 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm >= 1.2" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm >= 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "when it is met, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm >= 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm >= 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "when it is met, it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm >= 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm >= 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with nothing installed, it installs the latest version" do
flush_cache
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "when it is not met by an installed rpm, it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "with an equality constraint, when it is not met by an installed rpm, it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm = 1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm = 1.10" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm = 1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "with an equality constraint, when it is met by an installed rpm, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm = 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm = 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "when it is met by an installed rpm, it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "when there is no solution to the contraint" do
flush_cache
- dnf_package.package_name("chef_rpm > 2.0")
- expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ dnf_package "chef_rpm > 2.0"
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "when there is no solution to the contraint but an rpm is preinstalled" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm > 2.0")
- expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ dnf_package "chef_rpm > 2.0"
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "with a less than constraint, when nothing is installed, it installs" do
flush_cache
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "with a less than constraint, when the install version matches, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a less than constraint, when the install version fails, it should downgrade" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
end
context "with source arguments" do
it "raises an exception when the package does not exist" do
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm")
- expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm"
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "does not raise a hard exception in why-run mode when the package does not exist" do
Chef::Config[:why_run] = true
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm")
- dnf_package.run_action(:install)
- expect { dnf_package.run_action(:install) }.not_to raise_error
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm" do
+ options default_options
+ action :install
+ end
end
it "installs the package when using the source argument" do
flush_cache
- dnf_package.name "something"
- dnf_package.package_name "somethingelse"
- dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "something" do
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ options default_options
+ package_name "somethingelse"
+ name "something"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "something" do
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ options default_options
+ package_name "somethingelse"
+ name "something"
+ action :install
+ end.should_not_be_updated
end
it "installs the package when the name is a path to a file" do
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "downgrade on a local file with allow_downgrade true works" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.version "1.2-1"
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ version "1.2-1"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ version "1.2-1"
+ action :install
+ end.should_not_be_updated
end
it "does not downgrade the package with :install" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not upgrade the package with :install" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed and there is a version string" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.version "1.2-1"
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
- end
-
- it "is idempotent when the package is already installed and there is a version string with arch" do
- preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.version "1.2-1.#{pkg_arch}"
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ version "1.2-1"
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
@@ -490,124 +805,183 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :install
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with a local source" do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :install
+ end.should_not_be_updated
end
end
context "multipackage with arches", :intel_64bit do
it "installs two rpms" do
flush_cache
- dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "does nothing if both are installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
flush_cache
- dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "installs the second rpm if the first is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
it "installs the first rpm if the second is installed" do
preinstall("chef_rpm-1.10-1.i686.rpm")
- dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs two rpms with multi-arch" do
flush_cache
- dnf_package.package_name(%w{chef_rpm chef_rpm} )
- dnf_package.arch([pkg_arch, "i686"])
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ action :install
+ end.should_not_be_updated
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs the second rpm if the first is installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name(%w{chef_rpm chef_rpm} )
- dnf_package.arch([pkg_arch, "i686"])
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs the first rpm if the second is installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name(%w{chef_rpm chef_rpm} )
- dnf_package.arch([pkg_arch, "i686"])
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ action :install
+ end.should_not_be_updated
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "does nothing if both are installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
- dnf_package.package_name(%w{chef_rpm chef_rpm} )
- dnf_package.arch([pkg_arch, "i686"])
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ action :install
+ end.should_not_be_updated
end
end
context "repo controls" do
it "should fail with the repo disabled" do
flush_cache
- dnf_package.options("--disablerepo=chef-dnf-localtesting")
- expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=chef-dnf-localtesting"
+ action :install
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "should work with disablerepo first" do
flush_cache
- dnf_package.options(["--disablerepo=*", "--enablerepo=chef-dnf-localtesting"])
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options ["--nogpgcheck", "--disablerepo=*", "--enablerepo=chef-dnf-localtesting"]
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options ["--nogpgcheck", "--disablerepo=*", "--enablerepo=chef-dnf-localtesting"]
+ action :install
+ end.should_not_be_updated
end
it "should work to enable a disabled repo" do
shell_out!("dnf config-manager --set-disabled chef-dnf-localtesting")
flush_cache
- expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck"
+ action :install
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
flush_cache
- dnf_package.options("--enablerepo=chef-dnf-localtesting")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --enablerepo=chef-dnf-localtesting"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --enablerepo=chef-dnf-localtesting"
+ action :install
+ end.should_not_be_updated
end
it "when an idempotent install action is run, does not leave repos disabled" do
@@ -617,17 +991,22 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
# accomplishes both those goals (it would be easier if we had other rpms in this repo, but with
# one rpm we need to do this).
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.options("--disablerepo=chef-dnf-localtesting")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=chef-dnf-localtesting"
+ action :upgrade
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
# now we're still using the same cache in the dnf_helper.py cache and we test to see if the
# repo that we temporarily disabled is enabled on this pass.
- dnf_package.package_name("chef_rpm-1.10-1.#{pkg_arch}")
- dnf_package.options(nil)
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.10-1.#{pkg_arch}" do
+ options "--nogpgcheck"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.10-1.#{pkg_arch}" do
+ options "--nogpgcheck"
+ action :install
+ end.should_not_be_updated
end
end
end
@@ -636,64 +1015,102 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
context "downgrades" do
it "just work with DNF" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.version("1.2")
- dnf_package.run_action(:install)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2"
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2"
+ action :install
+ end.should_not_be_updated
end
it "throws a deprecation warning with allow_downgrade" do
Chef::Config[:treat_deprecation_warnings_as_errors] = false
- expect(Chef).to receive(:deprecated).with(:dnf_package_allow_downgrade, /^the allow_downgrade property on the dnf_package provider is not used/)
+ expect(Chef).to receive(:deprecated).at_least(:once).with(:dnf_package_allow_downgrade, /^the allow_downgrade property on the dnf_package provider is not used/)
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.version("1.2")
- dnf_package.run_action(:install)
- dnf_package.allow_downgrade true
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2"
+ allow_downgrade true
+ action :install
+ end.should_be_updated
expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2"
+ allow_downgrade true
+ action :install
+ end.should_not_be_updated
end
end
context "with source arguments" do
it "installs the package when using the source argument" do
flush_cache
- dnf_package.name "something"
- dnf_package.package_name "somethingelse"
- dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "something" do
+ options default_options
+ package_name "somethingelse"
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "something" do
+ options default_options
+ package_name "somethingelse"
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ action :upgrade
+ end.should_not_be_updated
end
it "installs the package when the name is a path to a file" do
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "downgrades the package when allow_downgrade is true" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "upgrades the package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "is idempotent when the package is already installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
@@ -702,201 +1119,382 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :upgrade
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with a local source" do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
flush_cache
- dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
end
context "version pinning" do
- it "with an equality pin in the name it upgrades a prior package" do
+ it "with a full version pin it installs a later package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm-1.10")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1"
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10-1"
+ action :upgrade
+ end.should_not_be_updated
end
- it "with a prco equality pin in the name it upgrades a prior package" do
+ it "with a full version pin in the name it downgrades the package" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2-1"
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.2-1"
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a partial (no release) version pin it installs a later package" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10"
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version "1.10"
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a partial (no release) version pin in the name it downgrades the package" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm" do
+ options default_options
+ version("1.2")
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm" do
+ options default_options
+ version("1.2")
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a full version pin it installs a later package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm = 1.10")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.10-1" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.10-1" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
- it "with an equality pin in the name it downgrades a later package" do
+ it "with a full version pin in the name it downgrades the package" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm-1.2")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm-1.2-1" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.2-1" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a partial (no release) version pin it installs a later package" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.10" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a partial (no release) version pin in the name it downgrades the package" do
+ preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm-1.2" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ end
+
+ it "with a prco equality pin in the name it upgrades a prior package" do
+ preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ dnf_package "chef_rpm = 1.10" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm = 1.10" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "with a prco equality pin in the name it downgrades a later package" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm = 1.2")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm = 1.2" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm = 1.2" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "with a > pin in the name and no rpm installed it installs" do
flush_cache
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "with a < pin in the name and no rpm installed it installs" do
flush_cache
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "with a > pin in the name and matching rpm installed it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with a < pin in the name and no rpm installed it installs" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a > pin in the name and non-matching rpm installed it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm > 1.2")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm > 1.2" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
it "with a < pin in the name and non-matching rpm installed it downgrades" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.package_name("chef_rpm < 1.10")
- dnf_package.run_action(:upgrade)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :upgrade
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ dnf_package "chef_rpm < 1.10" do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
end
end
end
describe ":remove" do
context "vanilla use case" do
- let(:package_name) { "chef_rpm" }
it "does nothing if the package is not installed" do
flush_cache
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
end
it "removes the package if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
end
it "does not remove the package twice" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
end
it "removes the package if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
it "removes the package if the i686 package is installed", :intel_64bit do
skip "FIXME: should this be fixed or is the current behavior correct?"
preinstall("chef_rpm-1.10-1.i686.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
it "removes the package if the prior version i686 package is installed", :intel_64bit do
skip "FIXME: should this be fixed or is the current behavior correct?"
preinstall("chef_rpm-1.2-1.i686.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
end
context "with 64-bit arch", :intel_64bit do
- let(:package_name) { "chef_rpm.#{pkg_arch}" }
it "does nothing if the package is not installed" do
flush_cache
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
end
it "removes the package if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
it "removes the package if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
it "does nothing if the i686 package is installed" do
preinstall("chef_rpm-1.10-1.i686.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
end
it "does nothing if the prior version i686 package is installed" do
preinstall("chef_rpm-1.2-1.i686.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$")
end
end
context "with 32-bit arch", :intel_64bit do
- let(:package_name) { "chef_rpm.i686" }
it "removes only the 32-bit arch if both are installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ dnf_package "chef_rpm.i686" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
end
end
@@ -904,9 +1502,15 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- dnf_package.run_action(:remove)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :remove
+ end.should_be_updated
expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ dnf_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=*"
+ action :remove
+ end.should_not_be_updated
end
end
end
@@ -922,53 +1526,75 @@ describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do
it "locks an rpm" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.run_action(:lock)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :lock
+ end.should_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :lock
+ end.should_not_be_updated
end
it "does not lock if its already locked" do
flush_cache
shell_out!("dnf versionlock add chef_rpm")
- dnf_package.package_name("chef_rpm")
- dnf_package.run_action(:lock)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :lock
+ end.should_not_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:")
end
it "unlocks an rpm" do
flush_cache
shell_out!("dnf versionlock add chef_rpm")
- dnf_package.package_name("chef_rpm")
- dnf_package.run_action(:unlock)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm" do
+ options default_options
+ action :unlock
+ end.should_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:")
+ dnf_package "chef_rpm" do
+ options default_options
+ action :unlock
+ end.should_not_be_updated
end
it "does not unlock an already locked rpm" do
flush_cache
- dnf_package.package_name("chef_rpm")
- dnf_package.run_action(:unlock)
- expect(dnf_package.updated_by_last_action?).to be false
+ dnf_package "chef_rpm" do
+ options default_options
+ action :unlock
+ end.should_not_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:")
end
it "check that we can lock based on provides" do
flush_cache
- dnf_package.package_name("chef_rpm_provides")
- dnf_package.run_action(:lock)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm_provides" do
+ options default_options
+ action :lock
+ end.should_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:")
+ dnf_package "chef_rpm_provides" do
+ options default_options
+ action :lock
+ end.should_not_be_updated
end
it "check that we can unlock based on provides" do
flush_cache
shell_out!("dnf versionlock add chef_rpm")
- dnf_package.package_name("chef_rpm_provides")
- dnf_package.run_action(:unlock)
- expect(dnf_package.updated_by_last_action?).to be true
+ dnf_package "chef_rpm_provides" do
+ options default_options
+ action :unlock
+ end.should_be_updated
expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:")
+ dnf_package "chef_rpm_provides" do
+ options default_options
+ action :unlock
+ end.should_not_be_updated
end
end
end
diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb
index d0baeae7a9..b22599266b 100644
--- a/spec/functional/resource/dsc_script_spec.rb
+++ b/spec/functional/resource/dsc_script_spec.rb
@@ -21,7 +21,7 @@ require "chef/mixin/powershell_exec"
require "chef/mixin/windows_architecture_helper"
require "support/shared/integration/integration_helper"
-describe Chef::Resource::DscScript, :windows_powershell_dsc_only, :windows64_only do
+describe Chef::Resource::DscScript, :windows_powershell_dsc_only, :ruby64_only do
include Chef::Mixin::WindowsArchitectureHelper
include Chef::Mixin::PowershellExec
before(:all) do
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
index a682e9c0c7..279f8ac8d4 100644
--- a/spec/functional/resource/group_spec.rb
+++ b/spec/functional/resource/group_spec.rb
@@ -72,7 +72,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows do
end
def windows_domain_user?(user_name)
- domain, user = user_name.split('\\')
+ domain, user = user_name.split("\\")
if user && domain != "."
computer_name = ENV["computername"]
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
index c1de3bf99d..734897aaa4 100644
--- a/spec/functional/resource/link_spec.rb
+++ b/spec/functional/resource/link_spec.rb
@@ -84,7 +84,7 @@ describe Chef::Resource::Link do
end
def canonicalize(path)
- windows? ? path.tr("/", '\\') : path
+ windows? ? path.tr("/", "\\") : path
end
def symlink(a, b)
diff --git a/spec/functional/resource/ohai_spec.rb b/spec/functional/resource/ohai_spec.rb
index 836a4f6da3..3d6caca088 100644
--- a/spec/functional/resource/ohai_spec.rb
+++ b/spec/functional/resource/ohai_spec.rb
@@ -19,10 +19,6 @@
require "spec_helper"
describe Chef::Resource::Ohai do
- let(:ohai) do
- OHAI_SYSTEM
- end
-
let(:node) { Chef::Node.new }
let(:run_context) do
@@ -34,13 +30,9 @@ describe Chef::Resource::Ohai do
shared_examples_for "reloaded :uptime" do
it "should reload :uptime" do
- initial_uptime = ohai[:uptime]
-
- # Sleep for a second so the uptime gets updated.
- sleep 1
-
+ expect(node[:uptime_seconds]).to be nil
ohai_resource.run_action(:reload)
- expect(node[:uptime]).not_to eq(initial_uptime)
+ expect(Integer(node[:uptime_seconds])).to be_an(Integer)
end
end
diff --git a/spec/functional/resource/registry_spec.rb b/spec/functional/resource/registry_spec.rb
index aa7e12b3cb..f3ad946f0e 100644
--- a/spec/functional/resource/registry_spec.rb
+++ b/spec/functional/resource/registry_spec.rb
@@ -127,12 +127,12 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
@new_resource.cookbook_version(@cookbook_version)
end
- after (:all) do
+ after(:all) do
clean_registry
end
context "when action is create" do
- before (:all) do
+ before(:all) do
reset_registry
end
it "creates registry key, value if the key is missing" do
@@ -290,7 +290,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
end
context "while running in whyrun mode" do
- before (:each) do
+ before(:each) do
Chef::Config[:why_run] = true
end
@@ -342,7 +342,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
end
context "when action is create_if_missing" do
- before (:all) do
+ before(:all) do
reset_registry
end
@@ -454,7 +454,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
end
context "while running in whyrun mode" do
- before (:each) do
+ before(:each) do
Chef::Config[:why_run] = true
end
@@ -576,7 +576,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
end
context "while running in whyrun mode" do
- before (:each) do
+ before(:each) do
Chef::Config[:why_run] = true
end
it "does nothing if the action is delete" do
@@ -591,7 +591,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
end
context "when the action is delete_key" do
- before (:all) do
+ before(:all) do
reset_registry
create_deletable_keys
end
@@ -653,7 +653,7 @@ describe Chef::Resource::RegistryKey, :windows_only, broken: true do
expect(@report["total_res_count"]).to eq("1")
end
context "while running in whyrun mode" do
- before (:each) do
+ before(:each) do
Chef::Config[:why_run] = true
end
diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb
index 09e4fdccb4..3e929d22f7 100644
--- a/spec/functional/resource/remote_file_spec.rb
+++ b/spec/functional/resource/remote_file_spec.rb
@@ -155,7 +155,7 @@ describe Chef::Resource::RemoteFile do
before do
shell_out("net.exe share #{smb_share_name} /delete")
File.write(smb_file_local_path, smb_file_content )
- shell_out!("net.exe share #{smb_share_name}=\"#{smb_share_root_directory.tr("/", '\\')}\" /grant:\"authenticated users\",read")
+ shell_out!("net.exe share #{smb_share_name}=\"#{smb_share_root_directory.tr("/", "\\")}\" /grant:\"authenticated users\",read")
end
after do
diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb
deleted file mode 100644
index 50da812b0f..0000000000
--- a/spec/functional/resource/user/dscl_spec.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/mixin/shell_out"
-
-metadata = {
- macos_1013: true,
- requires_root: true,
-}
-
-describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do
- include Chef::Mixin::ShellOut
-
- def clean_user
- shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'")
- rescue Mixlib::ShellOut::ShellCommandFailed
- # Raised when the user is already cleaned
- end
-
- def user_should_exist
- expect(shell_out("/usr/bin/dscl . -ls /Users").stdout).to include username
- end
-
- def check_password(pass)
- # In order to test the password we use dscl passwd command since
- # that's the only command that gets the user password from CLI.
- expect(shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus).to eq(0)
- # Now reset the password back
- expect(shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus).to eq(0)
- end
-
- let(:node) do
- n = Chef::Node.new
- n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
- n
- end
-
- let(:events) do
- Chef::EventDispatch::Dispatcher.new
- end
-
- let(:run_context) do
- Chef::RunContext.new(node, {}, events)
- end
-
- let(:username) do
- "greatchef"
- end
-
- let(:uid) { nil }
- let(:gid) { 20 }
- let(:home) { nil }
- let(:manage_home) { false }
- let(:password) { "XXXYYYZZZ" }
- let(:comment) { "Great Chef" }
- let(:shell) { "/bin/bash" }
- let(:salt) { nil }
- let(:iterations) { nil }
-
- let(:user_resource) do
- r = Chef::Resource::User::DsclUser.new("TEST USER RESOURCE", run_context)
- r.username(username)
- r.uid(uid)
- r.gid(gid)
- r.home(home)
- r.shell(shell)
- r.comment(comment)
- r.manage_home(manage_home)
- r.password(password)
- r.salt(salt)
- r.iterations(iterations)
- r
- end
-
- before do
- clean_user
- end
-
- after(:each) do
- clean_user
- end
-
- describe "action :create" do
- it "should create the user" do
- user_resource.run_action(:create)
- user_should_exist
- check_password(password)
- end
- end
-
- describe "when user exists" do
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
- user_should_exist
- end
-
- describe "when password is updated" do
- it "should update the password of the user" do
- user_resource.password("mykitchen")
- user_resource.run_action(:create)
- check_password("mykitchen")
- end
- end
- end
-
- describe "when password is being set via shadow hash" do
- let(:password) do
- "c734b6e4787c3727bb35e29fdd92b97c\
-1de12df509577a045728255ec7c6c5f5\
-c18efa05ed02b682ffa7ebc05119900e\
-b1d4880833aa7a190afc13e2bf0936b8\
-20123e8c98f0f9bcac2a629d9163caac\
-9464a8c234f3919082400b4f939bb77b\
-c5adbbac718b7eb99463a7b679571e0f\
-1c9fef2ef08d0b9e9c2bcf644eed2ffc"
- end
-
- let(:iterations) { 25000 }
- let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" }
-
- it "action :create should create the user" do
- user_resource.run_action(:create)
- user_should_exist
- check_password("soawesome")
- end
-
- describe "when user exists" do
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
- user_should_exist
- end
-
- describe "when password is updated" do
- it "should update the password of the user" do
- user_resource.password("mykitchen")
- user_resource.run_action(:create)
- check_password("mykitchen")
- end
- end
- end
- end
-
- describe "when a user is member of some groups" do
- let(:groups) { %w{staff operator} }
-
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
-
- groups.each do |group|
- shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}")
- end
- end
-
- after do
- groups.each do |group|
- # Do not raise an error when user is correctly removed
- shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}")
- end
- end
-
- it ":remove action removes the user from the groups and deletes the user" do
- user_resource.run_action(:remove)
- groups.each do |group|
- # Do not raise an error when group is empty
- expect(shell_out("dscl . read /Groups/staff GroupMembership").stdout).not_to include(group)
- end
- end
- end
-
-end
diff --git a/spec/functional/resource/user/mac_user_spec.rb b/spec/functional/resource/user/mac_user_spec.rb
index dabc303afb..32701da644 100644
--- a/spec/functional/resource/user/mac_user_spec.rb
+++ b/spec/functional/resource/user/mac_user_spec.rb
@@ -19,9 +19,9 @@ require "spec_helper"
require "chef/mixin/shell_out"
metadata = {
- macos_gte_1014: true,
requires_root: true,
-}
+ macos_only: true,
+ }
describe "Chef::Resource::User with Chef::Provider::User::MacUser provider", metadata do
include Chef::Mixin::ShellOut
diff --git a/spec/functional/resource/windows_certificate_spec.rb b/spec/functional/resource/windows_certificate_spec.rb
index 20d444dd59..df2d1cbec8 100644
--- a/spec/functional/resource/windows_certificate_spec.rb
+++ b/spec/functional/resource/windows_certificate_spec.rb
@@ -19,478 +19,355 @@ require "spec_helper"
require "chef/mixin/powershell_exec"
require "chef/resource/windows_certificate"
-module WindowsCertificateHelper
+describe Chef::Resource::WindowsCertificate, :windows_only do
include Chef::Mixin::PowershellExec
- def create_store(store)
- path = "Cert:\\LocalMachine\\" + store
- command = <<~EOC
- New-Item -Path #{path}
+ def create_store(store_location: "LocalMachine", store_name: store)
+ powershell_exec <<~EOC
+ New-Item -Path Cert:\\#{store_location}\\#{store_name}
EOC
- powershell_exec(command)
end
- def cleanup(store)
- path = "Cert:\\LocalMachine\\" + store
- command = <<~EOC
- Remove-Item -Path #{path} -Recurse
+ def delete_store(store_location: "LocalMachine", store_name: store)
+ powershell_exec <<~EOC
+ Remove-Item -Path Cert:\\#{store_location}\\#{store_name} -Recurse
EOC
- powershell_exec(command)
end
- def no_of_certificates
- path = "Cert:\\LocalMachine\\" + store
- # Seems weird that we have to call dir twice right?
- # The powershell pki module cache the last dir in module session state
- # Issuing dir with a different arg (-Force) seems to refresh that state.
- command = <<~EOC
- dir #{path} -Force | Out-Null
- (dir #{path} | measure).Count
+ def certificate_count(store_location: "LocalMachine", store_name: store)
+ powershell_exec(<<~EOC).result.to_i
+ (Get-ChildItem -Force -Path Cert:\\#{store_location}\\#{store_name} | measure).Count
+ EOC
+ end
+
+ def list_certificates(store_location: "LocalMachine", store_name: store)
+ powershell_exec(<<~EOC)
+ Get-ChildItem -Force -Path Cert:\\#{store_location}\\#{store_name} -Recurse
+ EOC
+ end
+
+ def refresh_certstore(store_location: "LocalMachine")
+ powershell_exec(<<~EOC)
+ Get-ChildItem -Force -Path Cert:\\#{store_location} -Recurse
EOC
- powershell_exec(command).result.to_i
end
-end
-describe Chef::Resource::WindowsCertificate, :windows_only do
- include WindowsCertificateHelper
-
- let(:stdout) { StringIO.new }
- let(:username) { "ChefFunctionalTest" }
- let(:node) { Chef::Node.new }
- let(:events) { Chef::EventDispatch::Dispatcher.new }
- let(:run_context) { Chef::RunContext.new(node, {}, events) }
- let(:new_resource) { Chef::Resource::WindowsCertificate.new(username, run_context) }
let(:password) { "P@ssw0rd!" }
let(:store) { "Chef-Functional-Test" }
- let(:certificate_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "windows_certificates")) }
- let(:cer_path) { File.join(certificate_path, "test.cer") }
- let(:base64_path) { File.join(certificate_path, "base64_test.cer") }
- let(:pem_path) { File.join(certificate_path, "test.pem") }
- let(:p7b_path) { File.join(certificate_path, "test.p7b") }
- let(:pfx_path) { File.join(certificate_path, "test.pfx") }
- let(:out_path) { File.join(certificate_path, "testout.pem") }
+ let(:store_name) { "MY" }
+ let(:store_location) { "LocalMachine" }
+ let(:download_cert_url) { "https://testingchef.blob.core.windows.net/files/test.cer" }
+ let(:cert_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.cer") }
+ let(:pfx_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.pfx") }
+ let(:key_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.key") }
+ let(:cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.cer") }
+ let(:base64_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "base64_test.cer") }
+ let(:pem_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pem") }
+ let(:p7b_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.p7b") }
+ let(:pfx_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pfx") }
let(:tests_thumbprint) { "e45a4a7ff731e143cf20b8bfb9c7c4edd5238bb3" }
- let(:other_cer_path) { File.join(certificate_path, "othertest.cer") }
+ let(:other_cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "othertest.cer") }
let(:others_thumbprint) { "6eae1deefaf59daf1a97c9ceeff39c98b3da38cb" }
let(:p7b_thumbprint) { "f867e25b928061318ed2c36ca517681774b06260" }
let(:p7b_nested_thumbprint) { "dc395eae6be5b69951b8b6e1090cfc33df30d2cd" }
+ let(:resource) do
+ run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
+ Chef::Resource::WindowsCertificate.new("ChefFunctionalTest", run_context).tap do |r|
+ r.store_name = store
+ end
+ end
+
before do
- opts = { store_name: store }
- key = :store_name
- to_be = ["TRUSTEDPUBLISHER", "TrustedPublisher", "CLIENTAUTHISSUER",
- "REMOTE DESKTOP", "ROOT", "TRUSTEDDEVICES", "WEBHOSTING",
- "CA", "AUTHROOT", "TRUSTEDPEOPLE", "MY", "SMARTCARDROOT", "TRUST",
- "DISALLOWED"]
-
- # Byepassing the validation so that we may create a custom store
+ # Bypass validation of the store name so we can use a fake test store.
allow_any_instance_of(Chef::Mixin::ParamsValidate)
.to receive(:_pv_equal_to)
- .with(opts, key, to_be)
+ .with({ store_name: store }, :store_name, anything)
.and_return(true)
- # Creating a custom store for the testing
- create_store(store)
+ create_store
- allow(Chef::Log).to receive(:info) do |msg|
- stdout.puts(msg)
- end
end
- after { cleanup(store) }
+ after { delete_store }
- subject(:win_certificate) do
- new_resource.store_name = store
- new_resource
- end
+ describe "action: create" do
+ it "starts with no certificates" do
+ delete_store
+ create_store
+ foo = list_certificates
+ expect(certificate_count).to eq(0)
+ end
- it "Initially there are no certificates" do
- expect(no_of_certificates).to eq(0)
- end
+ it "can add a certificate idempotently" do
+ resource.source = cer_path
+ resource.run_action(:create)
- describe "action :create" do
- before do
- win_certificate.source = cer_path
- win_certificate.run_action(:create)
- end
+ expect(certificate_count).to eq(1)
+ expect(resource).to be_updated_by_last_action
- context "Adding a certificate" do
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
+ # Adding the cert again should have no effect
+ resource.run_action(:create)
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
- it "Converges while addition" do
- expect(win_certificate).to be_updated_by_last_action
- end
- end
+ # Adding the cert again with a different format should have no effect
+ resource.source = pem_path
+ resource.run_action(:create)
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
- context "Again adding the same certificate" do
- before do
- win_certificate.run_action(:create)
- end
- it "Does not imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while addition" do
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
- end
+ # Adding another cert should work correctly
+ resource.source = other_cer_path
+ resource.run_action(:create)
- context "Again adding the same certificate of other format" do
- before do
- win_certificate.source = pem_path
- win_certificate.run_action(:create)
- end
- it "Does not imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while addition" do
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ expect(certificate_count).to eq(2)
+ expect(resource).to be_updated_by_last_action
end
- context "Adding another certificate" do
- before do
- win_certificate.source = other_cer_path
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(2)
- end
- it "Converges while addition" do
- expect(no_of_certificates).to eq(2)
- expect(win_certificate).to be_updated_by_last_action
- end
+ it "can add a certificate from a valid url" do
+ resource.source = download_cert_url
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+ expect(resource).to be_updated_by_last_action
end
- end
- describe "Works for various formats" do
- context "Adds CER" do
- before do
- win_certificate.source = cer_path
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while adding again" do
- win_certificate.run_action(:create)
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "can add a base64 encoded certificate idempotently" do
+ resource.source = base64_path
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+
+ resource.run_action(:create)
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
end
- context "Adds Base64 Encoded CER" do
- before do
- win_certificate.source = base64_path
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while adding again" do
- win_certificate.run_action(:create)
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "can add a PEM certificate idempotently" do
+ resource.source = pem_path
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
end
- context "Adds PEM" do
- before do
- win_certificate.source = pem_path
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while adding again" do
- win_certificate.run_action(:create)
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "can add a P7B certificate idempotently" do
+ resource.source = p7b_path
+ resource.run_action(:create)
+
+ # A P7B cert includes nested certs
+ expect(certificate_count).to eq(3)
+
+ resource.run_action(:create)
+
+ expect(resource).not_to be_updated_by_last_action
+ expect(certificate_count).to eq(3)
end
- context "Adds P7B" do
- before do
- win_certificate.source = p7b_path
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).not_to eq(0)
- end
- it "Idempotent: Does not converge while adding again" do
- win_certificate.run_action(:create)
- expect(win_certificate).not_to be_updated_by_last_action
- end
- it "Nested certificates are also imported" do
- expect(no_of_certificates).to eq(3)
- end
+ it "can add a PFX certificate idempotently with a valid password" do
+ resource.source = pfx_path
+ resource.pfx_password = password
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+
+ resource.run_action(:create)
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
end
- context "Adds PFX" do
- context "With valid password" do
- before do
- win_certificate.source = pfx_path
- win_certificate.pfx_password = password
- win_certificate.run_action(:create)
- end
- it "Imports certificate into store" do
- expect(no_of_certificates).to eq(1)
- end
- it "Idempotent: Does not converge while adding again" do
- win_certificate.run_action(:create)
- expect(no_of_certificates).to eq(1)
- expect(win_certificate).not_to be_updated_by_last_action
- end
- end
+ it "raises an error when adding a PFX certificate with an invalid password" do
+ resource.source = pfx_path
+ resource.pfx_password = "Invalid password"
- context "With Invalid password" do
- before do
- win_certificate.source = pfx_path
- win_certificate.pfx_password = "Invalid password"
- end
- it "Raises an error" do
- expect { win_certificate.run_action(:create) }.to raise_error(OpenSSL::PKCS12::PKCS12Error)
- end
- end
+ expect { resource.run_action(:create) }.to raise_error(OpenSSL::PKCS12::PKCS12Error)
end
+ after { delete_store }
end
describe "action: verify" do
- context "When a certificate is not present" do
- before do
- win_certificate.source = tests_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is not present" do
- expect(no_of_certificates).to eq(0)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate not found")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ before do
+ create_store
+ end
+ it "fails with no certificates in the store" do
+ expect(Chef::Log).to receive(:info).with("Certificate not found")
+
+ resource.source = tests_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
- context "When a certificate is present" do
+ context "with a certificate in the store" do
before do
- win_certificate.source = cer_path
- win_certificate.run_action(:create)
+ resource.source = cer_path
+ resource.run_action(:create)
end
- context "For a valid thumbprint" do
- before do
- win_certificate.source = tests_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(1)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate is valid")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "succeeds with a valid thumbprint" do
+ expect(Chef::Log).to receive(:info).with("Certificate is valid")
+
+ resource.source = tests_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
- context "For an invalid thumbprint" do
- before do
- win_certificate.source = others_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(1)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate not found")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "fails with an invalid thumbprint" do
+ expect(Chef::Log).to receive(:info).with("Certificate not found")
+
+ resource.source = others_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
end
- context "When multiple certificates are present" do
+ context "with a nested certificate in the store" do
before do
- win_certificate.source = p7b_path
- win_certificate.run_action(:create)
+ resource.source = p7b_path
+ resource.run_action(:create)
end
- context "With main certificate's thumbprint" do
- before do
- win_certificate.source = p7b_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(3)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate is valid")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "succeeds with the main certificate's thumbprint" do
+ expect(Chef::Log).to receive(:info).with("Certificate is valid")
+
+ resource.source = p7b_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
- context "With nested certificate's thumbprint" do
- before do
- win_certificate.source = p7b_nested_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(3)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate is valid")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "succeeds with the nested certificate's thumbprint" do
+ expect(Chef::Log).to receive(:info).with("Certificate is valid")
+
+ resource.source = p7b_nested_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
- context "For an invalid thumbprint" do
- before do
- win_certificate.source = others_thumbprint
- win_certificate.run_action(:verify)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(3)
- end
- it "Displays correct message" do
- expect(stdout.string.strip).to eq("Certificate not found")
- end
- it "Does not converge while verifying" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "fails with an invalid thumbprint" do
+ expect(Chef::Log).to receive(:info).with("Certificate not found")
+
+ resource.source = others_thumbprint
+ resource.run_action(:verify)
+
+ expect(resource).not_to be_updated_by_last_action
end
end
end
describe "action: fetch" do
- context "When a certificate is not present" do
- before do
- win_certificate.source = tests_thumbprint
- win_certificate.run_action(:fetch)
+ context "with no certificate in the store" do
+ it "throws an error with no certificates in the store" do
+ expect(Chef::Log).not_to receive(:info)
+ resource.source = others_thumbprint
+ resource.output_path = cert_output_path
+ expect { resource.run_action :fetch }.to raise_error(ArgumentError)
end
- it "Initial check if certificate is not present" do
- expect(no_of_certificates).to eq(0)
+ end
+
+ context "with a certificate in the store" do
+ before do
+ resource.source = cer_path
+ resource.run_action(:create)
end
- it "Does not show any content" do
- expect(stdout.string.strip).to be_empty
+
+ it "succeeds with a valid thumbprint" do
+ resource.source = tests_thumbprint
+ local_output_path = ::File.join(Chef::Config[:file_cache_path], "test.pem")
+ resource.output_path = local_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(local_output_path)).to be_truthy
end
- it "Does not converge while fetching" do
- expect(win_certificate).not_to be_updated_by_last_action
+
+ it "fails with an invalid thumbprint" do
+ expect(Chef::Log).not_to receive(:info)
+
+ resource.source = others_thumbprint
+
+ Dir.mktmpdir do |dir|
+ path = File.join(dir, "test.pem")
+
+ resource.output_path = path
+ expect { resource.run_action :fetch }.to raise_error(ArgumentError)
+ end
+
end
end
- context "When a certificate is present" do
+ context "with a pfx/pkcs12 object in the store" do
before do
- win_certificate.source = cer_path
- win_certificate.run_action(:create)
+ create_store
+ refresh_certstore
+ resource.source = pfx_path
+ resource.pfx_password = password
+ resource.exportable = true
+ resource.run_action(:create)
end
- after do
- if File.exist?(out_path)
- File.delete(out_path)
- end
+ it "exports a PFX file with a valid thumbprint" do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ resource.output_path = pfx_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(pfx_output_path)).to be_truthy
end
- context "For a valid thumbprint" do
- before do
- win_certificate.source = tests_thumbprint
- win_certificate.cert_path = out_path
- win_certificate.run_action(:fetch)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(1)
- end
- it "Stores Certificate content at given path" do
- expect(File.exist?(out_path)).to be_truthy
- end
- it "Does not converge while fetching" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "exports a key file with a valid thumbprint" do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ resource.output_path = key_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(key_output_path)).to be_truthy
end
- context "For an invalid thumbprint" do
- before do
- win_certificate.source = others_thumbprint
- win_certificate.cert_path = out_path
- win_certificate.run_action(:fetch)
- end
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(1)
- end
- it "Does not show any content" do
- expect(stdout.string.strip).to be_empty
- end
- it "Does not store certificate content at given path" do
- expect(File.exist?(out_path)).to be_falsy
- end
- it "Does not converge while fetching" do
- expect(win_certificate).not_to be_updated_by_last_action
- end
+ it "throws an exception when output_path is not specified" do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ expect { resource.run_action :fetch }.to raise_error(::Chef::Exceptions::ResourceNotFound)
end
+
+ after { delete_store }
+
end
end
describe "action: delete" do
- context "When a certificate is not present" do
- before do
- win_certificate.source = tests_thumbprint
- win_certificate.run_action(:delete)
- end
- it "Initial check if certificate is not present" do
- expect(no_of_certificates).to eq(0)
- end
- it "Does not delete any certificate" do
- expect(stdout.string.strip).to be_empty
- end
+ it "throws an argument error when attempting to delete a certificate that doesn't exist" do
+ resource.source = tests_thumbprint
+ expect { resource.run_action :delete }.to raise_error(ArgumentError)
end
- context "When a certificate is present" do
- before do
- win_certificate.source = cer_path
- win_certificate.run_action(:create)
- end
- before { win_certificate.source = tests_thumbprint }
- it "Initial check if certificate is present" do
- expect(no_of_certificates).to eq(1)
- end
- it "Deletes the certificate" do
- win_certificate.run_action(:delete)
- expect(no_of_certificates).to eq(0)
- end
- it "Converges while deleting" do
- win_certificate.run_action(:delete)
- expect(win_certificate).to be_updated_by_last_action
- end
- it "Idempotent: Does not converge while deleting again" do
- win_certificate.run_action(:delete)
- win_certificate.run_action(:delete)
- expect(no_of_certificates).to eq(0)
- expect(win_certificate).not_to be_updated_by_last_action
- end
- it "Deletes the valid certificate" do
- # Add another certificate"
- win_certificate.source = other_cer_path
- win_certificate.run_action(:create)
- expect(no_of_certificates).to eq(2)
-
- # Delete previously added certificate
- win_certificate.source = tests_thumbprint
- win_certificate.run_action(:delete)
- expect(no_of_certificates).to eq(1)
-
- # Verify another certificate still exists
- win_certificate.source = others_thumbprint
- win_certificate.run_action(:verify)
- expect(stdout.string.strip).to eq("Certificate is valid")
- end
+ it "deletes an existing certificate while leaving other certificates alone" do
+ # Add two certs
+ resource.source = cer_path
+ resource.run_action(:create)
+
+ resource.source = other_cer_path
+ resource.run_action(:create)
+
+ # Delete the first cert added
+ resource.source = tests_thumbprint
+ resource.run_action(:delete)
+
+ expect(certificate_count).to eq(1)
+ expect(resource).to be_updated_by_last_action
+
+ expect { resource.run_action :delete }.to raise_error(ArgumentError)
+ expect(certificate_count).to eq(1)
+ expect(resource).not_to be_updated_by_last_action
+
+ # Verify second cert still exists
+ expect(Chef::Log).to receive(:info).with("Certificate is valid")
+ resource.source = others_thumbprint
+ resource.run_action(:verify)
end
end
end
diff --git a/spec/functional/resource/windows_env_spec.rb b/spec/functional/resource/windows_env_spec.rb
index bbcbf393e2..4d77ee34bd 100644
--- a/spec/functional/resource/windows_env_spec.rb
+++ b/spec/functional/resource/windows_env_spec.rb
@@ -38,7 +38,7 @@ describe Chef::Resource::WindowsEnv, :windows_only do
if environment_variables && environment_variables.length > 0
environment_variables.each do |env|
env_obj = env.wmi_ole_object
- return env_obj if env_obj.username.split('\\').last.casecmp(test_resource.user) == 0
+ return env_obj if env_obj.username.split("\\").last.casecmp(test_resource.user) == 0
end
end
nil
@@ -93,7 +93,7 @@ describe Chef::Resource::WindowsEnv, :windows_only do
test_resource.value(env_value1)
test_resource.user(env_user)
test_resource.run_action(:create)
- expect(env_obj.username.split('\\').last.upcase).to eq(env_user)
+ expect(env_obj.username.split("\\").last.upcase).to eq(env_user)
end
context "when env variable exist with same name" do
diff --git a/spec/functional/resource/windows_hostname_spec.rb b/spec/functional/resource/windows_hostname_spec.rb
new file mode 100644
index 0000000000..a0a58b2919
--- /dev/null
+++ b/spec/functional/resource/windows_hostname_spec.rb
@@ -0,0 +1,91 @@
+# Author: John McCrae (john.mccrae@progress.com)
+# Copyright:: Copyright (c) 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 "spec_helper"
+require "chef/mixin/powershell_exec"
+require "chef/resource/hostname"
+
+describe Chef::Resource::Hostname, :windows_only do
+ include Chef::Mixin::PowershellExec
+
+ def get_domain_status
+ powershell_exec!("(Get-WmiObject -Class Win32_ComputerSystem).PartofDomain").result
+ end
+
+ let(:new_hostname) { "New-Hostname" }
+ let(:local_domain_user) { "'mydomain\\Groucho'" }
+ let(:local_domain_password) { "'P@ssw0rd'" }
+ let(:local_windows_reboot) { false }
+ let(:domain_status) { get_domain_status }
+
+ let(:run_context) do
+ node = Chef::Node.new
+ node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version]
+ node.automatic["os"] = "windows"
+ node.automatic["platform"] = "windows"
+ node.automatic["platform_version"] = "6.1"
+ node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ end
+
+ subject do
+ new_resource = Chef::Resource::Hostname.new("oldhostname", run_context)
+ new_resource.hostname = "Grendel"
+ new_resource.windows_reboot = false
+ new_resource
+ end
+
+ describe "Changing machine names" do
+ context "The system can be renamed without a user or password when in a Workgroup" do
+ let(:hostname) { "Cucumber" }
+ it "does change" do
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+
+ describe "Mocking being joined to a domain" do
+ before do
+ allow(subject).to receive(:is_domain_joined?) { true }
+ end
+ context "the system gets renamed when in a domain" do
+ let(:hostname) { "Gherkin" }
+ it "does change the domain account" do
+ subject.windows_reboot true
+ subject.domain_user local_domain_user
+ subject.domain_password local_domain_password
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+ end
+
+ describe "testing for error handling" do
+ before do
+ allow(subject).to receive(:is_domain_joined?) { true }
+ end
+ context "when a node is renamed while in a domain" do
+ it "and fails because of missing credentials" do
+ subject.windows_reboot true
+ subject.domain_user local_domain_user
+ expect { subject.run_action(:set) }.to raise_error(RuntimeError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/windows_pagefile_spec.rb b/spec/functional/resource/windows_pagefile_spec.rb
new file mode 100644
index 0000000000..fd44c01973
--- /dev/null
+++ b/spec/functional/resource/windows_pagefile_spec.rb
@@ -0,0 +1,98 @@
+# Author: John McCrae (john.mccrae@progress.com)
+# Copyright:: Copyright (c) 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 "spec_helper"
+require "chef/mixin/powershell_exec"
+
+describe Chef::Resource::WindowsPagefile, :windows_only do
+ include Chef::Mixin::PowershellExec
+
+ let(:c_path) { "c:\\" }
+ let(:e_path) { "e:\pagefile.sys" }
+
+ let(:run_context) do
+ node = Chef::Node.new
+ node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version]
+ node.automatic["os"] = "windows"
+ node.automatic["platform"] = "windows"
+ node.automatic["platform_version"] = "6.1"
+ node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ end
+
+ subject do
+ new_resource = Chef::Resource::WindowsPagefile.new("pagefile", run_context)
+ new_resource
+ end
+
+ describe "Setting Up Pagefile Management" do
+ context "Disable Automatic Management" do
+ it "Disables Automatic Management" do
+ subject.path c_path
+ subject.automatic_managed false
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+
+ it "Enable Automatic Management " do
+ subject.path c_path
+ subject.automatic_managed true
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+ end
+
+ describe "Creating a new Pagefile" do
+ context "Create new pagefile" do
+ it "Creates a new pagefile on a different drive that doesn't exist" do
+ subject.path e_path
+ expect { subject.run_action(:set) }.to raise_error(RuntimeError)
+ end
+ end
+
+ context "Update a pagefile" do
+ it "Changes a pagefile to use custom sizes" do
+ subject.path c_path
+ subject.initial_size 20000
+ subject.maximum_size 80000
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+ end
+
+ describe "Deleting a Pagefile and Resetting to Automatically Managed" do
+ context "delete the pagefile on disk" do
+ it "deletes the pagefile located at the given path" do
+ subject.path c_path
+ subject.run_action(:delete)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+
+ context "Re-enable automatic management of pagefiles" do
+ it "Enable Automatic Management " do
+ subject.path c_path
+ subject.automatic_managed true
+ subject.run_action(:set)
+ expect(subject).to be_updated_by_last_action
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/windows_service_spec.rb b/spec/functional/resource/windows_service_spec.rb
deleted file mode 100644
index 4c0c3acb58..0000000000
--- a/spec/functional/resource/windows_service_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Author:: Chris Doherty (<cdoherty@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only do
-
- include_context "using Win32::Service"
-
- let(:username) { "service_spec_user" }
- let(:qualified_username) { "#{ENV["COMPUTERNAME"]}\\#{username}" }
- let(:password) { "1a2b3c4X!&narf" }
-
- let(:user_resource) do
- r = Chef::Resource::User::WindowsUser.new(username, run_context)
- r.username(username)
- r.password(password)
- r.comment("temp spec user")
- r
- end
-
- let(:global_service_file_path) do
- "#{ENV["WINDIR"]}\\temp\\#{File.basename(test_service[:service_file_path])}"
- end
-
- let(:service_params) do
-
- id = "#{$$}_#{rand(1000)}"
-
- test_service.merge( {
- run_as_user: qualified_username,
- run_as_password: password,
- service_name: "spec_service_#{id}",
- service_display_name: "windows_service spec #{id}}",
- service_description: "Test service for running the windows_service functional spec.",
- service_file_path: global_service_file_path,
- } )
- end
-
- let(:manager) do
- Chef::Application::WindowsServiceManager.new(service_params)
- end
-
- let(:service_resource) do
- r = Chef::Resource::WindowsService.new(service_params[:service_name], run_context)
- %i{run_as_user run_as_password}.each { |prop| r.send(prop, service_params[prop]) }
- r
- end
-
- let(:run_context) do
- Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
- end
-
- before do
- user_resource.run_action(:create)
-
- # the service executable has to be outside the current user's home
- # directory in order for the logon user to execute it.
- FileUtils.copy_file(test_service[:service_file_path], global_service_file_path)
-
- # if you don't make the file executable by the service user, you'll get
- # the not-very-helpful "service did not respond fast enough" error.
-
- # #mode may break in a post-Windows 8.1 release, and have to be replaced
- # with the rights stuff in the file resource.
- file = Chef::Resource::File.new(global_service_file_path, run_context)
- file.mode("0777")
-
- file.run_action(:create)
-
- manager.run(%w{--action install})
- end
-
- after do
- user_resource.run_action(:remove)
- manager.run(%w{--action uninstall})
- File.delete(global_service_file_path)
- end
-
- describe "logon as a service" do
- it "successfully runs a service as another user" do
- service_resource.run_action(:start)
- end
-
- it "grants the user the log on as service right" do
- service_resource.run_action(:start)
- expect(Chef::ReservedNames::Win32::Security.get_account_right(qualified_username)).to include("SeServiceLogonRight")
- end
- end
-end
diff --git a/spec/functional/resource/yum_package_spec.rb b/spec/functional/resource/yum_package_spec.rb
index 5f902cff17..d401651f70 100644
--- a/spec/functional/resource/yum_package_spec.rb
+++ b/spec/functional/resource/yum_package_spec.rb
@@ -19,8 +19,9 @@ require "spec_helper"
require "chef/mixin/shell_out"
# run this test only for following platforms.
-exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && !File.exist?("/usr/bin/dnf"))
+exclude_test = !(%w{rhel fedora amazon}.include?(OHAI_SYSTEM[:platform_family]) && !File.exist?("/usr/bin/dnf"))
describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
+ include RecipeDSLHelper
include Chef::Mixin::ShellOut
# NOTE: every single test here either needs to explicitly call flush_cache or needs to explicitly
@@ -38,11 +39,17 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
flush_cache
end
+ def expect_matching_installed_version(version)
+ expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(version)
+ end
+
before(:all) do
shell_out!("yum -y install yum-utils")
end
before(:each) do
+ # force errors to fail and not retry
+ ENV["YUM_HELPER_NO_RETRIES"] = "true"
File.open("/etc/yum.repos.d/chef-yum-localtesting.repo", "w+") do |f|
f.write <<~EOF
[chef-yum-localtesting]
@@ -52,6 +59,7 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
gpgcheck=0
EOF
end
+ # ensure we don't have any stray chef_rpms installed
shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e")
# next line is useful cleanup if you happen to have been testing both yum + dnf func tests on the same box and
# have some dnf garbage left around
@@ -63,137 +71,133 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
end
- let(:run_context) do
- Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
- end
-
- let(:package_name) { "chef_rpm" }
- let(:yum_package) do
- r = Chef::Resource::YumPackage.new(package_name, run_context)
- r.options("--nogpgcheck")
- r
- end
+ let(:default_options) { "--nogpgcheck --disablerepo=* --enablerepo=chef-yum-localtesting" }
def pkg_arch
- ohai[:kernel][:machine]
+ OHAI_SYSTEM[:kernel][:machine]
end
describe ":install" do
context "vanilla use case" do
- let(:package_name) { "chef_rpm" }
-
it "installs if the package is not installed" do
flush_cache
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not install if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not install twice" do
flush_cache
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not install if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "does not install if the i686 package is installed", :intel_64bit do
skip "FIXME: do nothing, or install the #{pkg_arch} version?"
preinstall("chef_rpm-1.10-1.i686.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.i686$")
end
it "does not install if the prior version i686 package is installed", :intel_64bit do
skip "FIXME: do nothing, or install the #{pkg_arch} version?"
preinstall("chef_rpm-1.2-1.i686.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$")
+ yum_package("chef_rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.i686$")
end
end
context "with versions or globs in the name" do
it "works with a version" do
flush_cache
- yum_package.package_name("chef_rpm-1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.10") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "works with an older version" do
flush_cache
- yum_package.package_name("chef_rpm-1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.2") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with an evra" do
flush_cache
- yum_package.package_name("chef_rpm-0:1.2-1.#{pkg_arch}")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm-0:1.2-1.#{pkg_arch}") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with version and release" do
flush_cache
- yum_package.package_name("chef_rpm-1.2-1")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.2-1") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with a version glob" do
flush_cache
- yum_package.package_name("chef_rpm-1*")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1*") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "works with a name glob + version glob" do
flush_cache
- yum_package.package_name("chef_rp*-1*")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rp*-1*") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "upgrades when the installed version does not match the version string" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm-1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}")
+ yum_package("chef_rpm-1.10") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}")
end
it "downgrades when the installed version is higher than the package_name version" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm-1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.2") do
+ allow_downgrade true
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
@@ -201,11 +205,11 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
context "with version property" do
it "matches the full version" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("1.10")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "matches with a glob" do
@@ -213,49 +217,49 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
# the name field rather than trying to use a name of "tcpdump" and a version of "4*".
pending "this does not work, is not easily supported by the underlying yum libraries, but does work in the new dnf_package provider"
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("1*")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("1*")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "matches the vr" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("1.10-1")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("1.10-1")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "matches the evr" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("0:1.10-1")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("0:1.10-1")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "matches with a vr glob" do
pending "doesn't work on command line either"
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("1.10-1*")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("1.10-1*")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "matches with an evr glob" do
pending "doesn't work on command line either"
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.version("0:1.10-1*")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ version("0:1.10-1*")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
end
@@ -263,249 +267,257 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
it "downgrades the package when allow_downgrade" do
flush_cache
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm")
- yum_package.allow_downgrade true
- yum_package.version("1.2-1")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ allow_downgrade true
+ version("1.2-1")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
context "with arches", :intel_64bit do
it "installs with 64-bit arch in the name" do
flush_cache
- yum_package.package_name("chef_rpm.#{pkg_arch}")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm.#{pkg_arch}") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "installs with 32-bit arch in the name" do
flush_cache
- yum_package.package_name("chef_rpm.i686")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ yum_package("chef_rpm.i686") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.i686$")
end
it "installs with 64-bit arch in the property" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.arch((pkg_arch).to_s)
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm") do
+ options default_options
+ arch((pkg_arch).to_s)
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "installs with 32-bit arch in the property" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.arch("i686")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ yum_package("chef_rpm") do
+ options default_options
+ arch("i686")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.i686$")
end
end
context "with constraints" do
it "with nothing installed, it installs the latest version" do
flush_cache
- yum_package.package_name("chef_rpm >= 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm >= 1.2") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "when it is met, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm >= 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm >= 1.2") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "when it is met, it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm >= 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm >= 1.2") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with nothing intalled, it installs the latest version" do
flush_cache
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "when it is not met by an installed rpm, it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with an equality constraint, when it is not met by an installed rpm, it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm = 1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm = 1.10") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with an equality constraint, when it is met by an installed rpm, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm = 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm = 1.2") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "when it is met by an installed rpm, it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
- it "when there is no solution to the contraint" do
+ it "when there is no solution to the constraint" do
flush_cache
- yum_package.package_name("chef_rpm > 2.0")
- expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ yum_package("chef_rpm > 2.0") do
+ options default_options
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
- it "when there is no solution to the contraint but an rpm is preinstalled" do
+ it "when there is no solution to the constraint but an rpm is preinstalled" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm > 2.0")
- expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ yum_package("chef_rpm > 2.0") do
+ options default_options
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "with a less than constraint, when nothing is installed, it installs" do
flush_cache
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ allow_downgrade true
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a less than constraint, when the install version matches, it does nothing" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ allow_downgrade true
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a less than constraint, when the install version fails, it should downgrade" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ allow_downgrade true
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
context "with source arguments" do
it "raises an exception when the package does not exist" do
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm")
- expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") do
+ options default_options
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "does not raise a hard exception in why-run mode when the package does not exist" do
Chef::Config[:why_run] = true
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm")
- yum_package.run_action(:install)
- expect { yum_package.run_action(:install) }.not_to raise_error
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") do
+ options default_options
+ end
end
it "installs the package when using the source argument" do
flush_cache
- yum_package.name "something"
- yum_package.package_name "somethingelse"
- yum_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package "something" do
+ package_name "somethingelse"
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "installs the package when the name is a path to a file" do
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "downgrade on a local file is ignored when allow_downgrade is false" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade false
- yum_package.version "1.2-1"
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ allow_downgrade false
+ version "1.2-1"
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "downgrade on a local file with allow_downgrade true works" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.version "1.2-1"
- yum_package.allow_downgrade true
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ version "1.2-1"
+ allow_downgrade true
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "does not downgrade the package with :install" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "does not upgrade the package with :install" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed and there is a version string" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.version "1.2-1"
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ version "1.2-1"
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed and there is a version string with arch" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.version "1.2-1.#{pkg_arch}"
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ version "1.2-1.#{pkg_arch}"
+ options default_options
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
@@ -513,124 +525,132 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package "chef_rpm" do
+ options "--nogpgcheck"
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with a local source" do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options "--nogpgcheck"
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
context "multipackage with arches", :intel_64bit do
it "installs two rpms" do
flush_cache
- yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
it "does nothing if both are installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
flush_cache
- yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
+ yum_package([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) do
+ options default_options
+ end.should_not_be_updated
end
it "installs the second rpm if the first is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
it "installs the first rpm if the second is installed" do
preinstall("chef_rpm-1.10-1.i686.rpm")
- yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] )
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package [ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] do
+ options default_options
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs two rpms with multi-arch" do
flush_cache
- yum_package.package_name(%w{chef_rpm chef_rpm} )
- yum_package.arch([pkg_arch, "i686"])
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs the second rpm if the first is installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name(%w{chef_rpm chef_rpm} )
- yum_package.arch([pkg_arch, "i686"])
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "installs the first rpm if the second is installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name(%w{chef_rpm chef_rpm} )
- yum_package.arch([pkg_arch, "i686"])
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/)
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/)
+ yum_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ end.should_be_updated
+ expect_matching_installed_version(/^chef_rpm-1.10-1.#{pkg_arch}$/)
+ expect_matching_installed_version(/^chef_rpm-1.10-1.i686$/)
end
# unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name
it "does nothing if both are installed (muti-arch)" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
- yum_package.package_name(%w{chef_rpm chef_rpm} )
- yum_package.arch([pkg_arch, "i686"])
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be false
+ yum_package %w{chef_rpm chef_rpm} do
+ options default_options
+ arch [pkg_arch, "i686"]
+ end.should_not_be_updated
end
end
context "repo controls" do
it "should fail with the repo disabled" do
flush_cache
- yum_package.options("--disablerepo=chef-yum-localtesting")
- expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ yum_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=chef-yum-localtesting"
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
end
it "should work with disablerepo first" do
flush_cache
- yum_package.options(["--disablerepo=*", "--enablerepo=chef-yum-localtesting"])
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=* --enablerepo=chef-yum-localtesting"
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "should work to enable a disabled repo" do
shell_out!("yum-config-manager --disable chef-yum-localtesting")
flush_cache
- expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
+ expect {
+ yum_package "chef_rpm" do
+ options "--nogpgcheck"
+ end
+ }.to raise_error(Chef::Exceptions::Package, /No candidate version available/)
flush_cache
- yum_package.options("--enablerepo=chef-yum-localtesting")
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package "chef_rpm" do
+ options "--nogpgcheck --enablerepo=chef-yum-localtesting"
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "when an idempotent install action is run, does not leave repos disabled" do
@@ -640,17 +660,17 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
# accomplishes both those goals (it would be easier if we had other rpms in this repo, but with
# one rpm we need to do this).
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.options("--disablerepo=chef-yum-localtesting")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package "chef_rpm" do
+ options "--nogpgcheck --disablerepo=chef-yum-localtesting"
+ action :upgrade
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
# now we're still using the same cache in the yum_helper.py cache and we test to see if the
# repo that we temporarily disabled is enabled on this pass.
- yum_package.package_name("chef_rpm-1.10-1.#{pkg_arch}")
- yum_package.options(nil)
- yum_package.run_action(:install)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.10-1.#{pkg_arch}") do
+ options "--nogpgcheck"
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
end
end
@@ -660,45 +680,49 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
context "with source arguments" do
it "installs the package when using the source argument" do
flush_cache
- yum_package.name "something"
- yum_package.package_name "somethingelse"
- yum_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package "something" do
+ package_name "somethingelse"
+ source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "installs the package when the name is a path to a file" do
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "downgrades the package when allow_downgrade is true" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ allow_downgrade true
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "upgrades the package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "is idempotent when the package is already installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
@@ -706,204 +730,239 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options "--nogpgcheck"
+ action :upgrade
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "works with a local source" do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
flush_cache
- yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") do
+ options "--nogpgcheck"
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
context "version pinning" do
it "with an equality pin in the name it upgrades a prior package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm-1.10")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.10") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with a prco equality pin in the name it upgrades a prior package" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm == 1.10")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm == 1.10") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with an equality pin in the name it downgrades a later package" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm-1.2")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm-1.2") do
+ allow_downgrade true
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a prco equality pin in the name it downgrades a later package" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm == 1.2")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm == 1.2") do
+ allow_downgrade true
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a > pin in the name and no rpm installed it installs" do
flush_cache
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with a < pin in the name and no rpm installed it installs" do
flush_cache
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a > pin in the name and matching rpm installed it does nothing" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with a < pin in the name and no rpm installed it installs" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ options default_options
+ action :upgrade
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
it "with a > pin in the name and non-matching rpm installed it upgrades" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.package_name("chef_rpm > 1.2")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package("chef_rpm > 1.2") do
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
it "with a < pin in the name and non-matching rpm installed it downgrades" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.allow_downgrade true
- yum_package.package_name("chef_rpm < 1.10")
- yum_package.run_action(:upgrade)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$")
+ yum_package("chef_rpm < 1.10") do
+ allow_downgrade true
+ options default_options
+ action :upgrade
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.#{pkg_arch}$")
end
end
end
describe ":remove" do
context "vanilla use case" do
- let(:package_name) { "chef_rpm" }
it "does nothing if the package is not installed" do
flush_cache
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "does not remove the package twice" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the i686 package is installed", :intel_64bit do
skip "FIXME: should this be fixed or is the current behavior correct?"
preinstall("chef_rpm-1.10-1.i686.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the prior version i686 package is installed", :intel_64bit do
skip "FIXME: should this be fixed or is the current behavior correct?"
preinstall("chef_rpm-1.2-1.i686.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
end
context "with 64-bit arch", :intel_64bit do
- let(:package_name) { "chef_rpm.#{pkg_arch}" }
it "does nothing if the package is not installed" do
flush_cache
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the package is installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "removes the package if the prior version package is installed" do
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
it "does nothing if the i686 package is installed" do
preinstall("chef_rpm-1.10-1.i686.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$")
+ yum_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.i686$")
end
it "does nothing if the prior version i686 package is installed" do
preinstall("chef_rpm-1.2-1.i686.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be false
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$")
+ yum_package "chef_rpm.#{pkg_arch}" do
+ options default_options
+ action :remove
+ end.should_not_be_updated
+ expect_matching_installed_version("^chef_rpm-1.2-1.i686$")
end
end
context "with 32-bit arch", :intel_64bit do
- let(:package_name) { "chef_rpm.i686" }
it "removes only the 32-bit arch if both are installed" do
preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$")
+ yum_package "chef_rpm.i686" do
+ options default_options
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^chef_rpm-1.10-1.#{pkg_arch}$")
end
end
@@ -911,9 +970,11 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
it "works when a package is installed" do
FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo"
preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm")
- yum_package.run_action(:remove)
- expect(yum_package.updated_by_last_action?).to be true
- expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$")
+ yum_package "chef_rpm" do
+ options "--nogpgcheck"
+ action :remove
+ end.should_be_updated
+ expect_matching_installed_version("^package chef_rpm is not installed$")
end
end
end
@@ -929,52 +990,58 @@ describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do
it "locks an rpm" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.run_action(:lock)
- expect(yum_package.updated_by_last_action?).to be true
+ yum_package("chef_rpm") do
+ options default_options
+ action :lock
+ end.should_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-")
end
it "does not lock if its already locked" do
flush_cache
shell_out!("yum versionlock add chef_rpm")
- yum_package.package_name("chef_rpm")
- yum_package.run_action(:lock)
- expect(yum_package.updated_by_last_action?).to be false
+ yum_package("chef_rpm") do
+ options default_options
+ action :lock
+ end.should_not_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-")
end
it "unlocks an rpm" do
flush_cache
shell_out!("yum versionlock add chef_rpm")
- yum_package.package_name("chef_rpm")
- yum_package.run_action(:unlock)
- expect(yum_package.updated_by_last_action?).to be true
+ yum_package("chef_rpm") do
+ options default_options
+ action :unlock
+ end.should_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-")
end
it "does not unlock an already locked rpm" do
flush_cache
- yum_package.package_name("chef_rpm")
- yum_package.run_action(:unlock)
- expect(yum_package.updated_by_last_action?).to be false
+ yum_package("chef_rpm") do
+ options default_options
+ action :unlock
+ end.should_not_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-")
end
it "check that we can lock based on provides" do
flush_cache
- yum_package.package_name("chef_rpm_provides")
- yum_package.run_action(:lock)
- expect(yum_package.updated_by_last_action?).to be true
+ yum_package("chef_rpm_provides") do
+ options default_options
+ action :lock
+ end.should_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-")
end
it "check that we can unlock based on provides" do
flush_cache
shell_out!("yum versionlock add chef_rpm")
- yum_package.package_name("chef_rpm_provides")
- yum_package.run_action(:unlock)
- expect(yum_package.updated_by_last_action?).to be true
+ yum_package("chef_rpm_provides") do
+ options default_options
+ action :unlock
+ end.should_be_updated
expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-")
end
end
diff --git a/spec/functional/version_spec.rb b/spec/functional/version_spec.rb
index 5d0f0fce43..3b348ed024 100644
--- a/spec/functional/version_spec.rb
+++ b/spec/functional/version_spec.rb
@@ -25,7 +25,7 @@ describe "Chef Versions", :executables do
include Chef::Mixin::ShellOut
let(:chef_dir) { File.join(__dir__, "..", "..") }
- binaries = [ ChefUtils::Dist::Infra::CLIENT, "chef-shell", "chef-apply", "knife", ChefUtils::Dist::Solo::EXEC ]
+ binaries = [ ChefUtils::Dist::Infra::CLIENT, "chef-shell", "chef-apply", ChefUtils::Dist::Solo::EXEC ]
binaries.each do |binary|
it "#{binary} version should be sane" do
diff --git a/spec/functional/win32/registry_spec.rb b/spec/functional/win32/registry_spec.rb
index 3a1c71edf1..2b371ee9ed 100644
--- a/spec/functional/win32/registry_spec.rb
+++ b/spec/functional/win32/registry_spec.rb
@@ -322,7 +322,7 @@ describe "Chef::Win32::Registry", :windows_only do
end
describe "delete_key" do
- before (:all) do
+ before(:all) do
::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Fruit"
::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Fruit', Win32::Registry::KEY_ALL_ACCESS) do |reg|
reg["Apple", Win32::Registry::REG_MULTI_SZ] = %w{Red Juicy}
diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb
deleted file mode 100644
index 5746283b78..0000000000
--- a/spec/functional/win32/service_manager_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-#
-# Author:: Serdar Sutay (<serdar@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-if ChefUtils.windows?
- require "chef/application/windows_service_manager"
-end
-
-#
-# ATTENTION:
-# This test creates a windows service for testing purposes and runs it
-# as Local System (or an otherwise specified user) on windows boxes.
-# This test will fail if you run the tests inside a Windows VM by
-# sharing the code from your host since Local System account by
-# default can't see the mounted partitions.
-# Run this test by copying the code to a local VM directory or setup
-# Local System account to see the maunted partitions for the shared
-# directories.
-#
-
-describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do
-
- include_context "using Win32::Service"
-
- context "with invalid service definition" do
- it "throws an error when initialized with no service definition" do
- expect { Chef::Application::WindowsServiceManager.new(nil) }.to raise_error(ArgumentError)
- end
-
- it "throws an error with required missing options" do
- %i{service_name service_display_name service_description service_file_path}.each do |key|
- service_def = test_service.dup
- service_def.delete(key)
-
- expect { Chef::Application::WindowsServiceManager.new(service_def) }.to raise_error(ArgumentError)
- end
- end
- end
-
- context "with valid definition" do
- before(:each) do
- @service_manager_output = [ ]
- # Uncomment below lines to debug this test
- # original_puts = $stdout.method(:puts)
- allow($stdout).to receive(:puts) do |message|
- @service_manager_output << message
- # original_puts.call(message)
- end
- end
-
- after(:each) do
- cleanup
- end
-
- context "when service doesn't exist" do
- it "default => should say service don't exist" do
- service_manager.run
-
- expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0
- end
-
- it "install => should install the service" do
- service_manager.run(["-a", "install"])
-
- expect(test_service_exists?).to be_truthy
- end
-
- it "other actions => should say service doesn't exist" do
- %w{delete start stop pause resume uninstall}.each do |action|
- service_manager.run(["-a", action])
- expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0
- @service_manager_output = [ ]
- end
- end
- end
-
- context "when service exists" do
- before(:each) do
- service_manager.run(["-a", "install"])
- end
-
- it "should have an own-process, non-interactive type" do
- status = ::Win32::Service.status("spec-service")
- expect(status[:service_type]).to eq("own process")
- expect(status[:interactive]).to be_falsey
- end
-
- it "install => should say service already exists" do
- service_manager.run(["-a", "install"])
- expect(@service_manager_output.grep(/already exists/).length).to be > 0
- end
-
- context "and service is stopped" do
- %w{delete uninstall}.each do |action|
- it "#{action} => should remove the service", :volatile do
- service_manager.run(["-a", action])
- expect(test_service_exists?).to be_falsey
- end
- end
-
- it "default, status => should say service is stopped" do
- service_manager.run([ ])
- expect(@service_manager_output.grep(/stopped/).length).to be > 0
- @service_manager_output = [ ]
-
- service_manager.run(["-a", "status"])
- expect(@service_manager_output.grep(/stopped/).length).to be > 0
- end
-
- it "start should start the service", :volatile do
- service_manager.run(["-a", "start"])
- expect(test_service_state).to eq("running")
- expect(File.exist?(test_service_file)).to be_truthy
- end
-
- it "stop should not affect the service" do
- service_manager.run(["-a", "stop"])
- expect(test_service_state).to eq("stopped")
- end
-
- %w{pause resume}.each do |action|
- it "#{action} => should raise error" do
- expect { service_manager.run(["-a", action]) }.to raise_error(SystemCallError)
- end
- end
-
- context "and service is started", :volatile do
- before(:each) do
- service_manager.run(["-a", "start"])
- end
-
- %w{delete uninstall}.each do |action|
- it "#{action} => should remove the service", :volatile do
- service_manager.run(["-a", action])
- expect(test_service_exists?).to be_falsey
- end
- end
-
- it "default, status => should say service is running" do
- service_manager.run([ ])
- expect(@service_manager_output.grep(/running/).length).to be > 0
- @service_manager_output = [ ]
-
- service_manager.run(["-a", "status"])
- expect(@service_manager_output.grep(/running/).length).to be > 0
- end
-
- it "stop should stop the service" do
- service_manager.run(["-a", "stop"])
- expect(test_service_state).to eq("stopped")
- end
-
- it "pause should pause the service" do
- service_manager.run(["-a", "pause"])
- expect(test_service_state).to eq("paused")
- end
-
- it "resume should have no affect" do
- service_manager.run(["-a", "resume"])
- expect(test_service_state).to eq("running")
- end
- end
-
- context "and service is paused", :volatile do
- before(:each) do
- service_manager.run(["-a", "start"])
- service_manager.run(["-a", "pause"])
- end
-
- actions = %w{delete uninstall}
- actions.each do |action|
- it "#{action} => should remove the service" do
- service_manager.run(["-a", action])
- expect(test_service_exists?).to be_falsey
- end
- end
-
- it "default, status => should say service is paused" do
- service_manager.run([ ])
- expect(@service_manager_output.grep(/paused/).length).to be > 0
- @service_manager_output = [ ]
-
- service_manager.run(["-a", "status"])
- expect(@service_manager_output.grep(/paused/).length).to be > 0
- end
-
- it "stop should stop the service" do
- service_manager.run(["-a", "stop"])
- expect(test_service_state).to eq("stopped")
- end
-
- it "pause should not affect the service" do
- service_manager.run(["-a", "pause"])
- expect(test_service_state).to eq("paused")
- end
-
- it "start should raise an error" do
- expect { service_manager.run(["-a", "start"]) }.to raise_error(::Win32::Service::Error)
- end
-
- end
- end
- end
- end
-end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index 71f517141e..ecd8ec5bb5 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -34,7 +34,7 @@ describe "chef-client" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..") }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -97,7 +97,8 @@ describe "chef-client" do
before { file ".chef/knife.rb", "xxx.xxx" }
it "should load .chef/knife.rb when -z is specified" do
- result = shell_out("#{chef_client} -z -o 'x::default'", cwd: path_to(""))
+ # On Solaris shell_out will invoke /bin/sh which doesn't understand how to correctly update ENV['PWD']
+ result = shell_out("#{chef_client} -z -o 'x::default'", cwd: path_to(""), env: { "PWD" => nil })
# FATAL: Configuration error NoMethodError: undefined method `xxx' for nil:NilClass
expect(result.stdout).to include("xxx")
end
@@ -348,26 +349,6 @@ describe "chef-client" do
expect(result.stdout).to include("Run List is [recipe[x::default]]")
result.error!
end
-
- it "should complete with success when using --profile-ruby and output a profile file", :not_supported_on_aix do
- file "config/client.rb", <<~EOM
- local_mode true
- cookbook_path "#{path_to("cookbooks")}"
- EOM
- result = shell_out!("#{chef_client} -c \"#{path_to("config/client.rb")}\" -o 'x::default' -z --profile-ruby", cwd: chef_dir)
- result.error!
- expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be true
- end
-
- it "doesn't produce a profile when --profile-ruby is not present" do
- file "config/client.rb", <<~EOM
- local_mode true
- cookbook_path "#{path_to("cookbooks")}"
- EOM
- result = shell_out!("#{chef_client} -c \"#{path_to("config/client.rb")}\" -o 'x::default' -z", cwd: chef_dir)
- result.error!
- expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be false
- end
end
when_the_repository "has a cookbook that outputs some node attributes" do
@@ -431,7 +412,10 @@ describe "chef-client" do
EOM
end
- it "the cheffish DSL is loaded lazily" do
+ xit "the cheffish DSL is loaded lazily" do
+ # pending "cheffish gem integration must address that cheffish requires chef/knife"
+ # # Note that this does work in CI - we should also track down how CI is managing to load
+ # chef/knife since it's not in the chef-client that's being bundle-exec'd.
command = shell_out("#{chef_client} -c \"#{path_to("config/client.rb")}\" -o 'x::default' --no-fork", cwd: chef_dir)
expect(command.exitstatus).to eql(0)
end
diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb
index 53a8f1f895..f552ae0fc1 100644
--- a/spec/integration/client/exit_code_spec.rb
+++ b/spec/integration/client/exit_code_spec.rb
@@ -12,7 +12,7 @@ describe "chef-client" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..") }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb
index 816e1c40e6..f575f9db95 100644
--- a/spec/integration/client/ipv6_spec.rb
+++ b/spec/integration/client/ipv6_spec.rb
@@ -74,7 +74,7 @@ describe "chef-client" do
basic_config_file
end
- let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..") }
let(:chef_client_cmd) { %Q{bundle exec chef-client --minimal-ohai -c "#{path_to("config/client.rb")}" -lwarn} }
diff --git a/spec/integration/compliance/compliance_spec.rb b/spec/integration/compliance/compliance_spec.rb
index 98776308de..70d048c7fc 100644
--- a/spec/integration/compliance/compliance_spec.rb
+++ b/spec/integration/compliance/compliance_spec.rb
@@ -4,12 +4,12 @@ require "support/shared/integration/integration_helper"
require "chef/mixin/shell_out"
require "chef-utils/dist"
-describe "chef-client with audit mode" do
+describe "chef-client with compliance phase" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..") }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -46,6 +46,7 @@ describe "chef-client with audit mode" do
file "attributes.json", <<~FILE
{
"audit": {
+ "compliance_phase": true,
"json_file": {
"location": "#{report_file}"
},
diff --git a/spec/integration/knife/chef_fs_data_store_spec.rb b/spec/integration/knife/chef_fs_data_store_spec.rb
deleted file mode 100644
index 6e04684c33..0000000000
--- a/spec/integration/knife/chef_fs_data_store_spec.rb
+++ /dev/null
@@ -1,557 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/list"
-require "chef/knife/delete"
-require "chef/knife/show"
-require "chef/knife/raw"
-require "chef/knife/cookbook_upload"
-
-describe "ChefFSDataStore tests", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- let(:cookbook_x_100_metadata_rb) { cb_metadata("x", "1.0.0") }
- let(:cookbook_z_100_metadata_rb) { cb_metadata("z", "1.0.0") }
-
- describe "with repo mode 'hosted_everything' (default)" do
- before do
- Chef::Config.chef_zero.osc_compat = false
- end
-
- when_the_repository "has one of each thing" do
- before do
- file "clients/x.json", {}
- file "cookbook_artifacts/x-111/metadata.rb", cookbook_x_100_metadata_rb
- file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb
- file "data_bags/x/y.json", {}
- file "environments/x.json", {}
- file "nodes/x.json", {}
- file "roles/x.json", {}
- # file "users/x.json", {}
- file "containers/x.json", {}
- file "groups/x.json", {}
- file "containers/x.json", {}
- file "groups/x.json", {}
- file "policies/x-111.json", {}
- file "policy_groups/x.json", {}
- end
-
- context "GET /TYPE" do
- it "knife list -z -R returns everything" do
- knife("list -z -Rfp /").should_succeed <<~EOM
- /acls/
- /acls/clients/
- /acls/clients/x.json
- /acls/containers/
- /acls/containers/x.json
- /acls/cookbook_artifacts/
- /acls/cookbook_artifacts/x.json
- /acls/cookbooks/
- /acls/cookbooks/x.json
- /acls/data_bags/
- /acls/data_bags/x.json
- /acls/environments/
- /acls/environments/x.json
- /acls/groups/
- /acls/groups/x.json
- /acls/nodes/
- /acls/nodes/x.json
- /acls/organization.json
- /acls/policies/
- /acls/policies/x.json
- /acls/policy_groups/
- /acls/policy_groups/x.json
- /acls/roles/
- /acls/roles/x.json
- /clients/
- /clients/x.json
- /containers/
- /containers/x.json
- /cookbook_artifacts/
- /cookbook_artifacts/x-111/
- /cookbook_artifacts/x-111/metadata.rb
- /cookbooks/
- /cookbooks/x/
- /cookbooks/x/metadata.rb
- /data_bags/
- /data_bags/x/
- /data_bags/x/y.json
- /environments/
- /environments/x.json
- /groups/
- /groups/x.json
- /invitations.json
- /members.json
- /nodes/
- /nodes/x.json
- /org.json
- /policies/
- /policies/x-111.json
- /policy_groups/
- /policy_groups/x.json
- /roles/
- /roles/x.json
- EOM
- end
- end
-
- context "DELETE /TYPE/NAME" do
- it "knife delete -z /clients/x.json works" do
- knife("delete -z /clients/x.json").should_succeed "Deleted /clients/x.json\n"
- knife("list -z -Rfp /clients").should_succeed ""
- end
-
- it "knife delete -z -r /cookbooks/x works" do
- knife("delete -z -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -z -Rfp /cookbooks").should_succeed ""
- end
-
- it "knife delete -z -r /data_bags/x works" do
- knife("delete -z -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
- knife("list -z -Rfp /data_bags").should_succeed ""
- end
-
- it "knife delete -z /data_bags/x/y.json works" do
- knife("delete -z /data_bags/x/y.json").should_succeed "Deleted /data_bags/x/y.json\n"
- knife("list -z -Rfp /data_bags").should_succeed "/data_bags/x/\n"
- end
-
- it "knife delete -z /environments/x.json works" do
- knife("delete -z /environments/x.json").should_succeed "Deleted /environments/x.json\n"
- knife("list -z -Rfp /environments").should_succeed ""
- end
-
- it "knife delete -z /nodes/x.json works" do
- knife("delete -z /nodes/x.json").should_succeed "Deleted /nodes/x.json\n"
- knife("list -z -Rfp /nodes").should_succeed ""
- end
-
- it "knife delete -z /roles/x.json works" do
- knife("delete -z /roles/x.json").should_succeed "Deleted /roles/x.json\n"
- knife("list -z -Rfp /roles").should_succeed ""
- end
-
- end
-
- context "GET /TYPE/NAME" do
- it "knife show -z /clients/x.json works" do
- knife("show -z /clients/x.json").should_succeed( /"x"/ )
- end
-
- it "knife show -z /cookbooks/x/metadata.rb works" do
- knife("show -z /cookbooks/x/metadata.rb").should_succeed "/cookbooks/x/metadata.rb:\n#{cookbook_x_100_metadata_rb}\n"
- end
-
- it "knife show -z /data_bags/x/y.json works" do
- knife("show -z /data_bags/x/y.json").should_succeed( /"y"/ )
- end
-
- it "knife show -z /environments/x.json works" do
- knife("show -z /environments/x.json").should_succeed( /"x"/ )
- end
-
- it "knife show -z /nodes/x.json works" do
- knife("show -z /nodes/x.json").should_succeed( /"x"/ )
- end
-
- it "knife show -z /roles/x.json works" do
- knife("show -z /roles/x.json").should_succeed( /"x"/ )
- end
-
- end
-
- context "PUT /TYPE/NAME" do
- before do
- file "empty.json", {}
- file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
- file "rolestuff.json", '{"description":"hi there","name":"x"}'
- file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb
- end
-
- it "knife raw -z -i empty.json -m PUT /clients/x" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_succeed( /"x"/ )
- knife("list --local /clients").should_succeed "/clients/x.json\n"
- end
-
- it "knife cookbook upload works" do
- knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} x").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploaded 1 cookbook.
- EOM
- knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/x/\n/cookbooks/x/metadata.json\n/cookbooks/x/metadata.rb\n"
- end
-
- it "knife raw -z -i empty.json -m PUT /data/x/y" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_succeed( /"y"/ )
- knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/y.json\n"
- end
-
- it "knife raw -z -i empty.json -m PUT /environments/x" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_succeed( /"x"/ )
- knife("list --local /environments").should_succeed "/environments/x.json\n"
- end
-
- it "knife raw -z -i dummynode.json -m PUT /nodes/x" do
- knife("raw -z -i #{path_to("dummynode.json")} -m PUT /nodes/x").should_succeed( /"x"/ )
- knife("list --local /nodes").should_succeed "/nodes/x.json\n"
- knife("show -z /nodes/x.json --verbose").should_succeed(/"bar"/)
- end
-
- it "knife raw -z -i empty.json -m PUT /roles/x" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_succeed( /"x"/ )
- knife("list --local /roles").should_succeed "/roles/x.json\n"
- end
-
- it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do
- knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ )
- expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
- {
- "name": "x",
- "description": "hi there"
- }
- EOM
- end
- end
- end
-
- when_the_repository "is empty" do
- context "POST /TYPE/NAME" do
- before do
- file "empty.json", { "name" => "z" }
- file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
- file "empty_x.json", { "name" => "x" }
- file "empty_id.json", { "id" => "z" }
- file "rolestuff.json", '{"description":"hi there","name":"x"}'
- file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb
- end
-
- it "knife raw -z -i empty.json -m POST /clients" do
- knife("raw -z -i #{path_to("empty.json")} -m POST /clients").should_succeed( /uri/ )
- knife("list --local /clients").should_succeed "/clients/z.json\n"
- end
-
- it "knife cookbook upload works" do
- knife("cookbook upload -z --cookbook-path #{path_to("cookbooks_to_upload")} z").should_succeed stderr: <<~EOM
- Uploading z [1.0.0]
- Uploaded 1 cookbook.
- EOM
- knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/z/\n/cookbooks/z/metadata.json\n/cookbooks/z/metadata.rb\n"
- end
-
- it "knife raw -z -i empty.json -m POST /data" do
- knife("raw -z -i #{path_to("empty.json")} -m POST /data").should_succeed( /uri/ )
- knife("list --local -Rfp /data_bags").should_succeed "/data_bags/z/\n"
- end
-
- it "knife raw -z -i empty.json -m POST /data/x" do
- knife("raw -z -i #{path_to("empty_x.json")} -m POST /data").should_succeed( /uri/ )
- knife("raw -z -i #{path_to("empty_id.json")} -m POST /data/x").should_succeed( /"z"/ )
- knife("list --local -Rfp /data_bags").should_succeed "/data_bags/x/\n/data_bags/x/z.json\n"
- end
-
- it "knife raw -z -i empty.json -m POST /environments" do
- knife("raw -z -i #{path_to("empty.json")} -m POST /environments").should_succeed( /uri/ )
- knife("list --local /environments").should_succeed "/environments/z.json\n"
- end
-
- it "knife raw -z -i dummynode.json -m POST /nodes" do
- knife("raw -z -i #{path_to("dummynode.json")} -m POST /nodes").should_succeed( /uri/ )
- knife("list --local /nodes").should_succeed "/nodes/z.json\n"
- knife("show -z /nodes/z.json").should_succeed(/"bar"/)
- end
-
- it "knife raw -z -i empty.json -m POST /roles" do
- knife("raw -z -i #{path_to("empty.json")} -m POST /roles").should_succeed( /uri/ )
- knife("list --local /roles").should_succeed "/roles/z.json\n"
- end
-
- it "After knife raw -z -i rolestuff.json -m POST /roles, the output is pretty" do
- knife("raw -z -i #{path_to("rolestuff.json")} -m POST /roles").should_succeed( /uri/ )
- expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
- {
- "name": "x",
- "description": "hi there"
- }
- EOM
- end
- end
-
- it "knife list -z -R returns nothing" do
- knife("list -z -Rfp /").should_succeed <<~EOM
- /acls/
- /acls/clients/
- /acls/containers/
- /acls/cookbook_artifacts/
- /acls/cookbooks/
- /acls/data_bags/
- /acls/environments/
- /acls/groups/
- /acls/nodes/
- /acls/organization.json
- /acls/policies/
- /acls/policy_groups/
- /acls/roles/
- /clients/
- /containers/
- /cookbook_artifacts/
- /cookbooks/
- /data_bags/
- /environments/
- /groups/
- /invitations.json
- /members.json
- /nodes/
- /org.json
- /policies/
- /policy_groups/
- /roles/
- EOM
- end
-
- context "DELETE /TYPE/NAME" do
- it "knife delete -z /clients/x.json fails with an error" do
- knife("delete -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n"
- end
-
- it "knife delete -z -r /cookbooks/x fails with an error" do
- knife("delete -z -r /cookbooks/x").should_fail "ERROR: /cookbooks/x: No such file or directory\n"
- end
-
- it "knife delete -z -r /data_bags/x fails with an error" do
- knife("delete -z -r /data_bags/x").should_fail "ERROR: /data_bags/x: No such file or directory\n"
- end
-
- it "knife delete -z /data_bags/x/y.json fails with an error" do
- knife("delete -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
- end
-
- it "knife delete -z /environments/x.json fails with an error" do
- knife("delete -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n"
- end
-
- it "knife delete -z /nodes/x.json fails with an error" do
- knife("delete -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n"
- end
-
- it "knife delete -z /roles/x.json fails with an error" do
- knife("delete -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
- end
-
- end
-
- context "GET /TYPE/NAME" do
- it "knife show -z /clients/x.json fails with an error" do
- knife("show -z /clients/x.json").should_fail "ERROR: /clients/x.json: No such file or directory\n"
- end
-
- it "knife show -z /cookbooks/x/metadata.rb fails with an error" do
- knife("show -z /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb: No such file or directory\n"
- end
-
- it "knife show -z /data_bags/x/y.json fails with an error" do
- knife("show -z /data_bags/x/y.json").should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
- end
-
- it "knife show -z /environments/x.json fails with an error" do
- knife("show -z /environments/x.json").should_fail "ERROR: /environments/x.json: No such file or directory\n"
- end
-
- it "knife show -z /nodes/x.json fails with an error" do
- knife("show -z /nodes/x.json").should_fail "ERROR: /nodes/x.json: No such file or directory\n"
- end
-
- it "knife show -z /roles/x.json fails with an error" do
- knife("show -z /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
- end
-
- end
-
- context "PUT /TYPE/NAME" do
- before do
- file "empty.json", {}
- end
-
- it "knife raw -z -i empty.json -m PUT /clients/x fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /clients/x").should_fail( /404/ )
- end
-
- it "knife raw -z -i empty.json -m PUT /data/x/y fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /data/x/y").should_fail( /404/ )
- end
-
- it "knife raw -z -i empty.json -m PUT /environments/x fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /environments/x").should_fail( /404/ )
- end
-
- it "knife raw -z -i empty.json -m PUT /nodes/x fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /nodes/x").should_fail( /404/ )
- end
-
- it "knife raw -z -i empty.json -m PUT /roles/x fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /roles/x").should_fail( /404/ )
- end
-
- end
- end
- end
-
- # We have to configure Zero for Chef 11 mode in order to test users because:
- # 1. local mode overrides your `chef_server_url` to something like "http://localhost:PORT"
- # 2. single org mode maps requests like "https://localhost:PORT/users" so
- # they're functionally equivalent to "https://localhost:PORT/organizations/DEFAULT/users"
- # 3. Users are global objects in Chef 12, and should be accessed at URLs like
- # "https://localhost:PORT/users" (there is an org-specific users endpoint,
- # but it's for listing users in an org, not for managing users).
- # 4. Therefore you can't hit the _real_ users endpoint in local mode when
- # configured for Chef Server 12 mode.
- #
- # Because of this, we have to configure Zero for Chef 11 OSC mode in order to
- # test the users part of the data store with local mode.
- describe "with repo mode 'everything'" do
- before do
- Chef::Config.repo_mode = "everything"
- Chef::Config.chef_zero.osc_compat = true
- end
-
- when_the_repository "has one of each thing" do
- before do
- file "clients/x.json", {}
- file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb
- file "data_bags/x/y.json", {}
- file "environments/x.json", {}
- file "nodes/x.json", {}
- file "roles/x.json", {}
- file "users/x.json", {}
- end
-
- context "GET /TYPE" do
- it "knife list -z -R returns everything" do
- knife("list -z -Rfp /").should_succeed <<~EOM
- /clients/
- /clients/x.json
- /cookbooks/
- /cookbooks/x/
- /cookbooks/x/metadata.rb
- /data_bags/
- /data_bags/x/
- /data_bags/x/y.json
- /environments/
- /environments/x.json
- /nodes/
- /nodes/x.json
- /roles/
- /roles/x.json
- /users/
- /users/x.json
- EOM
- end
- end
-
- context "DELETE /TYPE/NAME" do
- it "knife delete -z /users/x.json works" do
- knife("delete -z /users/x.json").should_succeed "Deleted /users/x.json\n"
- knife("list -z -Rfp /users").should_succeed ""
- end
- end
-
- context "GET /TYPE/NAME" do
- it "knife show -z /users/x.json works" do
- knife("show -z /users/x.json").should_succeed( /"x"/ )
- end
- end
-
- context "PUT /TYPE/NAME" do
- before do
- file "empty.json", {}
- file "dummynode.json", { "name" => "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
- file "rolestuff.json", '{"description":"hi there","name":"x"}'
- file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb
- end
-
- it "knife raw -z -i empty.json -m PUT /users/x" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_succeed( /"x"/ )
- knife("list --local /users").should_succeed "/users/x.json\n"
- end
-
- it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty" do
- knife("raw -z -i #{path_to("rolestuff.json")} -m PUT /roles/x").should_succeed( /"x"/ )
- expect(IO.read(path_to("roles/x.json"))).to eq <<~EOM.strip
- {
- "name": "x",
- "description": "hi there"
- }
- EOM
- end
- end
- end
-
- when_the_repository "is empty" do
- context "POST /TYPE/NAME" do
- before do
- file "empty.json", { "name" => "z" }
- file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } }
- file "empty_x.json", { "name" => "x" }
- file "empty_id.json", { "id" => "z" }
- file "rolestuff.json", '{"description":"hi there","name":"x"}'
- file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb
- end
-
- it "knife raw -z -i empty.json -m POST /users" do
- knife("raw -z -i #{path_to("empty.json")} -m POST /users").should_succeed( /uri/ )
- knife("list --local /users").should_succeed "/users/z.json\n"
- end
- end
-
- it "knife list -z -R returns nothing" do
- knife("list -z -Rfp /").should_succeed <<~EOM
- /clients/
- /cookbooks/
- /data_bags/
- /environments/
- /nodes/
- /roles/
- /users/
- EOM
- end
-
- context "DELETE /TYPE/NAME" do
- it "knife delete -z /users/x.json fails with an error" do
- knife("delete -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n"
- end
- end
-
- context "GET /TYPE/NAME" do
- it "knife show -z /users/x.json fails with an error" do
- knife("show -z /users/x.json").should_fail "ERROR: /users/x.json: No such file or directory\n"
- end
- end
-
- context "PUT /TYPE/NAME" do
- before do
- file "empty.json", {}
- end
-
- it "knife raw -z -i empty.json -m PUT /users/x fails with 404" do
- knife("raw -z -i #{path_to("empty.json")} -m PUT /users/x").should_fail( /404/ )
- end
- end
- end
- end
-end
diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb
deleted file mode 100644
index ac7dae15f0..0000000000
--- a/spec/integration/knife/chef_repo_path_spec.rb
+++ /dev/null
@@ -1,962 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/list"
-require "chef/knife/show"
-
-describe "chef_repo_path tests", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ }
-
- # TODO alternate repo_path / *_path
- context "alternate *_path" do
- when_the_repository "has clients and clients2, cookbooks and cookbooks2, etc." do
- before do
- file "clients/client1.json", {}
- file "cookbooks/cookbook1/metadata.rb", ""
- file "data_bags/bag/item.json", {}
- file "environments/env1.json", {}
- file "nodes/node1.json", {}
- file "roles/role1.json", {}
- file "users/user1.json", {}
-
- file "clients2/client2.json", {}
- file "cookbooks2/cookbook2/metadata.rb", ""
- file "data_bags2/bag2/item2.json", {}
- file "environments2/env2.json", {}
- file "nodes2/node2.json", {}
- file "roles2/role2.json", {}
- file "users2/user2.json", {}
-
- directory "chef_repo2" do
- file "clients/client3.json", {}
- file "cookbooks/cookbook3/metadata.rb", "name 'cookbook3'"
- file "data_bags/bag3/item3.json", {}
- file "environments/env3.json", {}
- file "nodes/node3.json", {}
- file "roles/role3.json", {}
- file "users/user3.json", {}
- end
- end
-
- it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do
- Chef::Config.delete(:chef_repo_path)
- knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM
- /clients/
- /clients/client3.json
- /cookbooks/
- /cookbooks/cookbook3/
- /cookbooks/cookbook3/metadata.rb
- /data_bags/
- /data_bags/bag3/
- /data_bags/bag3/item3.json
- /environments/
- /environments/env3.json
- /nodes/
- /nodes/node3.json
- /roles/
- /roles/role3.json
- /users/
- /users/user3.json
- EOM
- end
-
- # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default"
- it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do
- Chef::Config.delete(:chef_repo_path)
- knife("list --local -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM
- /clients/
- /clients/client3.json
- /cookbooks/
- /cookbooks/cookbook3/
- /cookbooks/cookbook3/metadata.rb
- /data_bags/
- /data_bags/bag3/
- /data_bags/bag3/item3.json
- /environments/
- /environments/env3.json
- /nodes/
- /nodes/node3.json
- /roles/
- /roles/role3.json
- /users/
- /users/user3.json
- EOM
- end
-
- # "Skipping for BK... As Windows 2019 has 8dot3name disabled by default"
- it "knife list --local -Rfp --chef-repo-path chef_r~1 / grabs chef_repo2 stuff", :windows_only, :skip_buildkite do
- Chef::Config.delete(:chef_repo_path)
- knife("list -z -Rfp --chef-repo-path #{path_to("chef_r~1")} /").should_succeed <<~EOM
- /acls/
- /acls/clients/
- /acls/clients/client3.json
- /acls/containers/
- /acls/cookbook_artifacts/
- /acls/cookbooks/
- /acls/cookbooks/cookbook3.json
- /acls/data_bags/
- /acls/data_bags/bag3.json
- /acls/environments/
- /acls/environments/env3.json
- /acls/groups/
- /acls/nodes/
- /acls/nodes/node3.json
- /acls/organization.json
- /acls/policies/
- /acls/policy_groups/
- /acls/roles/
- /acls/roles/role3.json
- /clients/
- /clients/client3.json
- /containers/
- /cookbook_artifacts/
- /cookbooks/
- /cookbooks/cookbook3/
- /cookbooks/cookbook3/metadata.rb
- /data_bags/
- /data_bags/bag3/
- /data_bags/bag3/item3.json
- /environments/
- /environments/env3.json
- /groups/
- /invitations.json
- /members.json
- /nodes/
- /nodes/node3.json
- /org.json
- /policies/
- /policy_groups/
- /roles/
- /roles/role3.json
- EOM
- end
-
- context "when all _paths are set to alternates" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
- end
- Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
- end
-
- it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do
- knife("list --local -Rfp --chef-repo-path #{path_to("chef_repo2")} /").should_succeed <<~EOM
- /clients/
- /clients/client3.json
- /cookbooks/
- /cookbooks/cookbook3/
- /cookbooks/cookbook3/metadata.rb
- /data_bags/
- /data_bags/bag3/
- /data_bags/bag3/item3.json
- /environments/
- /environments/env3.json
- /nodes/
- /nodes/node3.json
- /roles/
- /roles/role3.json
- /users/
- /users/user3.json
- EOM
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client2.json
- cookbooks/
- cookbooks/cookbook2/
- cookbooks/cookbook2/metadata.rb
- data_bags/
- data_bags/bag2/
- data_bags/bag2/item2.json
- environments/
- environments/env2.json
- nodes/
- nodes/node2.json
- roles/
- roles/role2.json
- users/
- users/user2.json
- EOM
- end
- end
-
- context "when cwd is inside data_bags2" do
- before { cwd "data_bags2" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag2/
- bag2/item2.json
- EOM
- end
- it "knife list --local -Rfp ../roles lists roles" do
- knife("list --local -Rfp ../roles").should_succeed "/roles/role2.json\n"
- end
- end
- end
-
- context "when all _paths except chef_repo_path are set to alternates" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
- end
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client2.json
- cookbooks/
- cookbooks/cookbook2/
- cookbooks/cookbook2/metadata.rb
- data_bags/
- data_bags/bag2/
- data_bags/bag2/item2.json
- environments/
- environments/env2.json
- nodes/
- nodes/node2.json
- roles/
- roles/role2.json
- users/
- users/user2.json
- EOM
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside data_bags2" do
- before { cwd "data_bags2" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag2/
- bag2/item2.json
- EOM
- end
- end
- end
-
- context "when only chef_repo_path is set to its alternate" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client3.json
- cookbooks/
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env3.json
- nodes/
- nodes/node3.json
- roles/
- roles/role3.json
- users/
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2/data_bags" do
- before { cwd "chef_repo2/data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag3/
- bag3/item3.json
- EOM
- end
- end
- end
-
- context "when paths are set to point to both versions of each" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config["#{object_name}_path".to_sym] = [
- File.join(Chef::Config.chef_repo_path, "#{object_name}s"),
- File.join(Chef::Config.chef_repo_path, "#{object_name}s2"),
- ]
- end
- Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
- end
-
- context "when there is a directory in clients1 and file in clients2 with the same name" do
- before do
- directory "clients/blah.json"
- file "clients2/blah.json", {}
- end
- it "knife show /clients/blah.json succeeds" do
- knife("show --local /clients/blah.json").should_succeed <<~EOM
- /clients/blah.json:
- {
-
- }
- EOM
- end
- end
-
- context "when there is a file in cookbooks1 and directory in cookbooks2 with the same name" do
- before do
- file "cookbooks/blah", ""
- file "cookbooks2/blah/metadata.rb", ""
- end
- it "knife list -Rfp cookbooks shows files in blah" do
- knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
- /cookbooks/blah/
- /cookbooks/blah/metadata.rb
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- EOM
- end
- end
-
- context "when there is an empty directory in cookbooks1 and a real cookbook in cookbooks2 with the same name" do
- before do
- directory "cookbooks/blah"
- file "cookbooks2/blah/metadata.rb", ""
- end
- it "knife list -Rfp cookbooks shows files in blah" do
- knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'blah' is empty or entirely chefignored at #{Chef::Config.cookbook_path[0]}/blah\n")
- /cookbooks/blah/
- /cookbooks/blah/metadata.rb
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- EOM
- end
- end
-
- context "when there is a cookbook in cookbooks1 and a cookbook in cookbooks2 with the same name" do
- before do
- file "cookbooks/blah/metadata.json", {}
- file "cookbooks2/blah/metadata.rb", ""
- end
- it "knife list -Rfp cookbooks shows files in the first cookbook and not the second" do
- knife("list --local -Rfp /cookbooks").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.cookbook_path[0]}/blah and #{Chef::Config.cookbook_path[1]}/blah\n")
- /cookbooks/blah/
- /cookbooks/blah/metadata.json
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- EOM
- end
- end
-
- context "when there is a file in data_bags1 and a directory in data_bags2 with the same name" do
- before do
- file "data_bags/blah", ""
- file "data_bags2/blah/item.json", ""
- end
- it "knife list -Rfp data_bags shows files in blah" do
- knife("list --local -Rfp /data_bags").should_succeed <<~EOM
- /data_bags/bag/
- /data_bags/bag/item.json
- /data_bags/bag2/
- /data_bags/bag2/item2.json
- /data_bags/blah/
- /data_bags/blah/item.json
- EOM
- end
- end
-
- context "when there is a data bag in data_bags1 and a data bag in data_bags2 with the same name" do
- before do
- file "data_bags/blah/item1.json", ""
- file "data_bags2/blah/item2.json", ""
- end
- it "knife list -Rfp data_bags shows only items in data_bags1" do
- knife("list --local -Rfp /data_bags").should_succeed(<<~EOM, stderr: "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.data_bag_path[0]}/blah and #{Chef::Config.data_bag_path[1]}/blah\n")
- /data_bags/bag/
- /data_bags/bag/item.json
- /data_bags/bag2/
- /data_bags/bag2/item2.json
- /data_bags/blah/
- /data_bags/blah/item1.json
- EOM
- end
- end
-
- context "when there is a directory in environments1 and file in environments2 with the same name" do
- before do
- directory "environments/blah.json"
- file "environments2/blah.json", {}
- end
- it "knife show /environments/blah.json succeeds" do
- knife("show --local /environments/blah.json").should_succeed <<~EOM
- /environments/blah.json:
- {
-
- }
- EOM
- end
- end
-
- context "when there is a directory in nodes1 and file in nodes2 with the same name" do
- before do
- directory "nodes/blah.json"
- file "nodes2/blah.json", {}
- end
- it "knife show /nodes/blah.json succeeds" do
- knife("show --local /nodes/blah.json").should_succeed <<~EOM
- /nodes/blah.json:
- {
-
- }
- EOM
- end
- end
-
- context "when there is a directory in roles1 and file in roles2 with the same name" do
- before do
- directory "roles/blah.json"
- file "roles2/blah.json", {}
- end
- it "knife show /roles/blah.json succeeds" do
- knife("show --local /roles/blah.json").should_succeed <<~EOM
- /roles/blah.json:
- {
-
- }
- EOM
- end
- end
-
- context "when there is a directory in users1 and file in users2 with the same name" do
- before do
- directory "users/blah.json"
- file "users2/blah.json", {}
- end
- it "knife show /users/blah.json succeeds" do
- knife("show --local /users/blah.json").should_succeed <<~EOM
- /users/blah.json:
- {
-
- }
- EOM
- end
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag2/
- bag2/item2.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client1.json
- clients/client2.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook2/
- cookbooks/cookbook2/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- data_bags/bag2/
- data_bags/bag2/item2.json
- environments/
- environments/env1.json
- environments/env2.json
- nodes/
- nodes/node1.json
- nodes/node2.json
- roles/
- roles/role1.json
- roles/role2.json
- users/
- users/user1.json
- users/user2.json
- EOM
- end
- end
-
- context "when cwd is inside data_bags2" do
- before { cwd "data_bags2" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag2/
- bag2/item2.json
- EOM
- end
- end
- end
-
- context "when when chef_repo_path is set to both places and no other _path is set" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.chef_repo_path = [
- Chef::Config.chef_repo_path,
- File.join(Chef::Config.chef_repo_path, "chef_repo2"),
- ]
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client1.json
- clients/client3.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env1.json
- environments/env3.json
- nodes/
- nodes/node1.json
- nodes/node3.json
- roles/
- roles/role1.json
- roles/role3.json
- users/
- users/user1.json
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag3/
- bag3/item3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client1.json
- clients/client3.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env1.json
- environments/env3.json
- nodes/
- nodes/node1.json
- nodes/node3.json
- roles/
- roles/role1.json
- roles/role3.json
- users/
- users/user1.json
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2/data_bags" do
- before { cwd "chef_repo2/data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag3/
- bag3/item3.json
- EOM
- end
- end
- end
-
- context "when cookbook_path is set and nothing else" do
- before :each do
- %w{client data_bag environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.delete(:chef_repo_path)
- Chef::Config.cookbook_path = File.join(@repository_dir, "chef_repo2", "cookbooks")
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client3.json
- cookbooks/
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env3.json
- nodes/
- nodes/node3.json
- roles/
- roles/role3.json
- users/
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2/data_bags" do
- before { cwd "chef_repo2/data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag3/
- bag3/item3.json
- EOM
- end
- end
- end
-
- context "when cookbook_path is set to multiple places and nothing else is set" do
- before :each do
- %w{client data_bag environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.delete(:chef_repo_path)
- Chef::Config.cookbook_path = [
- File.join(@repository_dir, "cookbooks"),
- File.join(@repository_dir, "chef_repo2", "cookbooks"),
- ]
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client1.json
- clients/client3.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env1.json
- environments/env3.json
- nodes/
- nodes/node1.json
- nodes/node3.json
- roles/
- roles/role1.json
- roles/role3.json
- users/
- users/user1.json
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag3/
- bag3/item3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client1.json
- clients/client3.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- data_bags/bag3/
- data_bags/bag3/item3.json
- environments/
- environments/env1.json
- environments/env3.json
- nodes/
- nodes/node1.json
- nodes/node3.json
- roles/
- roles/role1.json
- roles/role3.json
- users/
- users/user1.json
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2/data_bags" do
- before { cwd "chef_repo2/data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- bag3/
- bag3/item3.json
- EOM
- end
- end
- end
-
- context "when data_bag_path and chef_repo_path are set, and nothing else" do
- before :each do
- %w{client cookbook environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.data_bag_path = File.join(Chef::Config.chef_repo_path, "data_bags")
- Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "chef_repo2")
- end
-
- context "when cwd is at the top level" do
- before { cwd "." }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2" do
- before { cwd "chef_repo2" }
- it "knife list --local -Rfp lists everything" do
- knife("list --local -Rfp").should_succeed <<~EOM
- clients/
- clients/client3.json
- cookbooks/
- cookbooks/cookbook3/
- cookbooks/cookbook3/metadata.rb
- data_bags/
- data_bags/bag/
- data_bags/bag/item.json
- environments/
- environments/env3.json
- nodes/
- nodes/node3.json
- roles/
- roles/role3.json
- users/
- users/user3.json
- EOM
- end
- end
-
- context "when cwd is inside chef_repo2/data_bags" do
- before { cwd "chef_repo2/data_bags" }
- it "knife list --local -Rfp fails" do
- knife("list --local -Rfp").should_fail(error_rel_path_outside_repo)
- end
- end
- end
-
- context "when data_bag_path is set and nothing else" do
- include_context "default config options"
-
- before :each do
- %w{client cookbook environment node role user}.each do |object_name|
- Chef::Config.delete("#{object_name}_path".to_sym)
- end
- Chef::Config.delete(:chef_repo_path)
- Chef::Config.data_bag_path = File.join(@repository_dir, "data_bags")
- end
-
- it "knife list --local -Rfp / lists data bags" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /data_bags/
- /data_bags/bag/
- /data_bags/bag/item.json
- EOM
- end
-
- it "knife list --local -Rfp /data_bags lists data bags" do
- knife("list --local -Rfp /data_bags").should_succeed <<~EOM
- /data_bags/bag/
- /data_bags/bag/item.json
- EOM
- end
-
- context "when cwd is inside the data_bags directory" do
- before { cwd "data_bags" }
- it "knife list --local -Rfp lists data bags" do
- knife("list --local -Rfp").should_succeed <<~EOM
- bag/
- bag/item.json
- EOM
- end
- end
- end
- end
-
- when_the_repository "is empty" do
- context "when the repository _paths point to places that do not exist" do
- before :each do
- %w{client cookbook data_bag environment node role user}.each do |object_name|
- Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "nowhere", object_name)
- end
- Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, "nowhere")
- end
-
- it "knife list --local -Rfp / fails" do
- knife("list --local -Rfp /").should_succeed ""
- end
-
- it "knife list --local -Rfp /data_bags fails" do
- knife("list --local -Rfp /data_bags").should_fail("ERROR: /data_bags: No such file or directory\n")
- end
- end
- end
- end
-end
diff --git a/spec/integration/knife/chef_repository_file_system_spec.rb b/spec/integration/knife/chef_repository_file_system_spec.rb
deleted file mode 100644
index 295efc0c3a..0000000000
--- a/spec/integration/knife/chef_repository_file_system_spec.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/list"
-require "chef/knife/show"
-
-describe "General chef_repo file system checks", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- context "directories and files that should/should not be ignored" do
- when_the_repository "has empty roles, environments and data bag item directories" do
- before do
- directory "roles"
- directory "environments"
- directory "data_bags/bag1"
- end
-
- it "knife list --local -Rfp / returns them" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /data_bags/
- /data_bags/bag1/
- /environments/
- /roles/
- EOM
- end
- end
-
- when_the_repository "has an empty data_bags directory" do
- before { directory "data_bags" }
-
- it "knife list --local / returns it" do
- knife("list --local /").should_succeed "/data_bags\n"
- end
- end
-
- when_the_repository "has an empty cookbook directory" do
- before { directory "cookbooks/cookbook1" }
-
- it "knife list --local -Rfp / does not return it" do
- knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
- /cookbooks/
- EOM
- end
- end
-
- when_the_repository "has only empty cookbook subdirectories" do
- before { directory "cookbooks/cookbook1/recipes" }
-
- it "knife list --local -Rfp / does not return it" do
- knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
- /cookbooks/
- EOM
- end
- end
-
- when_the_repository "has empty and non-empty cookbook subdirectories" do
- before do
- directory "cookbooks/cookbook1/recipes"
- file "cookbooks/cookbook1/templates/default/x.txt", ""
- end
-
- it "knife list --local -Rfp / does not return the empty ones" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/templates/
- /cookbooks/cookbook1/templates/default/
- /cookbooks/cookbook1/templates/default/x.txt
- EOM
- end
- end
-
- when_the_repository "has only empty cookbook sub-sub-directories" do
- before { directory "cookbooks/cookbook1/templates/default" }
-
- it "knife list --local -Rfp / does not return it" do
- knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
- /cookbooks/
- EOM
- end
- end
-
- when_the_repository "has empty cookbook sub-sub-directories alongside non-empty ones" do
- before do
- file "cookbooks/cookbook1/templates/default/x.txt", ""
- directory "cookbooks/cookbook1/templates/rhel"
- directory "cookbooks/cookbook1/files/default"
- end
-
- it "knife list --local -Rfp / does not return the empty ones" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/templates/
- /cookbooks/cookbook1/templates/default/
- /cookbooks/cookbook1/templates/default/x.txt
- EOM
- end
- end
-
- when_the_repository "has an extra schmenvironments directory" do
- before do
- directory "schmenvironments" do
- file "_default.json", {}
- end
- end
-
- it "knife list --local -Rfp / should NOT return it" do
- knife("list --local -Rfp /").should_succeed ""
- end
- end
-
- when_the_repository "has extra subdirectories and files under data bag items, roles, and environments" do
- before do
- directory "data_bags/bag1" do
- file "item1.json", {}
- file "item2.xml", ""
- file "another_subdir/item.json", {}
- end
- directory "roles" do
- file "role1.json", {}
- file "role2.xml", ""
- file "subdir/role.json", {}
- end
- directory "environments" do
- file "environment1.json", {}
- file "environment2.xml", ""
- file "subdir/environment.json", {}
- end
- end
-
- it "knife list --local -Rfp / should NOT return them" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /environments/
- /environments/environment1.json
- /roles/
- /roles/role1.json
- EOM
- end
- end
-
- when_the_repository "has a file in cookbooks/" do
- before { file "cookbooks/file", "" }
- it "does not show up in list -Rfp" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- EOM
- end
- end
-
- when_the_repository "has a file in data_bags/" do
- before { file "data_bags/file", "" }
- it "does not show up in list -Rfp" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /data_bags/
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook starting with ." do
- before do
- file "cookbooks/.svn/metadata.rb", ""
- file "cookbooks/a.b/metadata.rb", ""
- end
- it "knife list does not show it" do
- knife("list --local -fp /cookbooks").should_succeed "/cookbooks/a.b/\n"
- end
- end
-
- when_the_repository "has a data bag starting with ." do
- before do
- file "data_bags/.svn/x.json", {}
- file "data_bags/a.b/x.json", {}
- end
- it "knife list does not show it" do
- knife("list --local -fp /data_bags").should_succeed "/data_bags/a.b/\n"
- end
- end
-end
diff --git a/spec/integration/knife/chefignore_spec.rb b/spec/integration/knife/chefignore_spec.rb
deleted file mode 100644
index eccd38d928..0000000000
--- a/spec/integration/knife/chefignore_spec.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/list"
-require "chef/knife/show"
-
-describe "chefignore tests", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- when_the_repository "has lots of stuff in it" do
- before do
- file "roles/x.json", {}
- file "environments/x.json", {}
- file "data_bags/bag1/x.json", {}
- file "cookbooks/cookbook1/x.json", {}
- end
-
- context "and has a chefignore everywhere except cookbooks" do
- before do
- chefignore = "x.json\nroles/x.json\nenvironments/x.json\ndata_bags/bag1/x.json\nbag1/x.json\ncookbooks/cookbook1/x.json\ncookbook1/x.json\n"
- file "chefignore", chefignore
- file "roles/chefignore", chefignore
- file "environments/chefignore", chefignore
- file "data_bags/chefignore", chefignore
- file "data_bags/bag1/chefignore", chefignore
- file "cookbooks/cookbook1/chefignore", chefignore
- end
-
- it "matching files and directories get ignored" do
- # NOTE: many of the "chefignore" files should probably not show up
- # themselves, but we have other tests that talk about that
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/chefignore
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/x.json
- /environments/
- /environments/x.json
- /roles/
- /roles/x.json
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook with only chefignored files" do
- before do
- file "cookbooks/cookbook1/templates/default/x.rb", ""
- file "cookbooks/cookbook1/libraries/x.rb", ""
- file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n"
- end
-
- it "the cookbook is not listed" do
- knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
- /cookbooks/
- EOM
- end
- end
-
- when_the_repository "has multiple cookbooks" do
- before do
- file "cookbooks/cookbook1/x.json", {}
- file "cookbooks/cookbook1/y.json", {}
- file "cookbooks/cookbook2/x.json", {}
- file "cookbooks/cookbook2/y.json", {}
- end
-
- context "and has a chefignore with filenames" do
- before { file "cookbooks/chefignore", "x.json\n" }
-
- it "matching files and directories get ignored in all cookbooks" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has a chefignore with wildcards" do
- before do
- file "cookbooks/chefignore", "x.*\n"
- file "cookbooks/cookbook1/x.rb", ""
- end
-
- it "matching files and directories get ignored in all cookbooks" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has a chefignore with relative paths" do
- before do
- file "cookbooks/cookbook1/recipes/x.rb", ""
- file "cookbooks/cookbook2/recipes/y.rb", ""
- file "cookbooks/chefignore", "recipes/x.rb\n"
- end
-
- it "matching directories get ignored" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/x.json
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/recipes/
- /cookbooks/cookbook2/recipes/y.rb
- /cookbooks/cookbook2/x.json
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has a chefignore with subdirectories" do
- before do
- file "cookbooks/cookbook1/recipes/y.rb", ""
- file "cookbooks/chefignore", "recipes\nrecipes/\n"
- end
-
- it "matching directories do NOT get ignored" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/recipes/
- /cookbooks/cookbook1/recipes/y.rb
- /cookbooks/cookbook1/x.json
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/x.json
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has a chefignore that ignores all files in a subdirectory" do
- before do
- file "cookbooks/cookbook1/templates/default/x.rb", ""
- file "cookbooks/cookbook1/libraries/x.rb", ""
- file "cookbooks/chefignore", "libraries/x.rb\ntemplates/default/x.rb\n"
- end
-
- it "ignores the subdirectory entirely" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/x.json
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/x.json
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has an empty chefignore" do
- before do
- file "cookbooks/chefignore", "\n"
- end
-
- it "nothing is ignored" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/x.json
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/x.json
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
-
- context "and has a chefignore with comments and empty lines" do
- before do
- file "cookbooks/chefignore", "\n\n # blah\n#\nx.json\n\n"
- end
-
- it "matching files and directories get ignored in all cookbooks" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/y.json
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/y.json
- EOM
- end
- end
- end
-
- when_the_repository "has multiple cookbook paths" do
- before :each do
- Chef::Config.cookbook_path = [
- File.join(Chef::Config.chef_repo_path, "cookbooks1"),
- File.join(Chef::Config.chef_repo_path, "cookbooks2"),
- ]
- end
-
- before do
- file "cookbooks1/mycookbook/metadata.rb", ""
- file "cookbooks1/mycookbook/x.json", {}
- file "cookbooks2/yourcookbook/metadata.rb", ""
- file "cookbooks2/yourcookbook/x.json", ""
- end
-
- context "and multiple chefignores" do
- before do
- file "cookbooks1/chefignore", "metadata.rb\n"
- file "cookbooks2/chefignore", "x.json\n"
- end
- it "chefignores apply only to the directories they are in" do
- knife("list --local -Rfp /").should_succeed <<~EOM
- /cookbooks/
- /cookbooks/mycookbook/
- /cookbooks/mycookbook/x.json
- /cookbooks/yourcookbook/
- /cookbooks/yourcookbook/metadata.rb
- EOM
- end
-
- context "and conflicting cookbooks" do
- before do
- file "cookbooks1/yourcookbook/metadata.rb", ""
- file "cookbooks1/yourcookbook/x.json", ""
- file "cookbooks1/yourcookbook/onlyincookbooks1.rb", ""
- file "cookbooks2/yourcookbook/onlyincookbooks2.rb", ""
- end
-
- it "chefignores apply only to the winning cookbook" do
- knife("list --local -Rfp /").should_succeed(<<~EOM, stderr: "WARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook\n")
- /cookbooks/
- /cookbooks/mycookbook/
- /cookbooks/mycookbook/x.json
- /cookbooks/yourcookbook/
- /cookbooks/yourcookbook/onlyincookbooks1.rb
- /cookbooks/yourcookbook/x.json
- EOM
- end
- end
- end
- end
-
- when_the_repository "has a cookbook named chefignore" do
- before do
- file "cookbooks/chefignore/metadata.rb", {}
- end
- it "knife list -Rfp /cookbooks shows it" do
- knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
- /cookbooks/chefignore/
- /cookbooks/chefignore/metadata.rb
- EOM
- end
- end
-
- when_the_repository "has multiple cookbook paths, one with a chefignore file and the other with a cookbook named chefignore" do
- before do
- file "cookbooks1/chefignore", ""
- file "cookbooks1/blah/metadata.rb", ""
- file "cookbooks2/chefignore/metadata.rb", ""
- end
- before :each do
- Chef::Config.cookbook_path = [
- File.join(Chef::Config.chef_repo_path, "cookbooks1"),
- File.join(Chef::Config.chef_repo_path, "cookbooks2"),
- ]
- end
- it "knife list -Rfp /cookbooks shows the chefignore cookbook" do
- knife("list --local -Rfp /cookbooks").should_succeed <<~EOM
- /cookbooks/blah/
- /cookbooks/blah/metadata.rb
- /cookbooks/chefignore/
- /cookbooks/chefignore/metadata.rb
- EOM
- end
- end
-end
diff --git a/spec/integration/knife/client_bulk_delete_spec.rb b/spec/integration/knife/client_bulk_delete_spec.rb
deleted file mode 100644
index 5c0ff94867..0000000000
--- a/spec/integration/knife/client_bulk_delete_spec.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife client bulk delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some clients" do
- before do
- client "concat", {}
- client "cons", {}
- client "car", {}
- client "cdr", {}
- client "cat", {}
- end
-
- it "deletes all matching clients" do
- knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
- The following clients will be deleted:
-
- car cat
-
- Are you sure you want to delete these clients? (Y/N) Deleted client car
- Deleted client cat
- EOM
-
- knife("client list").should_succeed <<~EOM
- cdr
- chef-validator
- chef-webui
- concat
- cons
- EOM
- end
-
- it "deletes all matching clients when unanchored" do
- knife("client bulk delete ca.*", input: "Y").should_succeed <<~EOM
- The following clients will be deleted:
-
- car cat concat
-
- Are you sure you want to delete these clients? (Y/N) Deleted client car
- Deleted client cat
- Deleted client concat
- EOM
-
- knife("client list").should_succeed <<~EOM
- cdr
- chef-validator
- chef-webui
- cons
- EOM
- end
- end
-
- when_the_chef_server "has a validator client" do
- before do
- client "cons", {}
- client "car", {}
- client "car-validator", { validator: true }
- client "cdr", {}
- client "cat", {}
- end
-
- it "refuses to delete a validator normally" do
- knife("client bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
- The following clients are validators and will not be deleted:
-
- car-validator
-
- You must specify --delete-validators to delete the validator clients
- The following clients will be deleted:
-
- car cat
-
- Are you sure you want to delete these clients? (Y/N) Deleted client car
- Deleted client cat
- EOM
-
- knife("client list").should_succeed <<~EOM
- car-validator
- cdr
- chef-validator
- chef-webui
- cons
- EOM
- end
-
- it "deletes a validator when told to" do
- knife("client bulk delete ^ca.* -D", input: "Y\nY").should_succeed <<~EOM
- The following validators will be deleted:
-
- car-validator
-
- Are you sure you want to delete these validators? (Y/N) Deleted client car-validator
- The following clients will be deleted:
-
- car cat
-
- Are you sure you want to delete these clients? (Y/N) Deleted client car
- Deleted client cat
- EOM
-
- knife("client list").should_succeed <<~EOM
- cdr
- chef-validator
- chef-webui
- cons
- EOM
- end
- end
-end
diff --git a/spec/integration/knife/client_create_spec.rb b/spec/integration/knife/client_create_spec.rb
deleted file mode 100644
index 2e48cde7ab..0000000000
--- a/spec/integration/knife/client_create_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "openssl"
-
-describe "knife client create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Created client[bah]\n" }
-
- when_the_chef_server "is empty" do
- it "creates a new client" do
- knife("client create -k bah").should_succeed stderr: out
- end
-
- it "creates a new validator client" do
- knife("client create -k --validator bah").should_succeed stderr: out
- knife("client show bah").should_succeed <<~EOM
- admin: false
- chef_type: client
- name: bah
- validator: true
- EOM
- end
-
- it "refuses to add an existing client" do
- pending "Knife client create must not blindly overwrite an existing client"
- knife("client create -k bah").should_succeed stderr: out
- expect { knife("client create -k bah") }.to raise_error(Net::HTTPClientException)
- end
-
- it "saves the private key to a file" do
- Dir.mktmpdir do |tgt|
- knife("client create -f #{tgt}/bah.pem bah").should_succeed stderr: out
- expect(File).to exist("#{tgt}/bah.pem")
- end
- end
-
- it "reads the public key from a file" do
- Dir.mktmpdir do |tgt|
- key = OpenSSL::PKey::RSA.generate(1024)
- File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) }
- knife("client create -p #{tgt}/public.pem bah").should_succeed stderr: out
- end
- end
-
- it "refuses to run if conflicting options are passed" do
- knife("client create -p public.pem --prevent-keygen blah").should_fail stderr: "FATAL: You cannot pass --public-key and --prevent-keygen\n", stdout: /^USAGE.*/
- end
- end
-end
diff --git a/spec/integration/knife/client_delete_spec.rb b/spec/integration/knife/client_delete_spec.rb
deleted file mode 100644
index 76a3b9a686..0000000000
--- a/spec/integration/knife/client_delete_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife client delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some clients" do
- before do
- client "cons", {}
- client "car", {}
- client "car-validator", { validator: true }
- client "cdr", {}
- client "cat", {}
- end
-
- it "deletes a client" do
- knife("client delete car", input: "Y").should_succeed <<~EOM
- Do you really want to delete car? (Y/N) Deleted client[car]
- EOM
-
- knife("client list").should_succeed <<~EOM
- car-validator
- cat
- cdr
- chef-validator
- chef-webui
- cons
- EOM
- end
-
- it "refuses to delete a validator normally" do
- knife("client delete car-validator", input: "Y").should_fail exit_code: 2, stdout: "Do you really want to delete car-validator? (Y/N) ", stderr: <<~EOM
- FATAL: You must specify --delete-validators to delete the validator client car-validator
- EOM
- end
-
- it "deletes a validator correctly" do
- knife("client delete car-validator -D", input: "Y").should_succeed <<~EOM
- Do you really want to delete car-validator? (Y/N) Deleted client[car-validator]
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/client_key_create_spec.rb b/spec/integration/knife/client_key_create_spec.rb
deleted file mode 100644
index b9838d6718..0000000000
--- a/spec/integration/knife/client_key_create_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "openssl"
-
-describe "knife client key create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Created key: new" }
-
- when_the_chef_server "has a client" do
- before do
- client "bah", {}
- end
-
- it "creates a new client key" do
- knife("client key create -k new bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/
- end
-
- it "creates a new client key with an expiration date" do
- date = "2017-12-31T23:59:59Z"
- knife("client key create -k new -e #{date} bah").should_succeed stderr: /^#{out}/, stdout: /.*BEGIN RSA PRIVATE KEY/
- knife("client key show bah new").should_succeed(/expiration_date:.*#{date}/)
- end
-
- it "refuses to add an already existing key" do
- knife("client key create -k new bah")
- expect { knife("client key create -k new bah") }.to raise_error(Net::HTTPClientException)
- end
-
- it "saves the private key to a file" do
- Dir.mktmpdir do |tgt|
- knife("client key create -f #{tgt}/bah.pem -k new bah").should_succeed stderr: /^#{out}/
- expect(File).to exist("#{tgt}/bah.pem")
- end
- end
-
- it "reads the public key from a file" do
- Dir.mktmpdir do |tgt|
- key = OpenSSL::PKey::RSA.generate(1024)
- File.open("#{tgt}/public.pem", "w") { |pub| pub.write(key.public_key.to_pem) }
- knife("client key create -p #{tgt}/public.pem -k new bah").should_succeed stderr: /^#{out}/
- end
- end
-
- end
-end
diff --git a/spec/integration/knife/client_key_delete_spec.rb b/spec/integration/knife/client_key_delete_spec.rb
deleted file mode 100644
index 2730ee8cae..0000000000
--- a/spec/integration/knife/client_key_delete_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife client key delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a client" do
- before do
- client "car", {}
- end
-
- it "deletes a client" do
- out = "Do you really want to delete the key named new for the client named car? (Y/N) "
- knife("client key create -k new car")
- knife("client key delete car new", input: "Y").should_succeed stdout: out, stderr: <<~EOM
- Deleted key named new for the client named car
- EOM
-
- knife("client key list car").should_succeed ""
- end
-
- end
-end
diff --git a/spec/integration/knife/client_key_list_spec.rb b/spec/integration/knife/client_key_list_spec.rb
deleted file mode 100644
index 773445eca9..0000000000
--- a/spec/integration/knife/client_key_list_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "date"
-
-describe "knife client key list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:now) { DateTime.now }
- let(:last_month) { (now << 1).strftime("%FT%TZ") }
- let(:next_month) { (now >> 1).strftime("%FT%TZ") }
-
- when_the_chef_server "has a client" do
- before do
- client "cons", {}
- knife("client key create cons -k new")
- knife("client key create cons -k next_month -e #{next_month}")
- knife("client key create cons -k expired -e #{last_month}")
- end
-
- it "lists the keys for a client" do
- knife("client key list cons").should_succeed "expired\nnew\nnext_month\n"
- end
-
- it "shows detailed output" do
- knife("client key list -w cons").should_succeed <<~EOM
- expired: http://127.0.0.1:8900/clients/cons/keys/expired (expired)
- new: http://127.0.0.1:8900/clients/cons/keys/new
- next_month: http://127.0.0.1:8900/clients/cons/keys/next_month
- EOM
- end
-
- it "lists the expired keys for a client" do
- knife("client key list -e cons").should_succeed "expired\n"
- end
-
- it "lists the unexpired keys for a client" do
- knife("client key list -n cons").should_succeed "new\nnext_month\n"
- end
-
- end
-end
diff --git a/spec/integration/knife/client_key_show_spec.rb b/spec/integration/knife/client_key_show_spec.rb
deleted file mode 100644
index ee17fc3e5a..0000000000
--- a/spec/integration/knife/client_key_show_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "date"
-
-describe "knife client key show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:now) { DateTime.now }
- let(:last_month) { (now << 1).strftime("%FT%TZ") }
- let(:next_month) { (now >> 1).strftime("%FT%TZ") }
-
- when_the_chef_server "has a client" do
- before do
- client "cons", {}
- knife("client key create cons -k new")
- knife("client key create cons -k next_month -e #{next_month}")
- knife("client key create cons -k expired -e #{last_month}")
- end
-
- it "shows a key for a client" do
- knife("client key show cons new").should_succeed stdout: /.*name:.*new/
- end
-
- end
-end
diff --git a/spec/integration/knife/client_list_spec.rb b/spec/integration/knife/client_list_spec.rb
deleted file mode 100644
index f7875b44af..0000000000
--- a/spec/integration/knife/client_list_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife client list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some clients" do
- before do
- client "cons", {}
- client "car", {}
- client "car-validator", { validator: true }
- client "cdr", {}
- client "cat", {}
- end
-
- it "lists the clients" do
- knife("client list").should_succeed <<~EOM
- car
- car-validator
- cat
- cdr
- chef-validator
- chef-webui
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/client_show_spec.rb b/spec/integration/knife/client_show_spec.rb
deleted file mode 100644
index 1520575e48..0000000000
--- a/spec/integration/knife/client_show_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife client show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a client" do
- before do
- client "cons", {}
- end
-
- it "shows a client" do
- knife("client show cons").should_succeed stdout: /.*name:.*cons/
- end
-
- end
-end
diff --git a/spec/integration/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb
deleted file mode 100644
index 468b7af8be..0000000000
--- a/spec/integration/knife/common_options_spec.rb
+++ /dev/null
@@ -1,174 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/raw"
-
-describe "knife common options", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- before do
- # Allow this for testing the various port binding stuffs. Remove when
- # we kill off --listen.
- Chef::Config.treat_deprecation_warnings_as_errors(false)
- end
-
- let(:local_listen_warning) { /\Awarn:.*local.*listen.*$/im }
-
- when_the_repository "has a node" do
- before { file "nodes/x.json", {} }
-
- context "When chef_zero.enabled is true" do
- before(:each) do
- Chef::Config.chef_zero.enabled = true
- end
-
- it "knife raw /nodes/x should retrieve the node in socketless mode" do
- Chef::Config.treat_deprecation_warnings_as_errors(true)
- knife("raw /nodes/x").should_succeed( /"name": "x"/ )
- end
-
- it "knife raw /nodes/x should retrieve the node" do
- knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- end
-
- context "And chef_zero.port is 9999" do
- before(:each) { Chef::Config.chef_zero.port = 9999 }
-
- it "knife raw /nodes/x should retrieve the node" do
- knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
- end
- end
-
- # 0.0.0.0 is not a valid address to bind to on windows.
- context "And chef_zero.host is 0.0.0.0", :unix_only do
- before(:each) { Chef::Config.chef_zero.host = "0.0.0.0" }
-
- it "knife raw /nodes/x should retrieve the role" do
- knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- end
- end
-
- context "and there is a private key" do
- before do
- file "mykey.pem", <<~EOM
- -----BEGIN RSA PRIVATE KEY-----
- MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
- 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
- NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
- 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
- AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
- HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
- 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
- zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
- k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
- i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
- G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
- ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
- awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
- 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
- g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
- Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
- HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
- V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
- fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
- lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
- c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
- fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
- YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
- syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
- +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
- -----END RSA PRIVATE KEY-----
- EOM
- end
-
- it "knife raw /nodes/x should retrieve the node" do
- knife("raw --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- end
- end
- end
-
- it "knife raw -z /nodes/x retrieves the node in socketless mode" do
- Chef::Config.treat_deprecation_warnings_as_errors(true)
- knife("raw -z /nodes/x").should_succeed( /"name": "x"/ )
- end
-
- it "knife raw -z /nodes/x retrieves the node" do
- knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- end
-
- it "knife raw --local-mode /nodes/x retrieves the node" do
- knife("raw --local-mode --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- end
-
- it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do
- knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
- end
-
- context "when the default port (8889) is already bound" do
- before :each do
-
- @server = ChefZero::Server.new(host: "localhost", port: 8889)
- @server.start_background
- rescue Errno::EADDRINUSE
- # OK. Don't care who has it in use, as long as *someone* does.
-
- end
- after :each do
- @server.stop if @server
- end
-
- it "knife raw -z /nodes/x retrieves the node" do
- knife("raw -z --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(URI(Chef::Config.chef_server_url).port).to be > 8889
- end
- end
-
- context "when port 9999 is already bound" do
- before :each do
-
- @server = ChefZero::Server.new(host: "localhost", port: 9999)
- @server.start_background
- rescue Errno::EADDRINUSE
- # OK. Don't care who has it in use, as long as *someone* does.
-
- end
- after :each do
- @server.stop if @server
- end
-
- it "knife raw -z --chef-zero-port=9999-20000 /nodes/x" do
- knife("raw -z --chef-zero-port=9999-20000 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(URI(Chef::Config.chef_server_url).port).to be > 9999
- end
-
- it "knife raw -z --chef-zero-port=9999-9999,19423" do
- knife("raw -z --chef-zero-port=9999-9999,19423 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(URI(Chef::Config.chef_server_url).port).to be == 19423
- end
- end
-
- it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do
- knife("raw -z --chef-zero-port=9999 --listen /nodes/x").should_succeed( /"name": "x"/, stderr: local_listen_warning )
- expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999")
- end
- end
-end
diff --git a/spec/integration/knife/config_list_spec.rb b/spec/integration/knife/config_list_spec.rb
deleted file mode 100644
index b05350ed87..0000000000
--- a/spec/integration/knife/config_list_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-#
-# Copyright 2018, Noah Kantrowitz
-#
-# 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife config list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_repository("has a custom env") do
- let(:cmd_args) { [] }
- let(:knife_list) do
- knife("config", "list", *cmd_args, instance_filter: lambda { |instance|
- # Fake the failsafe check because this command doesn't actually process knife.rb.
- $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
- allow(File).to receive(:file?).and_call_original
- })
- end
- subject { knife_list.stdout }
-
- around do |ex|
- # Store and reset the value of some env vars.
- old_home = ENV["HOME"]
- old_wd = Dir.pwd
- # Clear these out because they are cached permanently.
- ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
- Chef::Knife::ConfigList.reset_config_loader!
- begin
- ex.run
- ensure
- ENV["HOME"] = old_home
- Dir.chdir(old_wd)
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- end
- end
-
- before do
- # Always run from the temp folder. This can't be in the `around` block above
- # because it has to run after the before set in the "with a chef repo" shared context.
- directory("repo")
- Dir.chdir(path_to("repo"))
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- ENV["HOME"] = path_to(".")
- allow(TTY::Screen).to receive(:width).and_return(200)
- end
-
- # NOTE: The funky formatting with # at the end of the line of some of the
- # output examples are because of how the format strings are built, there is
- # substantial trailing whitespace in most cases which many editors "helpfully" remove.
-
- context "with no credentials file" do
- subject { knife_list.stderr }
- it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
- end
-
- context "with an empty credentials file" do
- before { file(".chef/credentials", "") }
- subject { knife_list.stderr }
- it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
- end
-
- context "with a simple default profile" do
- before { file(".chef/credentials", <<~EOH) }
- [default]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to eq <<~EOH.delete("#") }
- Profile Client Key Server #
- --------------------------------------------------------------------------------#
- *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
- EOH
- end
-
- context "with multiple profiles" do
- before { file(".chef/credentials", <<~EOH) }
- [default]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
-
- [prod]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/prod"
-
- [qa]
- client_name = "qauser"
- client_key = "~/src/qauser.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to eq <<~EOH.delete("#") }
- Profile Client Key Server #
- --------------------------------------------------------------------------------#
- *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
- prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
- qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
- EOH
- end
-
- context "with a non-default active profile" do
- let(:cmd_args) { %w{--profile prod} }
- before { file(".chef/credentials", <<~EOH) }
- [default]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
-
- [prod]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/prod"
-
- [qa]
- client_name = "qauser"
- client_key = "~/src/qauser.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to eq <<~EOH.delete("#") }
- Profile Client Key Server #
- --------------------------------------------------------------------------------#
- default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
- *prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
- qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
- EOH
- end
-
- context "with a bad profile as an active profile" do
- let(:cmd_args) { %w{--profile production} }
- before { file(".chef/credentials", <<~EOH) }
- [default]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
-
- [prod]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/prod"
-
- [qa]
- client_name = "qauser"
- client_key = "~/src/qauser.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to eq <<~EOH.delete("#") }
- Profile Client Key Server #
- --------------------------------------------------------------------------------#
- default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg #
- prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod #
- qa qauser ~/src/qauser.pem https://example.com/organizations/testorg #
- EOH
- end
-
- context "with a minimal profile" do
- before { file(".chef/credentials", <<~EOH) }
- [default]
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to match %r{^*default .*? https://example.com/organizations/testorg} }
- end
-
- context "with -i" do
- let(:cmd_args) { %w{-i} }
- before { file(".chef/credentials", <<~EOH) }
- [default]
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it { is_expected.to eq <<~EOH.delete("#") }
- Profile Client Key Server #
- --------------------------------------------------------------#
- *default https://example.com/organizations/testorg #
- EOH
- end
-
- context "with --format=json" do
- let(:cmd_args) { %w{--format=json node_name} }
- before { file(".chef/credentials", <<~EOH) }
- [default]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
-
- [prod]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/prod"
-
- [qa]
- client_name = "qauser"
- client_key = "~/src/qauser.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it {
- expect(JSON.parse(subject)).to eq [
- { "profile" => "default", "active" => true, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/testorg" },
- { "profile" => "prod", "active" => false, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/prod" },
- { "profile" => "qa", "active" => false, "client_name" => "qauser", "client_key" => path_to("src/qauser.pem"), "server_url" => "https://example.com/organizations/testorg" },
- ]
- }
- end
- end
-end
diff --git a/spec/integration/knife/config_show_spec.rb b/spec/integration/knife/config_show_spec.rb
deleted file mode 100644
index 9e6ff73aa1..0000000000
--- a/spec/integration/knife/config_show_spec.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-#
-# Copyright 2018, Noah Kantrowitz
-#
-# 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife config show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:cmd_args) { [] }
-
- when_the_repository("has a custom env") do
- subject do
- cmd = knife("config", "show", *cmd_args, instance_filter: lambda { |instance|
- # Clear the stub set up in KnifeSupport.
- allow(File).to receive(:file?).and_call_original
- # Lies, damn lies, and config files. We need to allow normal config loading
- # behavior to be able to test stuff.
- instance.config.delete(:config_file)
- $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
- })
- cmd.stdout
- end
-
- around do |ex|
- # Store and reset the value of some env vars.
- old_chef_home = ENV["CHEF_HOME"]
- old_knife_home = ENV["KNIFE_HOME"]
- old_home = ENV["HOME"]
- old_wd = Dir.pwd
- ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
- # Clear these out because they are cached permanently.
- ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
- Chef::Knife::ConfigShow.reset_config_loader!
- begin
- ex.run
- ensure
- ENV["CHEF_HOME"] = old_chef_home
- ENV["KNIFE_HOME"] = old_knife_home
- ENV["HOME"] = old_home
- Dir.chdir(old_wd)
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- ChefConfig::PathHelper.per_tool_home_environment = nil
- end
- end
-
- before do
- # Always run from the temp folder. This can't be in the `around` block above
- # because it has to run after the before set in the "with a chef repo" shared context.
- directory("repo")
- Dir.chdir(path_to("repo"))
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- ENV["HOME"] = path_to(".")
- end
-
- context "with a global knife.rb" do
- before { file(".chef/knife.rb", "node_name 'one'\n") }
-
- it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) }
- it { is_expected.to match(/^node_name:\s+one$/) }
- end
-
- context "with a repo knife.rb" do
- before { file("repo/.chef/knife.rb", "node_name 'two'\n") }
-
- it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) }
- it { is_expected.to match(/^node_name:\s+two$/) }
- end
-
- context "with both knife.rb" do
- before do
- file(".chef/knife.rb", "node_name 'one'\n")
- file("repo/.chef/knife.rb", "node_name 'two'\n")
- end
-
- it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/repo/.chef/knife.rb$}) }
- it { is_expected.to match(/^node_name:\s+two$/) }
- end
-
- context "with a credentials file" do
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) }
- it { is_expected.to match(/^node_name:\s+three$/) }
- end
-
- context "with a credentials file and knife.rb" do
- before do
- file(".chef/knife.rb", "node_name 'one'\n")
- file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
- end
-
- it { is_expected.to match(%r{^Loading from configuration file .*/#{File.basename(path_to("."))}/.chef/knife.rb$}) }
- it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/.chef/credentials$}) }
- it { is_expected.to match(/^node_name:\s+one$/) }
- end
-
- context "with a config dot d files" do
- before { file(".chef/config.d/abc.rb", "node_name 'one'\n") }
-
- it { is_expected.to match(%r{^Loading from .d/ configuration file .*/#{File.basename(path_to("."))}/.chef/config.d/abc.rb$}) }
- it { is_expected.to match(/^node_name:\s+one$/) }
- end
-
- context "with a credentials file and CHEF_HOME" do
- before do
- file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
- file("foo/.chef/credentials", "[default]\nclient_name = \"four\"\n")
- ENV["CHEF_HOME"] = path_to("foo")
- end
-
- it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/foo/.chef/credentials$}) }
- it { is_expected.to match(/^node_name:\s+four$/) }
- end
-
- context "with a credentials file and KNIFE_HOME" do
- before do
- file(".chef/credentials", "[default]\nclient_name = \"three\"\n")
- file("bar/.chef/credentials", "[default]\nclient_name = \"four\"\n")
- ENV["KNIFE_HOME"] = path_to("bar")
- end
-
- it { is_expected.to match(%r{^Loading from credentials file .*/#{File.basename(path_to("."))}/bar/.chef/credentials$}) }
- it { is_expected.to match(/^node_name:\s+four$/) }
- end
-
- context "with single argument" do
- let(:cmd_args) { %w{node_name} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { is_expected.to match(/^node_name:\s+three\Z/) }
- end
-
- context "with two arguments" do
- let(:cmd_args) { %w{node_name client_key} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\nclient_key = \"three.pem\"") }
-
- it { is_expected.to match(%r{^client_key:\s+\S*/.chef/three.pem\nnode_name:\s+three\Z}) }
- end
-
- context "with a dotted argument" do
- let(:cmd_args) { %w{knife.ssh_user} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n[default.knife]\nssh_user = \"foo\"\n") }
-
- it { is_expected.to match(/^knife.ssh_user:\s+foo\Z/) }
- end
-
- context "with regex argument" do
- let(:cmd_args) { %w{/name/} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { is_expected.to match(/^node_name:\s+three\Z/) }
- end
-
- context "with --all" do
- let(:cmd_args) { %w{-a /key_contents/} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { is_expected.to match(/^client_key_contents:\s+\nvalidation_key_contents:\s+\Z/) }
- end
-
- context "with --raw" do
- let(:cmd_args) { %w{-r node_name} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { is_expected.to eq("three\n") }
- end
-
- context "with --format=json" do
- let(:cmd_args) { %w{--format=json node_name} }
- before { file(".chef/credentials", "[default]\nclient_name = \"three\"\n") }
-
- it { expect(JSON.parse(subject)).to eq({ "node_name" => "three" }) }
- end
- end
-end
diff --git a/spec/integration/knife/config_use_spec.rb b/spec/integration/knife/config_use_spec.rb
deleted file mode 100644
index 0431729b25..0000000000
--- a/spec/integration/knife/config_use_spec.rb
+++ /dev/null
@@ -1,198 +0,0 @@
-#
-# Copyright 2018, Noah Kantrowitz
-#
-# 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife config use", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:cmd_args) { [] }
-
- when_the_repository("has a custom env") do
- let(:knife_use) do
- knife("config", "use", *cmd_args, instance_filter: lambda { |instance|
- # Fake the failsafe check because this command doesn't actually process knife.rb.
- $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
- allow(File).to receive(:file?).and_call_original
- })
- end
-
- subject { knife_use.stdout }
-
- around do |ex|
- # Store and reset the value of some env vars.
- old_chef_home = ENV["CHEF_HOME"]
- old_knife_home = ENV["KNIFE_HOME"]
- old_home = ENV["HOME"]
- old_wd = Dir.pwd
- ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME"
- # Clear these out because they are cached permanently.
- ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) }
- Chef::Knife::ConfigUse.reset_config_loader!
- begin
- ex.run
- ensure
- ENV["CHEF_HOME"] = old_chef_home
- ENV["KNIFE_HOME"] = old_knife_home
- ENV["HOME"] = old_home
- Dir.chdir(old_wd)
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- ChefConfig::PathHelper.per_tool_home_environment = nil
- end
- end
-
- before do
- # Always run from the temp folder. This can't be in the `around` block above
- # because it has to run after the before set in the "with a chef repo" shared context.
- directory("repo")
- Dir.chdir(path_to("repo"))
- ENV[ChefUtils.windows? ? "CD" : "PWD"] = Dir.pwd
- ENV["HOME"] = path_to(".")
- end
-
- context "with no argument" do
- context "with no configuration" do
- it { is_expected.to eq "default\n" }
- end
-
- context "with --profile" do
- let(:cmd_args) { %w{--profile production} }
- it { is_expected.to eq "production\n" }
- end
-
- context "with an environment variable" do
- around do |ex|
- old_chef_profile = ENV["CHEF_PROFILE"]
- begin
- ENV["CHEF_PROFILE"] = "staging"
- ex.run
- ensure
- ENV["CHEF_PROFILE"] = old_chef_profile
- end
- end
-
- it { is_expected.to eq "staging\n" }
- end
-
- context "with a context file" do
- before { file(".chef/context", "development\n") }
- it { is_expected.to eq "development\n" }
- end
-
- context "with a context file under $CHEF_HOME" do
- before do
- file("chefhome/.chef/context", "other\n")
- ENV["CHEF_HOME"] = path_to("chefhome")
- end
-
- it { is_expected.to eq "other\n" }
- end
-
- context "with a context file under $KNIFE_HOME" do
- before do
- file("knifehome/.chef/context", "other\n")
- ENV["KNIFE_HOME"] = path_to("knifehome")
- end
-
- it { is_expected.to eq "other\n" }
- end
- end
-
- context "with an argument" do
- let(:cmd_args) { %w{production} }
- before { file(".chef/credentials", <<~EOH) }
- [production]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- it do
- is_expected.to eq "Set default profile to production\n"
- expect(File.read(path_to(".chef/context"))).to eq "production\n"
- end
- end
-
- context "with no credentials file" do
- let(:cmd_args) { %w{production} }
- subject { knife_use.stderr }
- it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
- end
-
- context "with an empty credentials file" do
- let(:cmd_args) { %w{production} }
- before { file(".chef/credentials", "") }
- subject { knife_use.stderr }
- it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" }
- end
-
- context "with an wrong argument" do
- let(:cmd_args) { %w{staging} }
- before { file(".chef/credentials", <<~EOH) }
- [production]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- subject { knife_use }
- it { expect { subject }.to raise_error ChefConfig::ConfigurationError, "Profile staging doesn't exist. Please add it to #{path_to(".chef/credentials")} and if it is profile with DNS name check that you are not missing single quotes around it as per docs https://docs.chef.io/workstation/knife_setup/#knife-profiles." }
- end
-
- context "with $CHEF_HOME" do
- let(:cmd_args) { %w{staging} }
- before do
- ENV["CHEF_HOME"] = path_to("chefhome"); file("chefhome/tmp", "")
- file("chefhome/.chef/credentials", <<~EOH
- [staging]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- )
- end
-
- it do
- is_expected.to eq "Set default profile to staging\n"
- expect(File.read(path_to("chefhome/.chef/context"))).to eq "staging\n"
- expect(File.exist?(path_to(".chef/context"))).to be_falsey
- end
- end
-
- context "with $KNIFE_HOME" do
- let(:cmd_args) { %w{development} }
-
- before do
- ENV["KNIFE_HOME"] = path_to("knifehome"); file("knifehome/tmp", "")
- file("knifehome/.chef/credentials", <<~EOH
- [development]
- client_name = "testuser"
- client_key = "testkey.pem"
- chef_server_url = "https://example.com/organizations/testorg"
- EOH
- )
- end
-
- it do
- is_expected.to eq "Set default profile to development\n"
- expect(File.read(path_to("knifehome/.chef/context"))).to eq "development\n"
- expect(File.exist?(path_to(".chef/context"))).to be_falsey
- end
- end
- end
-end
diff --git a/spec/integration/knife/cookbook_api_ipv6_spec.rb b/spec/integration/knife/cookbook_api_ipv6_spec.rb
deleted file mode 100644
index b65cdc697b..0000000000
--- a/spec/integration/knife/cookbook_api_ipv6_spec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/mixin/shell_out"
-
-describe "Knife cookbook API integration with IPv6", :workstation, :not_supported_on_gce do
- include IntegrationSupport
- include Chef::Mixin::ShellOut
-
- when_the_chef_server "is bound to IPv6" do
- let(:chef_zero_opts) { { host: "::1" } }
-
- let(:client_key) do
- <<~END_VALIDATION_PEM
- -----BEGIN RSA PRIVATE KEY-----
- MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
- 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
- NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
- 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
- AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
- HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
- 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
- zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
- k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
- i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
- G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
- ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
- awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
- 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
- g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
- Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
- HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
- V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
- fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
- lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
- c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
- fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
- YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
- syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
- +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
- -----END RSA PRIVATE KEY-----
- END_VALIDATION_PEM
- end
-
- let(:cache_path) do
- Dir.mktmpdir
- end
-
- let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
- let(:knife) { "ruby '#{chef_dir}/knife'" }
-
- let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" }
-
- # Some Solaris test platforms are too old for IPv6. These tests should not
- # otherwise be platform dependent, so exclude solaris
- context "and the chef_server_url contains an IPv6 literal", :not_supported_on_solaris do
-
- # This provides helper functions we need such as #path_to()
- when_the_repository "has the cookbook to be uploaded" do
-
- let(:knife_rb_content) do
- <<~END_CLIENT_RB
- chef_server_url "http://[::1]:8900"
- syntax_check_cache_path '#{cache_path}'
- client_key '#{path_to("config/knifeuser.pem")}'
- node_name 'whoisthisis'
- cookbook_path '#{CHEF_SPEC_DATA}/cookbooks'
- END_CLIENT_RB
- end
-
- before do
- file "config/knife.rb", knife_rb_content
- file "config/knifeuser.pem", client_key
- end
-
- it "successfully uploads a cookbook" do
- shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir)
- versions_list_json = Chef::HTTP::Simple.new("http://[::1]:8900").get("/cookbooks/apache2", "accept" => "application/json")
- versions_list = Chef::JSONCompat.from_json(versions_list_json)
- expect(versions_list["apache2"]["versions"]).not_to be_empty
- end
-
- context "and the cookbook has been uploaded to the server" do
- before do
- shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", cwd: chef_dir)
- end
-
- it "downloads the cookbook" do
- shell_out!("knife cookbook download apache2 #{knife_config_flag} -d #{cache_path}", cwd: chef_dir)
- expect(Dir["#{cache_path}/*"].map { |entry| File.basename(entry) }).to include("apache2-0.0.1")
- end
- end
-
- end
- end
- end
-end
diff --git a/spec/integration/knife/cookbook_bulk_delete_spec.rb b/spec/integration/knife/cookbook_bulk_delete_spec.rb
deleted file mode 100644
index 677a6aaa31..0000000000
--- a/spec/integration/knife/cookbook_bulk_delete_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/cookbook_bulk_delete"
-
-describe "knife cookbook bulk delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "foo", "1.0.0"
- cookbook "foo", "0.6.5"
- cookbook "fox", "0.6.0"
- cookbook "fox", "0.6.5"
- cookbook "fax", "0.6.0"
- cookbook "zfa", "0.6.5"
- end
-
- # rubocop:disable Layout/TrailingWhitespace
- it "knife cookbook bulk delete deletes all matching cookbooks" do
- stdout = <<~EOM
- All versions of the following cookbooks will be deleted:
-
- foo fox
-
- Do you really want to delete these cookbooks? (Y/N)
- EOM
-
- stderr = <<~EOM
- Deleted cookbook foo [1.0.0]
- Deleted cookbook foo [0.6.5]
- Deleted cookbook fox [0.6.5]
- Deleted cookbook fox [0.6.0]
- EOM
-
- knife("cookbook bulk delete ^fo.*", input: "Y").should_succeed(stderr: stderr, stdout: stdout)
-
- knife("cookbook list -a").should_succeed <<~EOM
- fax 0.6.0
- zfa 0.6.5
- EOM
- end
- # rubocop:enable Layout/TrailingWhitespace
-
- end
-end
diff --git a/spec/integration/knife/cookbook_download_spec.rb b/spec/integration/knife/cookbook_download_spec.rb
deleted file mode 100644
index 1cc05c909a..0000000000
--- a/spec/integration/knife/cookbook_download_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/cookbook_download"
-require "tmpdir"
-
-describe "knife cookbook download", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:tmpdir) { Dir.mktmpdir }
-
- when_the_chef_server "has only one cookbook" do
- before do
- cookbook "x", "1.0.1"
- end
-
- it "knife cookbook download downloads the latest version" do
- knife("cookbook download -d #{tmpdir} x").should_succeed stderr: <<~EOM
- Downloading x cookbook version 1.0.1
- Downloading root_files
- Cookbook downloaded to #{tmpdir}/x-1.0.1
- EOM
- end
-
- it "knife cookbook download with a version downloads the specified version" do
- knife("cookbook download -d #{tmpdir} x 1.0.1").should_succeed stderr: <<~EOM
- Downloading x cookbook version 1.0.1
- Downloading root_files
- Cookbook downloaded to #{tmpdir}/x-1.0.1
- EOM
- end
-
- it "knife cookbook download with an unknown version raises an error" do
- expect { knife("cookbook download -d #{tmpdir} x 1.0.0") }.to raise_error(Net::HTTPClientException)
- end
- end
-
- when_the_chef_server "has multiple cookbook versions" do
- before do
- cookbook "x", "1.0.1"
- cookbook "x", "1.0.0"
- end
-
- it "knife cookbook download with no version prompts" do
- knife("cookbook download -d #{tmpdir} x", input: "2\n").should_succeed(stderr: <<~EOM, stdout: "Which version do you want to download?\n1. x 1.0.0\n2. x 1.0.1\n\n"
- Downloading x cookbook version 1.0.1
- Downloading root_files
- Cookbook downloaded to #{tmpdir}/x-1.0.1
- EOM
- )
- end
- end
-end
diff --git a/spec/integration/knife/cookbook_list_spec.rb b/spec/integration/knife/cookbook_list_spec.rb
deleted file mode 100644
index c94df52272..0000000000
--- a/spec/integration/knife/cookbook_list_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/cookbook_list"
-
-describe "knife cookbook list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "x", "1.0.0"
- cookbook "x", "0.6.5"
- cookbook "x", "0.6.0"
- cookbook "y", "0.6.5"
- cookbook "y", "0.6.0"
- cookbook "z", "0.6.5"
- end
-
- it "knife cookbook list shows all the cookbooks" do
- knife("cookbook list").should_succeed <<~EOM
- x 1.0.0
- y 0.6.5
- z 0.6.5
- EOM
- end
-
- it "knife cookbook list -a shows all the versions of all the cookbooks" do
- knife("cookbook list -a").should_succeed <<~EOM
- x 1.0.0 0.6.5 0.6.0
- y 0.6.5 0.6.0
- z 0.6.5
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/cookbook_show_spec.rb b/spec/integration/knife/cookbook_show_spec.rb
deleted file mode 100644
index 57701d4426..0000000000
--- a/spec/integration/knife/cookbook_show_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/cookbook_show"
-
-describe "knife cookbook show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "file 'n'", "x.rb" => "" } }
- cookbook "x", "0.6.5"
- end
-
- it "knife cookbook show x shows all the versions" do
- knife("cookbook show x").should_succeed "x 1.0.0 0.6.5\n"
- end
-
- # rubocop:disable Layout/TrailingWhitespace
- it "knife cookbook show x 1.0.0 shows the correct version" do
- knife("cookbook show x 1.0.0").should_succeed <<~EOM
- cookbook_name: x
- frozen?: false
- metadata:
- chef_versions:
- dependencies:
- description:
- eager_load_libraries: true
- gems:
- issues_url:
- license: All rights reserved
- long_description:
- maintainer:
- maintainer_email:
- name: x
- ohai_versions:
- platforms:
- privacy: false
- providing:
- x: >= 0.0.0
- x::x: >= 0.0.0
- recipes:
- x:
- x::x:
- source_url:
- version: 1.0.0
- name: x-1.0.0
- recipes:
- checksum: 4631b34cf58de10c5ef1304889941b2e
- name: recipes/default.rb
- path: recipes/default.rb
- specificity: default
- url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e
-
- checksum: d41d8cd98f00b204e9800998ecf8427e
- name: recipes/x.rb
- path: recipes/x.rb
- specificity: default
- url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e
- root_files:
- checksum: 8226671f751ba102dea6a6b6bd32fa8d
- name: metadata.rb
- path: metadata.rb
- specificity: default
- url: http://127.0.0.1:8900/file_store/checksums/8226671f751ba102dea6a6b6bd32fa8d
- version: 1.0.0
- EOM
- end
-
- it "knife cookbook show x 1.0.0 metadata shows the metadata" do
- knife("cookbook show x 1.0.0 metadata").should_succeed <<~EOM
- chef_versions:
- dependencies:
- description:
- eager_load_libraries: true
- gems:
- issues_url:
- license: All rights reserved
- long_description:
- maintainer:
- maintainer_email:
- name: x
- ohai_versions:
- platforms:
- privacy: false
- providing:
- x: >= 0.0.0
- x::x: >= 0.0.0
- recipes:
- x:
- x::x:
- source_url:
- version: 1.0.0
- EOM
- end
-
- it "knife cookbook show x 1.0.0 recipes shows all the recipes" do
- knife("cookbook show x 1.0.0 recipes").should_succeed <<~EOM
- checksum: 4631b34cf58de10c5ef1304889941b2e
- name: recipes/default.rb
- path: recipes/default.rb
- specificity: default
- url: http://127.0.0.1:8900/file_store/checksums/4631b34cf58de10c5ef1304889941b2e
-
- checksum: d41d8cd98f00b204e9800998ecf8427e
- name: recipes/x.rb
- path: recipes/x.rb
- specificity: default
- url: http://127.0.0.1:8900/file_store/checksums/d41d8cd98f00b204e9800998ecf8427e
- EOM
- end
- # rubocop:enable Layout/TrailingWhitespace
-
- it "knife cookbook show x 1.0.0 recipes default.rb shows the default recipe" do
- knife("cookbook show x 1.0.0 recipes default.rb").should_succeed "file 'n'\n"
- end
-
- it "knife cookbook show with a non-existent file displays an error" do
- expect { knife("cookbook show x 1.0.0 recipes moose.rb") }.to raise_error(Chef::Exceptions::FileNotFound)
- end
-
- it "knife cookbook show with a non-existent version displays an error" do
- expect { knife("cookbook show x 1.0.1") }.to raise_error(Net::HTTPClientException)
- end
-
- it "knife cookbook show with a non-existent cookbook displays an error" do
- expect { knife("cookbook show y") }.to raise_error(Net::HTTPClientException)
- end
- end
-end
diff --git a/spec/integration/knife/cookbook_upload_spec.rb b/spec/integration/knife/cookbook_upload_spec.rb
deleted file mode 100644
index 7139f0accd..0000000000
--- a/spec/integration/knife/cookbook_upload_spec.rb
+++ /dev/null
@@ -1,128 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/cookbook_upload"
-
-describe "knife cookbook upload", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:cb_dir) { "#{@repository_dir}/cookbooks" }
-
- when_the_chef_server "is empty" do
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- end
-
- it "knife cookbook upload uploads the cookbook" do
- knife("cookbook upload x -o #{cb_dir}").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploaded 1 cookbook.
- EOM
- end
-
- it "knife cookbook upload --freeze uploads and freezes the cookbook" do
- knife("cookbook upload x -o #{cb_dir} --freeze").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploaded 1 cookbook.
- EOM
- # Modify the file, attempt to reupload
- file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different'
- knife("cookbook upload x -o #{cb_dir} --freeze").should_fail stderr: <<~EOM
- Uploading x [1.0.0]
- ERROR: Version 1.0.0 of cookbook x is frozen. Use --force to override.
- WARNING: Not updating version constraints for x in the environment as the cookbook is frozen.
- ERROR: Failed to upload 1 cookbook.
- EOM
- end
- end
-
- when_the_repository "has a cookbook that depends on another cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\ndepends 'y'")
- file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
- end
-
- it "knife cookbook upload --include-dependencies uploads both cookbooks" do
- knife("cookbook upload --include-dependencies x -o #{cb_dir}").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploading y [1.0.0]
- Uploaded 2 cookbooks.
- EOM
- end
-
- it "knife cookbook upload fails due to missing dependencies" do
- knife("cookbook upload x -o #{cb_dir}").should_fail stderr: <<~EOM
- Uploading x [1.0.0]
- ERROR: Cookbook x depends on cookbooks which are not currently
- ERROR: being uploaded and cannot be found on the server.
- ERROR: The missing cookbook(s) are: 'y' version '>= 0.0.0'
- EOM
- end
-
- it "knife cookbook upload -a uploads both cookbooks" do
- knife("cookbook upload -a -o #{cb_dir}").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploading y [1.0.0]
- Uploaded all cookbooks.
- EOM
- end
- end
-
- when_the_repository "has cookbook metadata without name attribute in metadata file" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0")
- end
-
- it "knife cookbook upload x " do
- expect { knife("cookbook upload x -o #{cb_dir}") }.to raise_error(Chef::Exceptions::MetadataNotValid)
- end
- end
-
- when_the_repository "has cookbooks at multiple paths" do
-
- let(:cb_dir_first) do
- File.join(@repository_dir, "cookbooks")
- .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
- end
-
- let(:cb_dir_second) do
- File.join(@repository_dir, "test_cookbooks")
- .gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
- end
-
- before(:each) do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "test_cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
- end
-
- it "knife cookbook upload with -o or --cookbook-path" do
- knife("cookbook upload x y -o #{cb_dir_first}#{File::PATH_SEPARATOR}#{cb_dir_second}").should_succeed stderr: <<~EOM
- Uploading x [1.0.0]
- Uploading y [1.0.0]
- Uploaded 2 cookbooks.
- EOM
- end
-
- end
- end
-end
diff --git a/spec/integration/knife/data_bag_create_spec.rb b/spec/integration/knife/data_bag_create_spec.rb
deleted file mode 100644
index ca01a2d8ab..0000000000
--- a/spec/integration/knife/data_bag_create_spec.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/data_bag_create"
-
-describe "knife data bag create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:err) { "Created data_bag[foo]\n" }
- let(:out) { "Created data_bag_item[bar]\n" }
- let(:exists) { "Data bag foo already exists\n" }
- let(:secret) { "abc" }
-
- when_the_chef_server "is empty" do
- context "with encryption key" do
- it "creates a new data bag and item" do
- pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" })
- allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
- knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err
- expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n")
- expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n")
- end
-
- it "creates a new data bag and an empty item" do
- knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: err
- expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
- expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n")
- end
- end
-
- context "without encryption key" do
- it "creates a new data bag" do
- knife("data bag create foo").should_succeed stderr: err
- expect(knife("data bag show foo").stderr).to eq("")
- end
-
- it "creates a new data bag and item" do
- knife("data bag create foo bar").should_succeed stdout: out, stderr: err
- expect(knife("data bag show foo").stdout).to eq("bar\n")
- end
- end
- end
-
- when_the_chef_server "has some data bags" do
- before do
- data_bag "foo", {}
- data_bag "bag", { "box" => {} }
- end
-
- context "with encryption key" do
- it "creates a new data bag and item" do
- pretty_json = Chef::JSONCompat.to_json_pretty({ id: "bar", test: "pass" })
- allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
- knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM
- Created data_bag[rocket]
- EOM
- expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("Encrypted data bag detected, decrypting with provided secret.\n")
- expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\ntest: pass\n")
- end
-
- it "creates a new data bag and an empty item" do
- knife("data bag create rocket bar --secret #{secret}").should_succeed stdout: out, stderr: <<~EOM
- Created data_bag[rocket]
- EOM
- expect(knife("data bag show rocket bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
- expect(knife("data bag show rocket bar --secret #{secret}").stdout).to eq("id: bar\n")
- end
-
- it "adds a new item to an existing bag" do
- knife("data bag create foo bar --secret #{secret}").should_succeed stdout: out, stderr: exists
- expect(knife("data bag show foo bar --secret #{secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
- expect(knife("data bag show foo bar --secret #{secret}").stdout).to eq("id: bar\n")
- end
-
- it "fails to add an existing item" do
- expect { knife("data bag create bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
- end
- end
-
- context "without encryption key" do
- it "creates a new data bag" do
- knife("data bag create rocket").should_succeed stderr: <<~EOM
- Created data_bag[rocket]
- EOM
- end
-
- it "creates a new data bag and item" do
- knife("data bag create rocket bar").should_succeed stdout: out, stderr: <<~EOM
- Created data_bag[rocket]
- EOM
- end
-
- it "adds a new item to an existing bag" do
- knife("data bag create foo bar").should_succeed stdout: out, stderr: exists
- end
-
- it "refuses to create an existing data bag" do
- knife("data bag create foo").should_succeed stderr: exists
- end
-
- it "fails to add an existing item" do
- expect { knife("data bag create bag box") }.to raise_error(Net::HTTPClientException)
- end
- end
- end
-end
diff --git a/spec/integration/knife/data_bag_delete_spec.rb b/spec/integration/knife/data_bag_delete_spec.rb
deleted file mode 100644
index c0a17779b9..0000000000
--- a/spec/integration/knife/data_bag_delete_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/data_bag_delete"
-
-describe "knife data bag delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some data bags" do
- before do
- data_bag "x", {}
- data_bag "canteloupe", {}
- data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
- end
-
- it "with an empty data bag" do
- knife("data bag delete canteloupe", input: "y").should_succeed <<~EOM
- Do you really want to delete canteloupe? (Y/N) Deleted data_bag[canteloupe]
- EOM
- end
-
- it "with a bag with some items" do
- knife("data bag delete rocket", input: "y").should_succeed <<~EOM
- Do you really want to delete rocket? (Y/N) Deleted data_bag[rocket]
- EOM
- end
-
- it "with a single item" do
- knife("data bag delete rocket falcon9", input: "y").should_succeed <<~EOM
- Do you really want to delete falcon9? (Y/N) Deleted data_bag_item[falcon9]
- EOM
- end
-
- it "choosing not to delete" do
- knife("data bag delete rocket falcon9", input: "n").should_succeed <<~EOM, exit_code: 3
- Do you really want to delete falcon9? (Y/N) You said no, so I'm done here.
- EOM
- end
- end
-end
diff --git a/spec/integration/knife/data_bag_edit_spec.rb b/spec/integration/knife/data_bag_edit_spec.rb
deleted file mode 100644
index 1063b5d14f..0000000000
--- a/spec/integration/knife/data_bag_edit_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/data_bag_edit"
-
-describe "knife data bag edit", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Saved data_bag_item[box]\n" }
- let(:err) { "Saving data bag unencrypted. To encrypt it, provide an appropriate secret.\n" }
- let(:secret) { "abc" }
- let(:encrypt) { "Encrypted data bag detected, decrypting with provided secret.\n" }
-
- when_the_chef_server "is empty" do
- context "with encryption key" do
- it "fails to edit an item" do
- expect { knife("data bag edit bag box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
- end
- end
-
- context "without encryption key" do
- it "fails to edit an item" do
- expect { knife("data bag edit bag box") }.to raise_error(Net::HTTPClientException)
- end
- end
- end
-
- when_the_chef_server "has some data bags" do
- before do
- data_bag "foo", {}
- data_bag "bag", { "box" => {} }
- data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
- data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } }
- end
-
- context "with encryption key" do
- it "fails to edit a non-existing item" do
- expect { knife("data bag edit foo box --secret #{secret}") }.to raise_error(Net::HTTPClientException)
- end
-
- it "edits an encrypted data bag item" do
- pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", foo: "bar" })
- allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
- knife("data bag edit encrypt box --secret #{secret}")
- knife("data bag show encrypt box --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM
- foo: bar
- id: box
- EOM
- end
-
- it "encrypts an unencrypted data bag item" do
- knife("data bag edit rocket falcon9 --secret #{secret}")
- knife("data bag show rocket falcon9 --secret #{secret}").should_succeed stderr: encrypt, stdout: <<~EOM
- heavy: true
- id: falcon9
- EOM
- end
- end
-
- context "without encryption key" do
- it "fails to edit a non-existing item" do
- expect { knife("data bag edit foo box") }.to raise_error(Net::HTTPClientException)
- end
- it "edits an empty data bag item" do
- pretty_json = Chef::JSONCompat.to_json_pretty({ id: "box", ab: "abc" })
- allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
- knife("data bag edit bag box").should_succeed stderr: err, stdout: out
- knife("data bag show bag box").should_succeed <<~EOM
- ab: abc
- id: box
- EOM
- end
- it "edits a non-empty data bag item" do
- pretty_json = Chef::JSONCompat.to_json_pretty({ id: "falcon9", heavy: false })
- allow(Chef::JSONCompat).to receive(:to_json_pretty).and_return(pretty_json)
- knife("data bag edit rocket falcon9").should_succeed stderr: err, stdout: <<~EOM
- Saved data_bag_item[falcon9]
- EOM
- knife("data bag show rocket falcon9").should_succeed <<~EOM
- heavy: false
- id: falcon9
- EOM
- end
- end
- end
-end
diff --git a/spec/integration/knife/data_bag_from_file_spec.rb b/spec/integration/knife/data_bag_from_file_spec.rb
deleted file mode 100644
index 93801226d0..0000000000
--- a/spec/integration/knife/data_bag_from_file_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife data bag from file", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:db_dir) { "#{@repository_dir}/data_bags" }
-
- when_the_chef_server "has an empty data bag" do
- before do
- data_bag "foo", {}
- data_bag "bar", {}
- end
-
- when_the_repository "has some data bag items" do
- before do
- file "data_bags/foo/bar.json", { "id" => "bar", "foo" => "bar " }
- file "data_bags/foo/bzr.json", { "id" => "bzr", "foo" => "bar " }
- file "data_bags/foo/cat.json", { "id" => "cat", "foo" => "bar " }
- file "data_bags/foo/dog.json", { "id" => "dog", "foo" => "bar " }
- file "data_bags/foo/encrypted.json", <<~EOM
- {
- "id": "encrypted",
- "password": {
- "encrypted_data": "H6ab5RY9a9JAkS8A0RCMspXtOJh0ai8cNeA4Q3gLO8s=\\n",
- "iv": "uWKKKxrJgtELlGMCOLJdkA==\\n",
- "version": 1,
- "cipher": "aes-256-cbc"
- }
- }
- EOM
- file "data_bags/bar/round_trip.json", <<~EOM
- {
- "name": "data_bag_item_bar_round_trip",
- "json_class": "Chef::DataBagItem",
- "chef_type": "data_bag_item",
- "data_bag": "bar",
- "raw_data": {
- "id": "round_trip",
- "root_password": {
- "encrypted_data": "noDOsTpsTAZlTU5sprhmYZzUDfr8du7hH/zRDOjRAmoTJHTZyfYoR221EOOW\\nXJ1D\\n",
- "iv": "Bnqhfy6n0Hx1wCe9pxHLoA==\\n",
- "version": 1,
- "cipher": "aes-256-cbc"
- },
- "admin_password": {
- "encrypted_data": "TcC7dU1gx6OnE5Ab4i/k42UEf0Nnr7cAyuTHId/LNjNOwpNf7XZc27DQSjuy\\nHPlt\\n",
- "iv": "+TAWJuPWCI2+WB8lGJAyvw==\\n",
- "version": 1,
- "cipher": "aes-256-cbc"
- }
- }
- }
- EOM
- end
-
- it "uploads a single file" do
- knife("data bag from file foo #{db_dir}/foo/bar.json").should_succeed stderr: <<~EOM
- Updated data_bag_item[foo::bar]
- EOM
- end
-
- it "uploads a single encrypted file" do
- knife("data bag from file foo #{db_dir}/foo/encrypted.json").should_succeed stderr: <<~EOM
- Updated data_bag_item[foo::encrypted]
- EOM
- end
-
- it "uploads a file in chef's internal format" do
- pending "chef/chef#4815"
- knife("data bag from file bar #{db_dir}/bar/round_trip.json").should_succeed stderr: <<~EOM
- Updated data_bag_item[bar::round_trip]
- EOM
- end
-
- it "uploads many files" do
- knife("data bag from file foo #{db_dir}/foo/bar.json #{db_dir}/foo/bzr.json").should_succeed stderr: <<~EOM
- Updated data_bag_item[foo::bar]
- Updated data_bag_item[foo::bzr]
- EOM
- end
-
- it "uploads a whole directory" do
- knife("data bag from file foo #{db_dir}/foo")
- knife("data bag show foo").should_succeed <<~EOM
- bar
- bzr
- cat
- dog
- encrypted
- EOM
- end
-
- end
- end
-end
diff --git a/spec/integration/knife/data_bag_list_spec.rb b/spec/integration/knife/data_bag_list_spec.rb
deleted file mode 100644
index 0216b90c5d..0000000000
--- a/spec/integration/knife/data_bag_list_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/data_bag_list"
-
-describe "knife data bag list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some data bags" do
- before do
- data_bag "x", {}
- data_bag "canteloupe", {}
- data_bag "rocket", {}
- end
-
- it "knife data bag list shows all the cookbooks" do
- knife("data bag list").should_succeed <<~EOM
- canteloupe
- rocket
- x
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/data_bag_show_spec.rb b/spec/integration/knife/data_bag_show_spec.rb
deleted file mode 100644
index b332b1b114..0000000000
--- a/spec/integration/knife/data_bag_show_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/data_bag_show"
-
-describe "knife data bag show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "is empty" do
- it "raises error if try to retrieve it" do
- expect { knife("data bag show bag") }.to raise_error(Net::HTTPClientException)
- end
- end
-
- when_the_chef_server "contains data bags" do
- let(:right_secret) { "abc" }
- let(:wrong_secret) { "ab" }
- let(:err) { "Encrypted data bag detected, decrypting with provided secret.\n" }
- before do
- data_bag "x", {}
- data_bag "canteloupe", {}
- data_bag "rocket", { "falcon9" => { heavy: "true" }, "atlas" => {}, "ariane" => {} }
- data_bag "encrypt", { "box" => { id: "box", foo: { "encrypted_data": "J8N0pJ+LFDQF3XvhzWgkSBOuZZn8Og==\n", "iv": "4S1sb4zLnMt71SXV\n", "auth_tag": "4ChINhxz4WmqOizvZNoPPg==\n", "version": 3, "cipher": "aes-256-gcm" } } }
- end
-
- context "with encrypted data" do
- context "provided secret key" do
- it "shows data if secret key is correct" do
- knife("data bag show encrypt box --secret #{right_secret}").should_succeed stderr: err, stdout: <<~EOM
- foo: bar
- id: box
- EOM
- end
-
- it "raises error if secret key is incorrect" do
- expect { knife("data bag show encrypt box --secret #{wrong_secret}") }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
- end
- end
-
- context "not provided secret key" do
- it "shows encrypted data with a warning" do
- expect(knife("data bag show encrypt box").stderr).to eq("WARNING: Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.\n")
- end
- end
- end
-
- context "with unencrypted data" do
- context "provided secret key" do
- it "shows unencrypted data with a warning" do
- expect(knife("data bag show rocket falcon9 --secret #{right_secret}").stderr).to eq("WARNING: Unencrypted data bag detected, ignoring any provided secret options.\n")
- end
- end
-
- context "not provided secret key" do
- it "shows null with an empty data bag" do
- knife("data bag show canteloupe").should_succeed "\n"
- end
-
- it "show list of items in a bag" do
- knife("data bag show rocket").should_succeed <<~EOM
- ariane
- atlas
- falcon9
- EOM
- end
-
- it "show data of the item" do
- knife("data bag show rocket falcon9").should_succeed <<~EOM
- heavy: true
- id: falcon9
- EOM
- end
- end
- end
- end
-end
diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb
deleted file mode 100644
index 851c492a66..0000000000
--- a/spec/integration/knife/delete_spec.rb
+++ /dev/null
@@ -1,1018 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/delete"
-require "chef/knife/list"
-require "chef/knife/raw"
-
-describe "knife delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- let :everything do
- <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- let :server_everything do
- <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- end
- let :server_nothing do
- <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /cookbooks
- /data_bags
- /environments
- /environments/_default.json
- /nodes
- /roles
- /users
- /users/admin.json
- EOM
- end
-
- let :nothing do
- <<~EOM
- /clients
- /cookbooks
- /data_bags
- /environments
- /nodes
- /roles
- /users
- EOM
- end
-
- when_the_chef_server "has one of each thing" do
- before do
- client "x", "{}"
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => "{}" }
- environment "x", "{}"
- node "x", "{}"
- role "x", "{}"
- user "x", "{}"
- end
-
- when_the_repository "also has one of each thing" do
- before do
- file "clients/x.json", {}
- file "cookbooks/x/metadata.rb", ""
- file "data_bags/x/y.json", {}
- file "environments/_default.json", {}
- file "environments/x.json", {}
- file "nodes/x.json", {}
- file "roles/x.json", {}
- file "users/x.json", {}
- end
-
- it "knife delete --both /cookbooks/x fails" do
- knife("delete --both /cookbooks/x").should_fail <<~EOM
- ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete.
- ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete.
- EOM
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /cookbooks/x deletes x" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete -r --local /cookbooks/x deletes x locally but not remotely" do
- knife("delete -r --local /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete -r /cookbooks/x deletes x remotely but not locally" do
- knife("delete -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed everything
- end
-
- # TODO delete empty data bag (particularly different on local side)
- context "with an empty data bag on both" do
- before do
- data_bag "empty", {}
- directory "data_bags/empty"
- end
-
- it "knife delete --both /data_bags/empty fails but deletes local version" do
- knife("delete --both /data_bags/empty").should_fail <<~EOM
- ERROR: /data_bags/empty (remote) must be deleted recursively! Pass -r to knife delete.
- ERROR: /data_bags/empty (local) must be deleted recursively! Pass -r to knife delete.
- EOM
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/empty
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/empty
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
- end
-
- it "knife delete --both /data_bags/x fails" do
- knife("delete --both /data_bags/x").should_fail <<~EOM
- ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete.
- ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete.
- EOM
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /data_bags/x deletes x" do
- knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /environments/x.json deletes x" do
- knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /roles/x.json deletes x" do
- knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /environments/_default.json fails but still deletes the local copy" do
- knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /environments/nonexistent.json fails" do
- knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both / fails" do
- knife("delete --both /").should_fail <<~EOM
- ERROR: / (remote) cannot be deleted.
- ERROR: / (local) cannot be deleted.
- EOM
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /* fails" do
- knife("delete --both -r /*").should_fail <<~EOM
- ERROR: / (remote) cannot be deleted.
- ERROR: / (local) cannot be deleted.
- ERROR: /clients (remote) cannot be deleted.
- ERROR: /clients (local) cannot be deleted.
- ERROR: /cookbooks (remote) cannot be deleted.
- ERROR: /cookbooks (local) cannot be deleted.
- ERROR: /data_bags (remote) cannot be deleted.
- ERROR: /data_bags (local) cannot be deleted.
- ERROR: /environments (remote) cannot be deleted.
- ERROR: /environments (local) cannot be deleted.
- ERROR: /nodes (remote) cannot be deleted.
- ERROR: /nodes (local) cannot be deleted.
- ERROR: /roles (remote) cannot be deleted.
- ERROR: /roles (local) cannot be deleted.
- ERROR: /users (remote) cannot be deleted.
- ERROR: /users (local) cannot be deleted.
- EOM
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed everything
- end
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife delete --both /cookbooks/x fails" do
- knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (remote) must be deleted recursively! Pass -r to knife delete.\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both -r /cookbooks/x deletes x" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both /data_bags/x fails" do
- knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (remote) must be deleted recursively! Pass -r to knife delete.\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both -r /data_bags/x deletes x" do
- knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both /environments/x.json deletes x" do
- knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both /roles/x.json deletes x" do
- knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
- knife("list -Rf /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /users
- /users/admin.json
- /users/x.json
- EOM
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both /environments/_default.json fails" do
- knife("delete --both /environments/_default.json").should_fail "", stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both / fails" do
- knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both -r /* fails" do
- knife("delete --both -r /*").should_fail <<~EOM
- ERROR: / (remote) cannot be deleted.
- ERROR: / (local) cannot be deleted.
- ERROR: /clients (remote) cannot be deleted.
- ERROR: /clients (local) cannot be deleted.
- ERROR: /cookbooks (remote) cannot be deleted.
- ERROR: /cookbooks (local) cannot be deleted.
- ERROR: /data_bags (remote) cannot be deleted.
- ERROR: /data_bags (local) cannot be deleted.
- ERROR: /environments (remote) cannot be deleted.
- ERROR: /environments (local) cannot be deleted.
- ERROR: /nodes (remote) cannot be deleted.
- ERROR: /nodes (local) cannot be deleted.
- ERROR: /roles (remote) cannot be deleted.
- ERROR: /roles (local) cannot be deleted.
- ERROR: /users (remote) cannot be deleted.
- ERROR: /users (local) cannot be deleted.
- EOM
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- it "knife delete --both /environments/nonexistent.json fails" do
- knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
- knife("list -Rf /").should_succeed server_everything
- knife("list -Rf --local /").should_succeed nothing
- end
-
- context "and cwd is at the top level" do
- before { cwd "." }
- it "knife delete fails" do
- knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/
- knife("list -Rf /").should_succeed <<~EOM
- clients
- clients/chef-validator.json
- clients/chef-webui.json
- clients/x.json
- cookbooks
- cookbooks/x
- cookbooks/x/metadata.rb
- data_bags
- data_bags/x
- data_bags/x/y.json
- environments
- environments/_default.json
- environments/x.json
- nodes
- nodes/x.json
- roles
- roles/x.json
- users
- users/admin.json
- users/x.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- clients
- cookbooks
- data_bags
- environments
- nodes
- roles
- users
- EOM
- end
- end
- end
- end
-
- when_the_chef_server "is empty" do
- when_the_repository "has one of each thing" do
- before do
- file "clients/x.json", {}
- file "cookbooks/x/metadata.rb", ""
- file "data_bags/x/y.json", {}
- file "environments/_default.json", {}
- file "environments/x.json", {}
- file "nodes/x.json", {}
- file "roles/x.json", {}
- file "users/x.json", {}
- end
-
- it "knife delete --both /cookbooks/x fails" do
- knife("delete --both /cookbooks/x").should_fail "ERROR: /cookbooks/x (local) must be deleted recursively! Pass -r to knife delete.\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /cookbooks/x deletes x" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /data_bags/x fails" do
- knife("delete --both /data_bags/x").should_fail "ERROR: /data_bags/x (local) must be deleted recursively! Pass -r to knife delete.\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /data_bags/x deletes x" do
- knife("delete --both -r /data_bags/x").should_succeed "Deleted /data_bags/x\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /environments/x.json deletes x" do
- knife("delete --both /environments/x.json").should_succeed "Deleted /environments/x.json\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /roles/x.json deletes x" do
- knife("delete --both /roles/x.json").should_succeed "Deleted /roles/x.json\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/_default.json
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both /environments/_default.json fails but still deletes the local copy" do
- knife("delete --both /environments/_default.json").should_fail stderr: "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", stdout: "Deleted /environments/_default.json\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed <<~EOM
- /clients
- /clients/x.json
- /cookbooks
- /cookbooks/x
- /cookbooks/x/metadata.rb
- /data_bags
- /data_bags/x
- /data_bags/x/y.json
- /environments
- /environments/x.json
- /nodes
- /nodes/x.json
- /roles
- /roles/x.json
- /users
- /users/x.json
- EOM
- end
-
- it "knife delete --both / fails" do
- knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both -r /* fails" do
- knife("delete --both -r /*").should_fail <<~EOM
- ERROR: / (remote) cannot be deleted.
- ERROR: / (local) cannot be deleted.
- ERROR: /clients (remote) cannot be deleted.
- ERROR: /clients (local) cannot be deleted.
- ERROR: /cookbooks (remote) cannot be deleted.
- ERROR: /cookbooks (local) cannot be deleted.
- ERROR: /data_bags (remote) cannot be deleted.
- ERROR: /data_bags (local) cannot be deleted.
- ERROR: /environments (remote) cannot be deleted.
- ERROR: /environments (local) cannot be deleted.
- ERROR: /nodes (remote) cannot be deleted.
- ERROR: /nodes (local) cannot be deleted.
- ERROR: /roles (remote) cannot be deleted.
- ERROR: /roles (local) cannot be deleted.
- ERROR: /users (remote) cannot be deleted.
- ERROR: /users (local) cannot be deleted.
- EOM
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed everything
- end
-
- it "knife delete --both /environments/nonexistent.json fails" do
- knife("delete --both /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
- knife("list -Rf /").should_succeed server_nothing
- knife("list -Rf --local /").should_succeed everything
- end
-
- context "and cwd is at the top level" do
- before { cwd "." }
- it "knife delete fails" do
- knife("delete").should_fail "FATAL: You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"\n", stdout: /USAGE/
- knife("list -Rf /").should_succeed <<~EOM
- clients
- clients/chef-validator.json
- clients/chef-webui.json
- cookbooks
- data_bags
- environments
- environments/_default.json
- nodes
- roles
- users
- users/admin.json
- EOM
- knife("list -Rf --local /").should_succeed <<~EOM
- clients
- clients/x.json
- cookbooks
- cookbooks/x
- cookbooks/x/metadata.rb
- data_bags
- data_bags/x
- data_bags/x/y.json
- environments
- environments/_default.json
- environments/x.json
- nodes
- nodes/x.json
- roles
- roles/x.json
- users
- users/x.json
- EOM
- end
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", 'version "1.0.0"'
- file "cookbooks/x/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- # TODO this seems wrong
- it "knife delete --both -r /cookbooks/x deletes the latest version on the server and the local version" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("raw /cookbooks/x").should_succeed(/1.0.0/)
- knife("list --local /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife delete --both /cookbooks/x deletes the latest version on the server and the local version" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("raw /cookbooks/x").should_succeed(/0.9.9/)
- knife("list --local /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before { cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } }
-
- it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("raw /cookbooks/x").should_fail(/404/)
- knife("list --local /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before { cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } }
-
- it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("raw /cookbooks/x").should_fail(/404/)
- knife("list --local /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_repository "is empty" do
- when_the_chef_server "has two versions of a cookbook" do
- before do
- cookbook "x", "2.0.11"
- cookbook "x", "11.0.0"
- end
-
- it "knife delete deletes the latest version" do
- knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n"
- knife("raw /cookbooks/x").should_succeed( /2.0.11/ )
- end
- end
- end
-
- when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
- before do
- organization "foo" do
- container "x", {}
- group "x", {}
- policy "x", "1.2.3", {}
- policy_group "x", { "policies" => { "x" => { "revision_id" => "1.2.3" } } }
- end
- end
-
- before :each do
- Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
- end
-
- it "knife delete /acls/containers/environments.json fails with a reasonable error" do
- knife("delete /acls/containers/environments.json").should_fail "ERROR: /acls/containers/environments.json (remote) ACLs cannot be deleted.\n"
- end
-
- it "knife delete /containers/x.json succeeds" do
- knife("delete /containers/x.json").should_succeed "Deleted /containers/x.json\n"
- knife("raw /containers/x.json").should_fail(/404/)
- end
-
- it "knife delete /groups/x.json succeeds" do
- knife("delete /groups/x.json").should_succeed "Deleted /groups/x.json\n"
- knife("raw /groups/x.json").should_fail(/404/)
- end
-
- it "knife delete /policies/x-1.2.3.json succeeds" do
- knife("raw /policies/x/revisions/1.2.3").should_succeed "{\n \"name\": \"x\",\n \"revision_id\": \"1.2.3\",\n \"run_list\": [\n\n ],\n \"cookbook_locks\": {\n\n }\n}\n"
- knife("delete /policies/x-1.2.3.json").should_succeed "Deleted /policies/x-1.2.3.json\n"
- knife("raw /policies/x/revisions/1.2.3").should_fail(/404/)
- end
-
- it "knife delete /policy_groups/x.json succeeds" do
- knife("raw /policy_groups/x").should_succeed "{\n \"uri\": \"http://127.0.0.1:8900/organizations/foo/policy_groups/x\",\n \"policies\": {\n \"x\": {\n \"revision_id\": \"1.2.3\"\n }\n }\n}\n"
- knife("delete /policy_groups/x.json").should_succeed "Deleted /policy_groups/x.json\n"
- knife("raw /policy_groups/x").should_fail(/404/)
- end
-
- it "knife delete /org.json fails with a reasonable error" do
- knife("delete /org.json").should_fail "ERROR: /org.json (remote) cannot be deleted.\n"
- end
-
- it "knife delete /invitations.json fails with a reasonable error" do
- knife("delete /invitations.json").should_fail "ERROR: /invitations.json (remote) cannot be deleted.\n"
- end
-
- it "knife delete /members.json fails with a reasonable error" do
- knife("delete /members.json").should_fail "ERROR: /members.json (remote) cannot be deleted.\n"
- end
- end
-end
diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb
deleted file mode 100644
index 77505e6332..0000000000
--- a/spec/integration/knife/deps_spec.rb
+++ /dev/null
@@ -1,703 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/deps"
-
-describe "knife deps", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- context "local" do
- when_the_repository "has a role with no run_list" do
- before { file "roles/starring.json", {} }
- it "knife deps reports no dependencies" do
- knife("deps /roles/starring.json").should_succeed "/roles/starring.json\n"
- end
- end
-
- when_the_repository "has a role with a default run_list" do
- before do
- file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- file "roles/minor.json", {}
- file "cookbooks/quiche/metadata.rb", 'name "quiche"'
- file "cookbooks/quiche/recipes/default.rb", ""
- file "cookbooks/soup/metadata.rb", 'name "soup"'
- file "cookbooks/soup/recipes/chicken.rb", ""
- end
- it "knife deps reports all dependencies" do
- knife("deps /roles/starring.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- EOM
- end
- end
-
- when_the_repository "has a role with an env_run_list" do
- before do
- file "roles/starring.json", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } }
- file "roles/minor.json", {}
- file "cookbooks/quiche/metadata.rb", 'name "quiche"'
- file "cookbooks/quiche/recipes/default.rb", ""
- file "cookbooks/soup/metadata.rb", 'name "soup"'
- file "cookbooks/soup/recipes/chicken.rb", ""
- end
- it "knife deps reports all dependencies" do
- knife("deps /roles/starring.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- EOM
- end
- end
-
- when_the_repository "has a node with no environment or run_list" do
- before { file "nodes/mort.json", {} }
- it "knife deps reports just the node" do
- knife("deps /nodes/mort.json").should_succeed "/nodes/mort.json\n"
- end
- end
- when_the_repository "has a node with an environment" do
- before do
- file "environments/desert.json", {}
- file "nodes/mort.json", { "chef_environment" => "desert" }
- end
- it "knife deps reports just the node" do
- knife("deps /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n"
- end
- end
- when_the_repository "has a node with roles and recipes in its run_list" do
- before do
- file "roles/minor.json", {}
- file "cookbooks/quiche/metadata.rb", 'name "quiche"'
- file "cookbooks/quiche/recipes/default.rb", ""
- file "cookbooks/soup/metadata.rb", 'name "soup"'
- file "cookbooks/soup/recipes/chicken.rb", ""
- file "nodes/mort.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- end
- it "knife deps reports just the node" do
- knife("deps /nodes/mort.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /nodes/mort.json
- EOM
- end
- end
- when_the_repository "has a cookbook with no dependencies" do
- before do
- file "cookbooks/quiche/metadata.rb", 'name "quiche"'
- file "cookbooks/quiche/recipes/default.rb", ""
- end
- it "knife deps reports just the cookbook" do
- knife("deps /cookbooks/quiche").should_succeed "/cookbooks/quiche\n"
- end
- end
- when_the_repository "has a cookbook with dependencies" do
- before do
- file "cookbooks/kettle/metadata.rb", 'name "kettle"'
- file "cookbooks/quiche/metadata.rb", 'name "quiche"
-depends "kettle"'
- file "cookbooks/quiche/recipes/default.rb", ""
- end
- it "knife deps reports just the cookbook" do
- knife("deps /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
- end
- end
- when_the_repository "has a data bag" do
- before { file "data_bags/bag/item.json", {} }
- it "knife deps reports just the data bag" do
- knife("deps /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n"
- end
- end
- when_the_repository "has an environment" do
- before { file "environments/desert.json", {} }
- it "knife deps reports just the environment" do
- knife("deps /environments/desert.json").should_succeed "/environments/desert.json\n"
- end
- end
- when_the_repository "has a deep dependency tree" do
- before do
- file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- file "roles/minor.json", {}
- file "cookbooks/quiche/metadata.rb", 'name "quiche"'
- file "cookbooks/quiche/recipes/default.rb", ""
- file "cookbooks/soup/metadata.rb", 'name "soup"'
- file "cookbooks/soup/recipes/chicken.rb", ""
- file "environments/desert.json", {}
- file "nodes/mort.json", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] }
- file "nodes/bart.json", { "run_list" => [ "role[minor]" ] }
- end
-
- it "knife deps reports all dependencies" do
- knife("deps /nodes/mort.json").should_succeed <<~EOM
- /environments/desert.json
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps * reports all dependencies of all things" do
- knife("deps /nodes/*").should_succeed <<~EOM
- /roles/minor.json
- /nodes/bart.json
- /environments/desert.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps a b reports all dependencies of a and b" do
- knife("deps /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM
- /roles/minor.json
- /nodes/bart.json
- /environments/desert.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps --tree /* shows dependencies in a tree" do
- knife("deps --tree /nodes/*").should_succeed <<~EOM
- /nodes/bart.json
- /roles/minor.json
- /nodes/mort.json
- /environments/desert.json
- /roles/starring.json
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- EOM
- end
- it "knife deps --tree --no-recurse shows only the first level of dependencies" do
- knife("deps --tree --no-recurse /nodes/*").should_succeed <<~EOM
- /nodes/bart.json
- /roles/minor.json
- /nodes/mort.json
- /environments/desert.json
- /roles/starring.json
- EOM
- end
- end
-
- context "circular dependencies" do
- when_the_repository "has cookbooks with circular dependencies" do
- before do
- file "cookbooks/foo/metadata.rb", 'name "foo"
-depends "bar"'
- file "cookbooks/bar/metadata.rb", 'name "bar"
-depends "baz"'
- file "cookbooks/baz/metadata.rb", 'name "baz"
-depends "foo"'
- end
-
- it "knife deps prints each once" do
- knife("deps /cookbooks/foo").should_succeed(
- stdout: "/cookbooks/baz\n/cookbooks/bar\n/cookbooks/foo\n"
- )
- end
- it "knife deps --tree prints each once" do
- knife("deps --tree /cookbooks/foo").should_succeed(
- stdout: "/cookbooks/foo\n /cookbooks/bar\n /cookbooks/baz\n /cookbooks/foo\n"
- )
- end
- end
- when_the_repository "has roles with circular dependencies" do
- before do
- file "roles/foo.json", { "run_list" => [ "role[bar]" ] }
- file "roles/bar.json", { "run_list" => [ "role[baz]" ] }
- file "roles/baz.json", { "run_list" => [ "role[foo]" ] }
- file "roles/self.json", { "run_list" => [ "role[self]" ] }
- end
- it "knife deps prints each once" do
- knife("deps /roles/foo.json /roles/self.json").should_succeed <<~EOM
- /roles/baz.json
- /roles/bar.json
- /roles/foo.json
- /roles/self.json
- EOM
- end
- it "knife deps --tree prints each once" do
- knife("deps --tree /roles/foo.json /roles/self.json") do
- expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n")
- expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n")
- end
- end
- end
- end
-
- context "missing objects" do
- when_the_repository "is empty" do
- it "knife deps /blah reports an error" do
- knife("deps /blah").should_fail(
- exit_code: 2,
- stdout: "/blah\n",
- stderr: "ERROR: /blah: No such file or directory\n"
- )
- end
- it "knife deps /roles/x.json reports an error" do
- knife("deps /roles/x.json").should_fail(
- exit_code: 2,
- stdout: "/roles/x.json\n",
- stderr: "ERROR: /roles/x.json: No such file or directory\n"
- )
- end
- it "knife deps /nodes/x.json reports an error" do
- knife("deps /nodes/x.json").should_fail(
- exit_code: 2,
- stdout: "/nodes/x.json\n",
- stderr: "ERROR: /nodes/x.json: No such file or directory\n"
- )
- end
- it "knife deps /environments/x.json reports an error" do
- knife("deps /environments/x.json").should_fail(
- exit_code: 2,
- stdout: "/environments/x.json\n",
- stderr: "ERROR: /environments/x.json: No such file or directory\n"
- )
- end
- it "knife deps /cookbooks/x reports an error" do
- knife("deps /cookbooks/x").should_fail(
- exit_code: 2,
- stdout: "/cookbooks/x\n",
- stderr: "ERROR: /cookbooks/x: No such file or directory\n"
- )
- end
- it "knife deps /data_bags/bag/item.json reports an error" do
- knife("deps /data_bags/bag/item.json").should_fail(
- exit_code: 2,
- stdout: "/data_bags/bag/item.json\n",
- stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n"
- )
- end
- end
- when_the_repository "is missing a dependent cookbook" do
- before do
- file "roles/starring.json", { "run_list" => [ "recipe[quiche]"] }
- end
- it "knife deps reports the cookbook, along with an error" do
- knife("deps /roles/starring.json").should_fail(
- exit_code: 2,
- stdout: "/cookbooks/quiche\n/roles/starring.json\n",
- stderr: "ERROR: /cookbooks/quiche: No such file or directory\n"
- )
- end
- end
- when_the_repository "is missing a dependent environment" do
- before do
- file "nodes/mort.json", { "chef_environment" => "desert" }
- end
- it "knife deps reports the environment, along with an error" do
- knife("deps /nodes/mort.json").should_fail(
- exit_code: 2,
- stdout: "/environments/desert.json\n/nodes/mort.json\n",
- stderr: "ERROR: /environments/desert.json: No such file or directory\n"
- )
- end
- end
- when_the_repository "is missing a dependent role" do
- before do
- file "roles/starring.json", { "run_list" => [ "role[minor]"] }
- end
- it "knife deps reports the role, along with an error" do
- knife("deps /roles/starring.json").should_fail(
- exit_code: 2,
- stdout: "/roles/minor.json\n/roles/starring.json\n",
- stderr: "ERROR: /roles/minor.json: No such file or directory\n"
- )
- end
- end
- end
- context "invalid objects" do
- when_the_repository "is empty" do
- it "knife deps / reports itself only" do
- knife("deps /").should_succeed("/\n")
- end
- it "knife deps /roles reports an error" do
- knife("deps /roles").should_fail(
- exit_code: 2,
- stderr: "ERROR: /roles: No such file or directory\n",
- stdout: "/roles\n"
- )
- end
- end
- when_the_repository "has a data bag" do
- before { file "data_bags/bag/item.json", "" }
- it "knife deps /data_bags/bag shows no dependencies" do
- knife("deps /data_bags/bag").should_succeed("/data_bags/bag\n")
- end
- end
- when_the_repository "has a cookbook" do
- before { file "cookbooks/blah/metadata.rb", 'name "blah"' }
- it "knife deps on a cookbook file shows no dependencies" do
- knife("deps /cookbooks/blah/metadata.rb").should_succeed(
- "/cookbooks/blah/metadata.rb\n"
- )
- end
- end
- end
- end
-
- context "remote" do
- include_context "default config options"
-
- when_the_chef_server "has a role with no run_list" do
- before { role "starring", {} }
- it "knife deps reports no dependencies" do
- knife("deps --remote /roles/starring.json").should_succeed "/roles/starring.json\n"
- end
- end
-
- when_the_chef_server "has a role with a default run_list" do
- before do
- role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- role "minor", {}
- cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
- cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
- end
- it "knife deps reports all dependencies" do
- knife("deps --remote /roles/starring.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- EOM
- end
- end
-
- when_the_chef_server "has a role with an env_run_list" do
- before do
- role "starring", { "env_run_lists" => { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } }
- role "minor", {}
- cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
- cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
- end
- it "knife deps reports all dependencies" do
- knife("deps --remote /roles/starring.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- EOM
- end
- end
-
- when_the_chef_server "has a node with no environment or run_list" do
- before { node "mort", {} }
- it "knife deps reports just the node" do
- knife("deps --remote /nodes/mort.json").should_succeed "/nodes/mort.json\n"
- end
- end
- when_the_chef_server "has a node with an environment" do
- before do
- environment "desert", {}
- node "mort", { "chef_environment" => "desert" }
- end
- it "knife deps reports just the node" do
- knife("deps --remote /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n"
- end
- end
- when_the_chef_server "has a node with roles and recipes in its run_list" do
- before do
- role "minor", {}
- cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
- cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
- node "mort", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- end
- it "knife deps reports just the node" do
- knife("deps --remote /nodes/mort.json").should_succeed <<~EOM
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /nodes/mort.json
- EOM
- end
- end
- when_the_chef_server "has a cookbook with no dependencies" do
- before do
- cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
- end
- it "knife deps reports just the cookbook" do
- knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/quiche\n"
- end
- end
- when_the_chef_server "has a cookbook with dependencies" do
- before do
- cookbook "kettle", "1.0.0", { "metadata.rb" => %Q{name "kettle"\nversion "1.0.0"\n} }
- cookbook "quiche", "1.0.0", { "metadata.rb" => 'name "quiche"
-depends "kettle"', "recipes" => { "default.rb" => "" } }
- end
- it "knife deps reports the cookbook and its dependencies" do
- knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
- end
- end
- when_the_chef_server "has a data bag" do
- before { data_bag "bag", { "item" => {} } }
- it "knife deps reports just the data bag" do
- knife("deps --remote /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n"
- end
- end
- when_the_chef_server "has an environment" do
- before { environment "desert", {} }
- it "knife deps reports just the environment" do
- knife("deps --remote /environments/desert.json").should_succeed "/environments/desert.json\n"
- end
- end
- when_the_chef_server "has a deep dependency tree" do
- before do
- role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} }
- role "minor", {}
- cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } }
- cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } }
- environment "desert", {}
- node "mort", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] }
- node "bart", { "run_list" => [ "role[minor]" ] }
- end
-
- it "knife deps reports all dependencies" do
- knife("deps --remote /nodes/mort.json").should_succeed <<~EOM
- /environments/desert.json
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps * reports all dependencies of all things" do
- knife("deps --remote /nodes/*").should_succeed <<~EOM
- /roles/minor.json
- /nodes/bart.json
- /environments/desert.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps a b reports all dependencies of a and b" do
- knife("deps --remote /nodes/bart.json /nodes/mort.json").should_succeed <<~EOM
- /roles/minor.json
- /nodes/bart.json
- /environments/desert.json
- /cookbooks/quiche
- /cookbooks/soup
- /roles/starring.json
- /nodes/mort.json
- EOM
- end
- it "knife deps --tree /* shows dependencies in a tree" do
- knife("deps --remote --tree /nodes/*").should_succeed <<~EOM
- /nodes/bart.json
- /roles/minor.json
- /nodes/mort.json
- /environments/desert.json
- /roles/starring.json
- /roles/minor.json
- /cookbooks/quiche
- /cookbooks/soup
- EOM
- end
- it "knife deps --tree --no-recurse shows only the first level of dependencies" do
- knife("deps --remote --tree --no-recurse /nodes/*").should_succeed <<~EOM
- /nodes/bart.json
- /roles/minor.json
- /nodes/mort.json
- /environments/desert.json
- /roles/starring.json
- EOM
- end
- end
-
- context "circular dependencies" do
- when_the_chef_server "has cookbooks with circular dependencies" do
- before do
- cookbook "foo", "1.0.0", { "metadata.rb" => 'name "foo"
-depends "bar"' }
- cookbook "bar", "1.0.0", { "metadata.rb" => 'name "bar"
-depends "baz"' }
- cookbook "baz", "1.0.0", { "metadata.rb" => 'name "baz"
-depends "foo"' }
- cookbook "self", "1.0.0", { "metadata.rb" => 'name "self"
-depends "self"' }
- end
- it "knife deps prints each once" do
- knife("deps --remote /cookbooks/foo /cookbooks/self").should_succeed <<~EOM
- /cookbooks/baz
- /cookbooks/bar
- /cookbooks/foo
- /cookbooks/self
- EOM
- end
- it "knife deps --tree prints each once" do
- knife("deps --remote --tree /cookbooks/foo /cookbooks/self").should_succeed <<~EOM
- /cookbooks/foo
- /cookbooks/bar
- /cookbooks/baz
- /cookbooks/foo
- /cookbooks/self
- /cookbooks/self
- EOM
- end
- end
- when_the_chef_server "has roles with circular dependencies" do
- before do
- role "foo", { "run_list" => [ "role[bar]" ] }
- role "bar", { "run_list" => [ "role[baz]" ] }
- role "baz", { "run_list" => [ "role[foo]" ] }
- role "self", { "run_list" => [ "role[self]" ] }
- end
- it "knife deps prints each once" do
- knife("deps --remote /roles/foo.json /roles/self.json").should_succeed <<~EOM
- /roles/baz.json
- /roles/bar.json
- /roles/foo.json
- /roles/self.json
- EOM
- end
- it "knife deps --tree prints each once" do
- knife("deps --remote --tree /roles/foo.json /roles/self.json") do
- expect(stdout).to eq("/roles/foo.json\n /roles/bar.json\n /roles/baz.json\n /roles/foo.json\n/roles/self.json\n /roles/self.json\n")
- expect(stderr).to eq("WARNING: No knife configuration file found. See https://docs.chef.io/config_rb/ for details.\n")
- end
- end
- end
- end
-
- context "missing objects" do
- when_the_chef_server "is empty" do
- it "knife deps /blah reports an error" do
- knife("deps --remote /blah").should_fail(
- exit_code: 2,
- stdout: "/blah\n",
- stderr: "ERROR: /blah: No such file or directory\n"
- )
- end
- it "knife deps /roles/x.json reports an error" do
- knife("deps --remote /roles/x.json").should_fail(
- exit_code: 2,
- stdout: "/roles/x.json\n",
- stderr: "ERROR: /roles/x.json: No such file or directory\n"
- )
- end
- it "knife deps /nodes/x.json reports an error" do
- knife("deps --remote /nodes/x.json").should_fail(
- exit_code: 2,
- stdout: "/nodes/x.json\n",
- stderr: "ERROR: /nodes/x.json: No such file or directory\n"
- )
- end
- it "knife deps /environments/x.json reports an error" do
- knife("deps --remote /environments/x.json").should_fail(
- exit_code: 2,
- stdout: "/environments/x.json\n",
- stderr: "ERROR: /environments/x.json: No such file or directory\n"
- )
- end
- it "knife deps /cookbooks/x reports an error" do
- knife("deps --remote /cookbooks/x").should_fail(
- exit_code: 2,
- stdout: "/cookbooks/x\n",
- stderr: "ERROR: /cookbooks/x: No such file or directory\n"
- )
- end
- it "knife deps /data_bags/bag/item reports an error" do
- knife("deps --remote /data_bags/bag/item.json").should_fail(
- exit_code: 2,
- stdout: "/data_bags/bag/item.json\n",
- stderr: "ERROR: /data_bags/bag/item.json: No such file or directory\n"
- )
- end
- end
- when_the_chef_server "is missing a dependent cookbook" do
- before do
- role "starring", { "run_list" => [ "recipe[quiche]"] }
- end
- it "knife deps reports the cookbook, along with an error" do
- knife("deps --remote /roles/starring.json").should_fail(
- exit_code: 2,
- stdout: "/cookbooks/quiche\n/roles/starring.json\n",
- stderr: "ERROR: /cookbooks/quiche: No such file or directory\n"
- )
- end
- end
- when_the_chef_server "is missing a dependent environment" do
- before do
- node "mort", { "chef_environment" => "desert" }
- end
- it "knife deps reports the environment, along with an error" do
- knife("deps --remote /nodes/mort.json").should_fail(
- exit_code: 2,
- stdout: "/environments/desert.json\n/nodes/mort.json\n",
- stderr: "ERROR: /environments/desert.json: No such file or directory\n"
- )
- end
- end
- when_the_chef_server "is missing a dependent role" do
- before do
- role "starring", { "run_list" => [ "role[minor]"] }
- end
- it "knife deps reports the role, along with an error" do
- knife("deps --remote /roles/starring.json").should_fail(
- exit_code: 2,
- stdout: "/roles/minor.json\n/roles/starring.json\n",
- stderr: "ERROR: /roles/minor.json: No such file or directory\n"
- )
- end
- end
- end
- context "invalid objects" do
- when_the_chef_server "is empty" do
- it "knife deps / reports an error" do
- knife("deps --remote /").should_succeed("/\n")
- end
- it "knife deps /roles reports an error" do
- knife("deps --remote /roles").should_succeed("/roles\n")
- end
- end
- when_the_chef_server "has a data bag" do
- before { data_bag "bag", { "item" => {} } }
- it "knife deps /data_bags/bag shows no dependencies" do
- knife("deps --remote /data_bags/bag").should_succeed("/data_bags/bag\n")
- end
- end
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "blah", "1.0.0", { "metadata.rb" => 'name "blah"' }
- end
- it "knife deps on a cookbook file shows no dependencies" do
- knife("deps --remote /cookbooks/blah/metadata.rb").should_succeed(
- "/cookbooks/blah/metadata.rb\n"
- )
- end
- end
- end
- end
-
- it "knife deps --no-recurse reports an error" do
- knife("deps --no-recurse /").should_fail("ERROR: --no-recurse requires --tree\n")
- end
-end
diff --git a/spec/integration/knife/diff_spec.rb b/spec/integration/knife/diff_spec.rb
deleted file mode 100644
index 41ae5ea519..0000000000
--- a/spec/integration/knife/diff_spec.rb
+++ /dev/null
@@ -1,605 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/diff"
-
-describe "knife diff", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- context "without versioned cookbooks" do
- when_the_chef_server "has one of each thing" do
- before do
- client "x", "{}"
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => "{}" }
- environment "x", "{}"
- node "x", "{}"
- role "x", "{}"
- user "x", "{}"
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife diff reports everything as deleted" do
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients/chef-validator.json
- D\t/clients/chef-webui.json
- D\t/clients/x.json
- D\t/cookbooks/x
- D\t/data_bags/x
- D\t/environments/_default.json
- D\t/environments/x.json
- D\t/nodes/x.json
- D\t/roles/x.json
- D\t/users/admin.json
- D\t/users/x.json
- EOM
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
-
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife diff reports no differences" do
- knife("diff /").should_succeed ""
- end
-
- it "knife diff /environments/nonexistent.json reports an error" do
- knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
- end
-
- it "knife diff /environments/*.txt reports an error" do
- knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", <<~EOM
- {
- "foo": "bar"
- }
- EOM
- end
-
- it "knife diff reports the role as different" do
- knife("diff --name-status /").should_succeed <<~EOM
- M\t/roles/x.json
- EOM
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/blah.rb", ""
- file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife diff reports the new files as added" do
- knife("diff --name-status /").should_succeed <<~EOM
- A\t/clients/y.json
- A\t/cookbooks/x/blah.rb
- A\t/cookbooks/y
- A\t/data_bags/x/z.json
- A\t/data_bags/y
- A\t/environments/y.json
- A\t/nodes/y.json
- A\t/roles/y.json
- A\t/users/y.json
- EOM
- end
-
- context "when cwd is the data_bags directory" do
- before { cwd "data_bags" }
- it "knife diff reports different data bags" do
- knife("diff --name-status").should_succeed <<~EOM
- A\tx/z.json
- A\ty
- EOM
- end
- it "knife diff * reports different data bags" do
- knife("diff --name-status *").should_succeed <<~EOM
- A\tx/z.json
- A\ty
- EOM
- end
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife diff reports everything as deleted" do
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/onlyin1.0.0.rb", ""
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
- end
-
- it "knife diff /cookbooks/x shows differences" do
- knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
-
- it "knife diff --diff-filter=MAT does not show deleted files" do
- knife("diff --diff-filter=MAT --name-status /cookbooks/x").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
- end
- it "knife diff /cookbooks/x shows no differences" do
- knife("diff --name-status /cookbooks/x").should_succeed ""
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
- end
-
- it "knife diff /cookbooks/x shows the differences" do
- knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
- end
-
- it "knife diff /cookbooks/x shows the differences" do
- knife("diff --name-status /cookbooks/x").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin0.9.9.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
- end
-
- context "json diff tests" do
- when_the_repository "has an empty environment file" do
- before do
- file "environments/x.json", {}
- end
-
- when_the_chef_server "has an empty environment" do
- before { environment "x", {} }
- it "knife diff returns no differences" do
- knife("diff /environments/x.json").should_succeed ""
- end
- end
- when_the_chef_server "has an environment with a different value" do
- before { environment "x", { "description" => "hi" } }
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
-- "name": "x",
-- "description": "hi"
-\+ "name": "x"
- }
-/)
- end
- end
- end
-
- when_the_repository "has an environment file with a value in it" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
-
- when_the_chef_server "has an environment with the same value" do
- before do
- environment "x", { "description" => "hi" }
- end
- it "knife diff returns no differences" do
- knife("diff /environments/x.json").should_succeed ""
- end
- end
- when_the_chef_server "has an environment with no value" do
- before do
- environment "x", {}
- end
-
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
-- "name": "x"
-\+ "name": "x",
-\+ "description": "hi"
- }
-/)
- end
- end
- when_the_chef_server "has an environment with a different value" do
- before do
- environment "x", { "description" => "lo" }
- end
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
- "name": "x",
-- "description": "lo"
-\+ "description": "hi"
- }
-/)
- end
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before { environment "x", {} }
- when_the_repository "has an environment with bad JSON" do
- before { file "environments/x.json", "{" }
- it "knife diff reports an error and does a textual diff" do
- error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF"
- error_match = Regexp.new(Regexp.escape(error_text))
- knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match)
- end
- end
- end
- end # without versioned cookbooks
-
- context "with versioned cookbooks" do
- before { Chef::Config[:versioned_cookbooks] = true }
-
- when_the_chef_server "has one of each thing" do
- before do
- client "x", "{}"
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => "{}" }
- environment "x", "{}"
- node "x", "{}"
- role "x", "{}"
- user "x", "{}"
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife diff reports everything as deleted" do
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients/chef-validator.json
- D\t/clients/chef-webui.json
- D\t/clients/x.json
- D\t/cookbooks/x-1.0.0
- D\t/data_bags/x
- D\t/environments/_default.json
- D\t/environments/x.json
- D\t/nodes/x.json
- D\t/roles/x.json
- D\t/users/admin.json
- D\t/users/x.json
- EOM
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife diff reports no differences" do
- knife("diff /").should_succeed ""
- end
-
- it "knife diff /environments/nonexistent.json reports an error" do
- knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
- end
-
- it "knife diff /environments/*.txt reports an error" do
- knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", <<~EOM
- {
- "foo": "bar"
- }
- EOM
- end
-
- it "knife diff reports the role as different" do
- knife("diff --name-status /").should_succeed <<~EOM
- M\t/roles/x.json
- EOM
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", {}
- file "cookbooks/x-1.0.0/blah.rb", ""
- file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0")
- file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0")
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", {}
- end
-
- it "knife diff reports the new files as added" do
- knife("diff --name-status /").should_succeed <<~EOM
- A\t/clients/y.json
- A\t/cookbooks/x-1.0.0/blah.rb
- A\t/cookbooks/x-2.0.0
- A\t/cookbooks/y-1.0.0
- A\t/data_bags/x/z.json
- A\t/data_bags/y
- A\t/environments/y.json
- A\t/nodes/y.json
- A\t/roles/y.json
- A\t/users/y.json
- EOM
- end
-
- context "when cwd is the data_bags directory" do
- before { cwd "data_bags" }
- it "knife diff reports different data bags" do
- knife("diff --name-status").should_succeed <<~EOM
- A\tx/z.json
- A\ty
- EOM
- end
- it "knife diff * reports different data bags" do
- knife("diff --name-status *").should_succeed <<~EOM
- A\tx/z.json
- A\ty
- EOM
- end
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife diff reports everything as deleted" do
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x-1.0.0/onlyin1.0.0.rb", ""
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
- end
-
- it "knife diff /cookbooks shows differences" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x-1.0.1
- EOM
- end
-
- it "knife diff --diff-filter=MAT does not show deleted files" do
- knife("diff --diff-filter=MAT --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
- end
- it "knife diff /cookbooks shows the differences" do
- knife("diff --name-status /cookbooks").should_succeed "D\t/cookbooks/x-0.9.9\n"
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" }
- end
-
- it "knife diff /cookbooks shows the differences" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x-1.0.1
- A\t/cookbooks/x-1.0.0
- EOM
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" }
- end
-
- it "knife diff /cookbooks shows the differences" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x-0.9.9
- A\t/cookbooks/x-1.0.0
- EOM
- end
- end
- end
-
- context "json diff tests" do
- when_the_repository "has an empty environment file" do
- before { file "environments/x.json", {} }
- when_the_chef_server "has an empty environment" do
- before { environment "x", {} }
- it "knife diff returns no differences" do
- knife("diff /environments/x.json").should_succeed ""
- end
- end
- when_the_chef_server "has an environment with a different value" do
- before { environment "x", { "description" => "hi" } }
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
-- "name": "x",
-- "description": "hi"
-\+ "name": "x"
- }
-/)
- end
- end
- end
-
- when_the_repository "has an environment file with a value in it" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
-
- when_the_chef_server "has an environment with the same value" do
- before do
- environment "x", { "description" => "hi" }
- end
- it "knife diff returns no differences" do
- knife("diff /environments/x.json").should_succeed ""
- end
- end
- when_the_chef_server "has an environment with no value" do
- before { environment "x", {} }
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
-- "name": "x"
-\+ "name": "x",
-\+ "description": "hi"
- }
-/)
- end
- end
- when_the_chef_server "has an environment with a different value" do
- before do
- environment "x", { "description" => "lo" }
- end
- it "knife diff reports the difference" do
- knife("diff /environments/x.json").should_succeed(/
- {
- "name": "x",
-- "description": "lo"
-\+ "description": "hi"
- }
-/)
- end
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before { environment "x", {} }
- when_the_repository "has an environment with bad JSON" do
- before { file "environments/x.json", "{" }
- it "knife diff reports an error and does a textual diff" do
- error_text = "WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF"
- error_match = Regexp.new(Regexp.escape(error_text))
- knife("diff /environments/x.json").should_succeed(/- "name": "x"/, stderr: error_match)
- end
- end
- end
- end # without versioned cookbooks
-end
diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb
deleted file mode 100644
index 7bdec7b356..0000000000
--- a/spec/integration/knife/download_spec.rb
+++ /dev/null
@@ -1,1336 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/download"
-require "chef/knife/diff"
-
-describe "knife download", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- context "without versioned cookbooks" do
- when_the_chef_server "has one of each thing" do
-
- before do
- client "x", {}
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => {} }
- environment "x", {}
- node "x", {}
- role "x", {}
- user "x", {}
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife download downloads everything" do
- knife("download /").should_succeed <<~EOM
- Created /clients/chef-validator.json
- Created /clients/chef-webui.json
- Created /clients/x.json
- Created /cookbooks/x
- Created /cookbooks/x/metadata.rb
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments/_default.json
- Created /environments/x.json
- Created /nodes/x.json
- Created /roles/x.json
- Created /users/admin.json
- Created /users/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife download makes no changes" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife download --purge makes no changes" do
- knife("download --purge /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", <<~EOM
- {
- "chef_type": "role",
- "default_attributes": {
- },
- "description": "blarghle",
- "env_run_lists": {
- },
- "json_class": "Chef::Role",
- "name": "x",
- "override_attributes": {
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife download changes the role" do
- knife("download /").should_succeed "Updated /roles/x.json\n"
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife download --no-diff does not change the role" do
- knife("download --no-diff /").should_succeed ""
- knife("diff --name-status /").should_succeed "M\t/roles/x.json\n"
- end
- end
-
- context "except the role file is textually different, but not ACTUALLY different" do
- before do
- file "roles/x.json", <<~EOM
- {
- "chef_type": "role",
- "default_attributes": {
- },
- "env_run_lists": {
- },
- "json_class": "Chef::Role",
- "name": "x",
- "description": "",
- "override_attributes": {
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife download / does not change anything" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/blah.rb", ""
- file "cookbooks/y/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife download does nothing" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- A\t/clients/y.json
- A\t/cookbooks/x/blah.rb
- A\t/cookbooks/y
- A\t/data_bags/x/z.json
- A\t/data_bags/y
- A\t/environments/y.json
- A\t/nodes/y.json
- A\t/roles/y.json
- A\t/users/y.json
- EOM
- end
-
- it "knife download --purge deletes the extra files" do
- knife("download --purge /").should_succeed <<~EOM
- Deleted extra entry /clients/y.json (purge is on)
- Deleted extra entry /cookbooks/x/blah.rb (purge is on)
- Deleted extra entry /cookbooks/y (purge is on)
- Deleted extra entry /data_bags/x/z.json (purge is on)
- Deleted extra entry /data_bags/y (purge is on)
- Deleted extra entry /environments/y.json (purge is on)
- Deleted extra entry /nodes/y.json (purge is on)
- Deleted extra entry /roles/y.json (purge is on)
- Deleted extra entry /users/y.json (purge is on)
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife download creates the extra files" do
- knife("download /").should_succeed <<~EOM
- Created /clients
- Created /clients/chef-validator.json
- Created /clients/chef-webui.json
- Created /clients/x.json
- Created /cookbooks
- Created /cookbooks/x
- Created /cookbooks/x/metadata.rb
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments
- Created /environments/_default.json
- Created /environments/x.json
- Created /nodes
- Created /nodes/x.json
- Created /roles
- Created /roles/x.json
- Created /users
- Created /users/admin.json
- Created /users/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife download --no-diff creates the extra files" do
- knife("download --no-diff /").should_succeed <<~EOM
- Created /clients
- Created /clients/chef-validator.json
- Created /clients/chef-webui.json
- Created /clients/x.json
- Created /cookbooks
- Created /cookbooks/x
- Created /cookbooks/x/metadata.rb
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments
- Created /environments/_default.json
- Created /environments/x.json
- Created /nodes
- Created /nodes/x.json
- Created /roles
- Created /roles/x.json
- Created /users
- Created /users/admin.json
- Created /users/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
-
- context "when current directory is top level" do
- before do
- cwd "."
- end
-
- it "knife download with no parameters reports an error" do
- knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
- end
- end
- end
- end
-
- # Test download of an item when the other end doesn't even have the container
- when_the_repository "is empty" do
- when_the_chef_server "has two data bag items" do
- before do
- data_bag "x", { "y" => {}, "z" => {} }
- end
-
- it "knife download of one data bag item itself succeeds" do
- knife("download /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/z.json
- EOM
- end
-
- it "knife download /data_bags/x /data_bags/x/y.json downloads x once" do
- knife("download /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /data_bags/x/z.json
- EOM
- end
- end
- end
-
- when_the_repository "has three data bag items" do
- before do
- file "data_bags/x/deleted.json", <<~EOM
- {
- "id": "deleted"
- }
- EOM
- file "data_bags/x/modified.json", <<~EOM
- {
- "id": "modified"
- }
- EOM
- file "data_bags/x/unmodified.json", <<~EOM
- {
- "id": "unmodified"
- }
- EOM
- end
-
- when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
- before do
- data_bag "x", {
- "added" => {},
- "modified" => { "foo" => "bar" },
- "unmodified" => {},
- }
- end
-
- it "knife download of the modified file succeeds" do
- knife("download /data_bags/x/modified.json").should_succeed <<~EOM
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the unmodified file does nothing" do
- knife("download /data_bags/x/unmodified.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the added file succeeds" do
- knife("download /data_bags/x/added.json").should_succeed <<~EOM
- Created /data_bags/x/added.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the deleted file does nothing" do
- knife("download /data_bags/x/deleted.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download --purge of the deleted file deletes it" do
- knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- EOM
- end
- it "knife download of the entire data bag downloads everything" do
- knife("download /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download --purge of the entire data bag downloads everything" do
- knife("download --purge /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- context "when cwd is the /data_bags directory" do
- before do
- cwd "data_bags"
- end
- it "knife download fails" do
- knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
- end
- it "knife download --purge . downloads everything" do
- knife("download --purge .").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- it "knife download --purge * downloads everything" do
- knife("download --purge *").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/z.rb", ""
- end
-
- when_the_chef_server "has a modified, added and deleted file for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "metadata.rb" => cb_metadata("x", "1.0.0", "#extra content"), "y.rb" => "hi" }
- end
-
- it "knife download of a modified file succeeds" do
- knife("download /cookbooks/x/metadata.rb").should_succeed "Updated /cookbooks/x/metadata.rb\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/y.rb
- A\t/cookbooks/x/z.rb
- EOM
- end
- it "knife download of a deleted file does nothing" do
- knife("download /cookbooks/x/z.rb").should_succeed ""
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/y.rb
- A\t/cookbooks/x/z.rb
- EOM
- end
- it "knife download --purge of a deleted file succeeds" do
- knife("download --purge /cookbooks/x/z.rb").should_succeed "Deleted extra entry /cookbooks/x/z.rb (purge is on)\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/y.rb
- EOM
- end
- it "knife download of an added file succeeds" do
- knife("download /cookbooks/x/y.rb").should_succeed "Created /cookbooks/x/y.rb\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- A\t/cookbooks/x/z.rb
- EOM
- end
- it "knife download of the cookbook itself succeeds" do
- knife("download /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/metadata.rb
- Created /cookbooks/x/y.rb
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- A\t/cookbooks/x/z.rb
- EOM
- end
- it "knife download --purge of the cookbook itself succeeds" do
- knife("download --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/metadata.rb
- Created /cookbooks/x/y.rb
- Deleted extra entry /cookbooks/x/z.rb (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the latest version" do
- knife("download --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/metadata.rb
- Created /cookbooks/x/onlyin1.0.1.rb
- Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the updated file" do
- knife("download --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/onlyin1.0.0.rb
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the latest version" do
- knife("download --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/metadata.rb
- Created /cookbooks/x/onlyin1.0.1.rb
- Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the old version" do
- knife("download --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x/metadata.rb
- Created /cookbooks/x/onlyin0.9.9.rb
- Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "has a role" do
- before do
- role "x", {}
- end
- when_the_repository "has the role in ruby" do
- before do
- file "roles/x.rb", <<~EOM
- name "x"
- description "x"
- EOM
- end
-
- it "knife download refuses to change the role" do
- knife("download /roles/x.json").should_succeed "", stderr: "WARNING: /roles/x.rb cannot be updated (can't safely update ruby files).\n"
- knife("diff --name-status /roles/x.json").should_succeed "M\t/roles/x.rb\n"
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before do
- environment "x", {}
- end
- when_the_repository "has an environment with bad JSON" do
- before do
- file "environments/x.json", "{"
- end
- it "knife download succeeds" do
- warning = <<~EOH
- WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
- {
- (right here) ------^
-
- EOH
- knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n", stderr: warning
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
-
- when_the_repository "has the same environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
- it "knife download succeeds" do
- knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
-
- when_the_repository "has the same environment with no name in the file" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
- it "knife download succeeds" do
- knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
- end
- end # without versioned cookbooks
-
- context "with versioned cookbooks" do
- before { Chef::Config[:versioned_cookbooks] = true }
-
- when_the_chef_server "has one of each thing" do
- before do
- client "x", {}
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => {} }
- environment "x", {}
- node "x", {}
- role "x", {}
- user "x", {}
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife download downloads everything" do
- knife("download /").should_succeed <<~EOM
- Created /clients/chef-validator.json
- Created /clients/chef-webui.json
- Created /clients/x.json
- Created /cookbooks/x-1.0.0
- Created /cookbooks/x-1.0.0/metadata.rb
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments/_default.json
- Created /environments/x.json
- Created /nodes/x.json
- Created /roles/x.json
- Created /users/admin.json
- Created /users/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife download makes no changes" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife download --purge makes no changes" do
- knife("download --purge /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", { "description" => "blarghle" }
- end
-
- it "knife download changes the role" do
- knife("download /").should_succeed "Updated /roles/x.json\n"
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "except the role file is textually different, but not ACTUALLY different" do
- before do
- file "roles/x.json", <<~EOM
- {
- "chef_type": "role" ,
- "default_attributes": {
- },
- "env_run_lists": {
- },
- "json_class": "Chef::Role",
- "name": "x",
- "description": "",
- "override_attributes": {
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife download / does not change anything" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x-1.0.0/blah.rb", ""
- file "cookbooks/x-2.0.0/metadata.rb", 'version "2.0.0"'
- file "cookbooks/y-1.0.0/metadata.rb", 'version "1.0.0"'
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife download does nothing" do
- knife("download /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- A\t/clients/y.json
- A\t/cookbooks/x-1.0.0/blah.rb
- A\t/cookbooks/x-2.0.0
- A\t/cookbooks/y-1.0.0
- A\t/data_bags/x/z.json
- A\t/data_bags/y
- A\t/environments/y.json
- A\t/nodes/y.json
- A\t/roles/y.json
- A\t/users/y.json
- EOM
- end
-
- it "knife download --purge deletes the extra files" do
- knife("download --purge /").should_succeed <<~EOM
- Deleted extra entry /clients/y.json (purge is on)
- Deleted extra entry /cookbooks/x-1.0.0/blah.rb (purge is on)
- Deleted extra entry /cookbooks/x-2.0.0 (purge is on)
- Deleted extra entry /cookbooks/y-1.0.0 (purge is on)
- Deleted extra entry /data_bags/x/z.json (purge is on)
- Deleted extra entry /data_bags/y (purge is on)
- Deleted extra entry /environments/y.json (purge is on)
- Deleted extra entry /nodes/y.json (purge is on)
- Deleted extra entry /roles/y.json (purge is on)
- Deleted extra entry /users/y.json (purge is on)
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife download creates the extra files" do
- knife("download /").should_succeed <<~EOM
- Created /clients
- Created /clients/chef-validator.json
- Created /clients/chef-webui.json
- Created /clients/x.json
- Created /cookbooks
- Created /cookbooks/x-1.0.0
- Created /cookbooks/x-1.0.0/metadata.rb
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments
- Created /environments/_default.json
- Created /environments/x.json
- Created /nodes
- Created /nodes/x.json
- Created /roles
- Created /roles/x.json
- Created /users
- Created /users/admin.json
- Created /users/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
-
- context "when current directory is top level" do
- before do
- cwd "."
- end
- it "knife download with no parameters reports an error" do
- knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
- end
- end
- end
- end
-
- # Test download of an item when the other end doesn't even have the container
- when_the_repository "is empty" do
- when_the_chef_server "has two data bag items" do
- before do
- data_bag "x", { "y" => {}, "z" => {} }
- end
-
- it "knife download of one data bag item itself succeeds" do
- knife("download /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/z.json
- EOM
- end
- end
- end
-
- when_the_repository "has three data bag items" do
- before do
- file "data_bags/x/deleted.json", <<~EOM
- {
- "id": "deleted"
- }
- EOM
- file "data_bags/x/modified.json", <<~EOM
- {
- "id": "modified"
- }
- EOM
- file "data_bags/x/unmodified.json", <<~EOM
- {
- "id": "unmodified"
- }
- EOM
- end
-
- when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
- before do
- data_bag "x", {
- "added" => {},
- "modified" => { "foo" => "bar" },
- "unmodified" => {},
- }
- end
-
- it "knife download of the modified file succeeds" do
- knife("download /data_bags/x/modified.json").should_succeed <<~EOM
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the unmodified file does nothing" do
- knife("download /data_bags/x/unmodified.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the added file succeeds" do
- knife("download /data_bags/x/added.json").should_succeed <<~EOM
- Created /data_bags/x/added.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download of the deleted file does nothing" do
- knife("download /data_bags/x/deleted.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download --purge of the deleted file deletes it" do
- knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/added.json
- M\t/data_bags/x/modified.json
- EOM
- end
- it "knife download of the entire data bag downloads everything" do
- knife("download /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- A\t/data_bags/x/deleted.json
- EOM
- end
- it "knife download --purge of the entire data bag downloads everything" do
- knife("download --purge /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- context "when cwd is the /data_bags directory" do
- before do
- cwd "data_bags"
- end
- it "knife download fails" do
- knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
- end
- it "knife download --purge . downloads everything" do
- knife("download --purge .").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- it "knife download --purge * downloads everything" do
- knife("download --purge *").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", 'name "x"; version "1.0.0"#unmodified'
- file "cookbooks/x-1.0.0/z.rb", ""
- end
-
- when_the_chef_server "has a modified, added and deleted file for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "y.rb" => "hi" }
- end
-
- it "knife download of a modified file succeeds" do
- knife("download /cookbooks/x-1.0.0/metadata.rb").should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x-1.0.0/y.rb
- A\t/cookbooks/x-1.0.0/z.rb
- EOM
- end
- it "knife download of a deleted file does nothing" do
- knife("download /cookbooks/x-1.0.0/z.rb").should_succeed ""
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x-1.0.0/metadata.rb
- D\t/cookbooks/x-1.0.0/y.rb
- A\t/cookbooks/x-1.0.0/z.rb
- EOM
- end
- it "knife download --purge of a deleted file succeeds" do
- knife("download --purge /cookbooks/x-1.0.0/z.rb").should_succeed "Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x-1.0.0/metadata.rb
- D\t/cookbooks/x-1.0.0/y.rb
- EOM
- end
- it "knife download of an added file succeeds" do
- knife("download /cookbooks/x-1.0.0/y.rb").should_succeed "Created /cookbooks/x-1.0.0/y.rb\n"
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x-1.0.0/metadata.rb
- A\t/cookbooks/x-1.0.0/z.rb
- EOM
- end
- it "knife download of the cookbook itself succeeds" do
- knife("download /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0/metadata.rb
- Created /cookbooks/x-1.0.0/y.rb
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- A\t/cookbooks/x-1.0.0/z.rb
- EOM
- end
- it "knife download --purge of the cookbook itself succeeds" do
- knife("download --purge /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0/metadata.rb
- Created /cookbooks/x-1.0.0/y.rb
- Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the latest version" do
- knife("download --purge /cookbooks").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
- Created /cookbooks/x-1.0.1
- Created /cookbooks/x-1.0.1/metadata.rb
- Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife download /cookbooks downloads the updated file" do
- knife("download --purge /cookbooks").should_succeed <<~EOM
- Created /cookbooks/x-0.9.9
- Created /cookbooks/x-0.9.9/metadata.rb
- Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
- Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife download /cookbooks/x downloads the latest version" do
- knife("download --purge /cookbooks").should_succeed <<~EOM
- Created /cookbooks/x-1.0.1
- Created /cookbooks/x-1.0.1/metadata.rb
- Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
- Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife download --purge /cookbooks downloads the old version and deletes the new version" do
- knife("download --purge /cookbooks").should_succeed <<~EOM
- Created /cookbooks/x-0.9.9
- Created /cookbooks/x-0.9.9/metadata.rb
- Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
- Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before do
- environment "x", {}
- end
-
- when_the_repository "has the same environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
-
- it "knife download succeeds" do
- knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
-
- when_the_repository "has the same environment with no name in the file" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
-
- it "knife download succeeds" do
- knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
- end
- end # with versioned cookbooks
-
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "x", "1.0.0"
- end
-
- when_the_repository "is empty" do
- it "knife download /cookbooks/x signs all requests" do
-
- # Check that BasicClient.request() always gets called with X-OPS-USERID
- original_new = Chef::HTTP::BasicClient.method(:new)
- expect(Chef::HTTP::BasicClient).to receive(:new) { |args|
- new_result = original_new.call(*args)
- original_request = new_result.method(:request)
- expect(new_result).to receive(:request) { |method, url, body, headers, &response_handler|
- expect(headers["X-OPS-USERID"]).not_to be_nil
- original_request.call(method, url, body, headers, &response_handler)
- }.at_least(:once)
- new_result
- }.at_least(:once)
-
- knife("download /cookbooks/x").should_succeed <<~EOM
- Created /cookbooks
- Created /cookbooks/x
- Created /cookbooks/x/metadata.rb
- EOM
- end
- end
- end
-
- when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
- before do
- user "foo", {}
- user "bar", {}
- user "foobar", {}
- organization "foo", { "full_name" => "Something" }
- end
-
- before :each do
- Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
- end
-
- when_the_repository "has all the default stuff" do
- before do
- knife("download /").should_succeed <<~EOM
- Created /acls
- Created /acls/clients
- Created /acls/clients/foo-validator.json
- Created /acls/containers
- Created /acls/containers/clients.json
- Created /acls/containers/containers.json
- Created /acls/containers/cookbook_artifacts.json
- Created /acls/containers/cookbooks.json
- Created /acls/containers/data.json
- Created /acls/containers/environments.json
- Created /acls/containers/groups.json
- Created /acls/containers/nodes.json
- Created /acls/containers/policies.json
- Created /acls/containers/policy_groups.json
- Created /acls/containers/roles.json
- Created /acls/containers/sandboxes.json
- Created /acls/cookbook_artifacts
- Created /acls/cookbooks
- Created /acls/data_bags
- Created /acls/environments
- Created /acls/environments/_default.json
- Created /acls/groups
- Created /acls/groups/admins.json
- Created /acls/groups/billing-admins.json
- Created /acls/groups/clients.json
- Created /acls/groups/users.json
- Created /acls/nodes
- Created /acls/policies
- Created /acls/policy_groups
- Created /acls/roles
- Created /acls/organization.json
- Created /clients
- Created /clients/foo-validator.json
- Created /containers
- Created /containers/clients.json
- Created /containers/containers.json
- Created /containers/cookbook_artifacts.json
- Created /containers/cookbooks.json
- Created /containers/data.json
- Created /containers/environments.json
- Created /containers/groups.json
- Created /containers/nodes.json
- Created /containers/policies.json
- Created /containers/policy_groups.json
- Created /containers/roles.json
- Created /containers/sandboxes.json
- Created /cookbook_artifacts
- Created /cookbooks
- Created /data_bags
- Created /environments
- Created /environments/_default.json
- Created /groups
- Created /groups/admins.json
- Created /groups/billing-admins.json
- Created /groups/clients.json
- Created /groups/users.json
- Created /invitations.json
- Created /members.json
- Created /nodes
- Created /org.json
- Created /policies
- Created /policy_groups
- Created /roles
- EOM
- end
-
- context "and the server has one of each thing" do
- before do
- # acl_for %w(organizations foo groups blah)
- client "x", {}
- cookbook "x", "1.0.0"
- cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") }
- container "x", {}
- data_bag "x", { "y" => {} }
- environment "x", {}
- group "x", {}
- org_invite "foo"
- org_member "bar"
- node "x", {}
- policy "x", "1.0.0", {}
- policy "blah", "1.0.0", {}
- policy_group "x", {
- "policies" => {
- "x" => { "revision_id" => "1.0.0" },
- "blah" => { "revision_id" => "1.0.0" },
- },
- }
- role "x", {}
- end
-
- before do
- knife("download /acls /groups/clients.json /groups/users.json").should_succeed <<~EOM
- Created /acls/clients/x.json
- Created /acls/containers/x.json
- Created /acls/cookbook_artifacts/x.json
- Created /acls/cookbooks/x.json
- Created /acls/data_bags/x.json
- Created /acls/environments/x.json
- Created /acls/groups/x.json
- Created /acls/nodes/x.json
- Created /acls/policies/blah.json
- Created /acls/policies/x.json
- Created /acls/policy_groups/x.json
- Created /acls/roles/x.json
- Updated /groups/clients.json
- Updated /groups/users.json
- EOM
- end
-
- it "knife download / downloads everything" do
- knife("download /").should_succeed <<~EOM
- Created /clients/x.json
- Created /containers/x.json
- Created /cookbook_artifacts/x-1x1
- Created /cookbook_artifacts/x-1x1/metadata.rb
- Created /cookbooks/x
- Created /cookbooks/x/metadata.rb
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments/x.json
- Created /groups/x.json
- Updated /invitations.json
- Updated /members.json
- Created /nodes/x.json
- Created /policies/blah-1.0.0.json
- Created /policies/x-1.0.0.json
- Created /policy_groups/x.json
- Created /roles/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
-
- context "and the repository has an identical copy of each thing" do
- before do
- # TODO We have to upload acls for an existing group due to a lack of
- # dependency detection during upload. Fix that!
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "containers/x.json", {}
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/x.json", {}
- file "groups/x.json", {}
- file "invitations.json", [ "foo" ]
- file "members.json", [ "bar" ]
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "org.json", { "full_name" => "Something" }
- file "policies/x-1.0.0.json", {}
- file "policies/blah-1.0.0.json", {}
- file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } }
- file "roles/x.json", {}
- end
-
- it "knife download makes no changes" do
- knife("download /").should_succeed ""
- end
- end
-
- context "and the repository has a slightly different copy of each thing" do
- before do
- # acl_for %w(organizations foo groups blah)
- file "clients/x.json", { "validator" => true }
- file "containers/x.json", {}
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.1")
- file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.1")
- file "data_bags/x/y.json", { "a" => "b" }
- file "environments/x.json", { "description" => "foo" }
- file "groups/x.json", { "description" => "foo" }
- file "groups/x.json", { "groups" => [ "admin" ] }
- file "nodes/x.json", { "normal" => { "tags" => [] }, "run_list" => [ "blah" ] }
- file "org.json", { "full_name" => "Something Else " }
- file "policies/x-1.0.0.json", { "run_list" => [ "blah" ] }
- file "policy_groups/x.json", {
- "policies" => {
- "x" => { "revision_id" => "1.0.1" },
- "y" => { "revision_id" => "1.0.0" },
- },
- }
- file "roles/x.json", { "run_list" => [ "blah" ] }
- end
-
- it "knife download updates everything" do
- knife("download /").should_succeed <<~EOM
- Updated /clients/x.json
- Updated /cookbook_artifacts/x-1x1/metadata.rb
- Updated /cookbooks/x/metadata.rb
- Updated /data_bags/x/y.json
- Updated /environments/x.json
- Updated /groups/x.json
- Updated /invitations.json
- Updated /members.json
- Updated /nodes/x.json
- Updated /org.json
- Created /policies/blah-1.0.0.json
- Updated /policies/x-1.0.0.json
- Updated /policy_groups/x.json
- Updated /roles/x.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
- end
- end
- end
-end
diff --git a/spec/integration/knife/environment_compare_spec.rb b/spec/integration/knife/environment_compare_spec.rb
deleted file mode 100644
index 7a623adf4c..0000000000
--- a/spec/integration/knife/environment_compare_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment compare", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some environments" do
- before do
- cookbook "blah", "1.0.1"
- cookbook "blah", "1.1.1"
- cookbook "krad", "1.1.1"
- environment "x", {
- "cookbook_versions" => {
- "blah" => "= 1.0.0",
- "krad" => ">= 1.0.0",
- },
- }
- environment "y", {
- "cookbook_versions" => {
- "blah" => "= 1.1.0",
- "krad" => ">= 1.0.0",
- },
- }
- end
-
- # rubocop:disable Layout/TrailingWhitespace
- it "displays the cookbooks for a single environment" do
- knife("environment compare x").should_succeed <<~EOM
- x
- blah = 1.0.0
- krad >= 1.0.0
-
- EOM
- end
-
- it "compares the cookbooks for two environments" do
- knife("environment compare x y").should_succeed <<~EOM
- x y
- blah = 1.0.0 = 1.1.0
- krad >= 1.0.0 >= 1.0.0
-
- EOM
- end
-
- it "compares the cookbooks for all environments" do
- knife("environment compare --all").should_succeed <<~EOM
- x y
- blah = 1.0.0 = 1.1.0
- krad >= 1.0.0 >= 1.0.0
-
- EOM
- end
- # rubocop:enable Layout/TrailingWhitespace
- end
-end
diff --git a/spec/integration/knife/environment_create_spec.rb b/spec/integration/knife/environment_create_spec.rb
deleted file mode 100644
index 66ba9ed6e6..0000000000
--- a/spec/integration/knife/environment_create_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Created bah\n" }
-
- when_the_chef_server "is empty" do
- it "creates a new environment" do
- knife("environment create bah").should_succeed out
- end
-
- it "refuses to add an existing environment" do
- pending "Knife environment create must not blindly overwrite an existing environment"
- knife("environment create bah").should_succeed out
- expect { knife("environment create bah") }.to raise_error(Net::HTTPClientException)
- end
-
- end
-end
diff --git a/spec/integration/knife/environment_delete_spec.rb b/spec/integration/knife/environment_delete_spec.rb
deleted file mode 100644
index f55a1c96bd..0000000000
--- a/spec/integration/knife/environment_delete_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has an environment" do
- before do
- environment "y", {}
- end
-
- it "deletes an environment" do
- knife("environment delete y", input: "y").should_succeed "Do you really want to delete y? (Y/N) Deleted y\n"
- end
-
- end
-end
diff --git a/spec/integration/knife/environment_from_file_spec.rb b/spec/integration/knife/environment_from_file_spec.rb
deleted file mode 100644
index f9d35f4d47..0000000000
--- a/spec/integration/knife/environment_from_file_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment from file", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- # include_context "default config options"
-
- let(:env_dir) { "#{@repository_dir}/environments" }
-
- when_the_chef_server "is empty" do
- when_the_repository "has some environments" do
- before do
-
- file "environments/cons.json", <<~EOM
- {
- "name": "cons",
- "description": "An environment",
- "cookbook_versions": {
-
- },
- "json_class": "Chef::Environment",
- "chef_type": "environment",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- file "environments/car.json", <<~EOM
- {
- "name": "car",
- "description": "An environment for list nodes",
- "cookbook_versions": {
-
- },
- "json_class": "Chef::Environment",
- "chef_type": "environment",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- file "environments/cdr.json", <<~EOM
- {
- "name": "cdr",
- "description": "An environment for last nodes",
- "cookbook_versions": {
-
- },
- "json_class": "Chef::Environment",
- "chef_type": "environment",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- end
-
- it "uploads a single file" do
- knife("environment from file #{env_dir}/cons.json").should_succeed stderr: <<~EOM
- Updated Environment cons
- EOM
- end
-
- it "uploads many files" do
- knife("environment from file #{env_dir}/cons.json #{env_dir}/car.json #{env_dir}/cdr.json").should_succeed stderr: <<~EOM
- Updated Environment cons
- Updated Environment car
- Updated Environment cdr
- EOM
- end
-
- it "uploads all environments in the repository" do
- cwd(".")
- knife("environment from file --all")
- knife("environment list").should_succeed <<~EOM
- _default
- car
- cdr
- cons
- EOM
- end
-
- end
- end
-end
diff --git a/spec/integration/knife/environment_list_spec.rb b/spec/integration/knife/environment_list_spec.rb
deleted file mode 100644
index dba685a82e..0000000000
--- a/spec/integration/knife/environment_list_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some environments" do
- before do
- environment "b", {}
- environment "y", {}
- end
-
- it "lists all the environments" do
- knife("environment list").should_succeed <<~EOM
- _default
- b
- y
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/environment_show_spec.rb b/spec/integration/knife/environment_show_spec.rb
deleted file mode 100644
index de6ad1efd4..0000000000
--- a/spec/integration/knife/environment_show_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife environment show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some environments" do
- before do
- environment "b", {
- "default_attributes" => { "foo" => "bar", "baz" => { "raz.my" => "mataz" } },
- }
- end
-
- # rubocop:disable Layout/TrailingWhitespace
- it "shows an environment" do
- knife("environment show b").should_succeed <<~EOM
- chef_type: environment
- cookbook_versions:
- default_attributes:
- baz:
- raz.my: mataz
- foo: bar
- description:
- json_class: Chef::Environment
- name: b
- override_attributes:
- EOM
- end
- # rubocop:enable Layout/TrailingWhitespace
-
- it "shows the requested attribute of an environment" do
- knife("environment show b -a default_attributes").should_succeed <<~EOM
- b:
- default_attributes:
- baz:
- raz.my: mataz
- foo: bar
- EOM
- end
-
- it "shows the requested nested attribute of an environment" do
- knife("environment show b -a default_attributes.baz").should_succeed <<~EON
- b:
- default_attributes.baz:
- raz.my: mataz
- EON
- end
-
- it "shows the requested attribute of an environment with custom field separator" do
- knife("environment show b -S: -a default_attributes:baz").should_succeed <<~EOT
- b:
- default_attributes:baz:
- raz.my: mataz
- EOT
- end
- end
-end
diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb
deleted file mode 100644
index 4c711f3306..0000000000
--- a/spec/integration/knife/list_spec.rb
+++ /dev/null
@@ -1,1060 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/list"
-
-describe "knife list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "is empty" do
- it "knife list / returns all top level directories" do
- knife("list /").should_succeed <<~EOM
- /clients
- /cookbooks
- /data_bags
- /environments
- /nodes
- /roles
- /users
- EOM
- end
-
- it "knife list -R / returns everything" do
- knife("list -R /").should_succeed <<~EOM
- /:
- clients
- cookbooks
- data_bags
- environments
- nodes
- roles
- users
-
- /clients:
- chef-validator.json
- chef-webui.json
-
- /cookbooks:
-
- /data_bags:
-
- /environments:
- _default.json
-
- /nodes:
-
- /roles:
-
- /users:
- admin.json
- EOM
- end
- end
-
- when_the_chef_server "has plenty of stuff in it" do
- before do
- client "client1", {}
- client "client2", {}
- cookbook "cookbook1", "1.0.0"
- cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } }
- data_bag "bag1", { "item1" => {}, "item2" => {} }
- data_bag "bag2", { "item1" => {}, "item2" => {} }
- environment "environment1", {}
- environment "environment2", {}
- node "node1", {}
- node "node2", {}
- policy "policy1", "1.2.3", {}
- policy "policy2", "1.2.3", {}
- policy "policy2", "1.3.5", {}
- role "role1", {}
- role "role2", {}
- user "user1", {}
- user "user2", {}
- end
-
- it "knife list / returns all top level directories" do
- knife("list /").should_succeed <<~EOM
- /clients
- /cookbooks
- /data_bags
- /environments
- /nodes
- /roles
- /users
- EOM
- end
-
- it "knife list -R / returns everything" do
- knife("list -R /").should_succeed <<~EOM
- /:
- clients
- cookbooks
- data_bags
- environments
- nodes
- roles
- users
-
- /clients:
- chef-validator.json
- chef-webui.json
- client1.json
- client2.json
-
- /cookbooks:
- cookbook1
- cookbook2
-
- /cookbooks/cookbook1:
- metadata.rb
-
- /cookbooks/cookbook2:
- metadata.rb
- recipes
-
- /cookbooks/cookbook2/recipes:
- default.rb
-
- /data_bags:
- bag1
- bag2
-
- /data_bags/bag1:
- item1.json
- item2.json
-
- /data_bags/bag2:
- item1.json
- item2.json
-
- /environments:
- _default.json
- environment1.json
- environment2.json
-
- /nodes:
- node1.json
- node2.json
-
- /roles:
- role1.json
- role2.json
-
- /users:
- admin.json
- user1.json
- user2.json
- EOM
- end
-
- it "knife list -R --flat / returns everything" do
- knife("list -R --flat /").should_succeed <<~EOM
- /clients
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/client1.json
- /clients/client2.json
- /cookbooks
- /cookbooks/cookbook1
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes
- /cookbooks/cookbook2/recipes/default.rb
- /data_bags
- /data_bags/bag1
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /nodes
- /nodes/node1.json
- /nodes/node2.json
- /roles
- /roles/role1.json
- /roles/role2.json
- /users
- /users/admin.json
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- it "knife list -Rfp / returns everything" do
- knife("list -Rfp /").should_succeed <<~EOM
- /clients/
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/client1.json
- /clients/client2.json
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes/
- /cookbooks/cookbook2/recipes/default.rb
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /nodes/
- /nodes/node1.json
- /nodes/node2.json
- /roles/
- /roles/role1.json
- /roles/role2.json
- /users/
- /users/admin.json
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- it "knife list /cookbooks returns the list of cookbooks" do
- knife("list /cookbooks").should_succeed <<~EOM
- /cookbooks/cookbook1
- /cookbooks/cookbook2
- EOM
- end
-
- it "knife list /cookbooks/*2/*/*.rb returns the one file" do
- knife("list /cookbooks/*2/*/*.rb").should_succeed "/cookbooks/cookbook2/recipes/default.rb\n"
- end
-
- it "knife list /**.rb returns all ruby files" do
- knife("list /**.rb").should_succeed <<~EOM
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes/default.rb
- EOM
- end
-
- it "knife list /cookbooks/**.rb returns all ruby files" do
- knife("list /cookbooks/**.rb").should_succeed <<~EOM
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes/default.rb
- EOM
- end
-
- it "knife list /**.json returns all json files" do
- knife("list /**.json").should_succeed <<~EOM
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/client1.json
- /clients/client2.json
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /nodes/node1.json
- /nodes/node2.json
- /roles/role1.json
- /roles/role2.json
- /users/admin.json
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- it "knife list /data**.json returns all data bag json files" do
- knife("list /data**.json").should_succeed <<~EOM
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- EOM
- end
-
- it "knife list /environments/missing_file.json reports missing file" do
- knife("list /environments/missing_file.json").should_fail "ERROR: /environments/missing_file.json: No such file or directory\n"
- end
-
- context "missing file/directory exact match tests" do
- it "knife list /blarghle reports missing directory" do
- knife("list /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n"
- end
- end
-
- context "symlink tests" do
- when_the_repository "is empty" do
- context "when cwd is at the top of the repository" do
- before { cwd "." }
-
- it "knife list -Rfp returns everything" do
- knife("list -Rfp").should_succeed <<~EOM
- clients/
- clients/chef-validator.json
- clients/chef-webui.json
- clients/client1.json
- clients/client2.json
- cookbooks/
- cookbooks/cookbook1/
- cookbooks/cookbook1/metadata.rb
- cookbooks/cookbook2/
- cookbooks/cookbook2/metadata.rb
- cookbooks/cookbook2/recipes/
- cookbooks/cookbook2/recipes/default.rb
- data_bags/
- data_bags/bag1/
- data_bags/bag1/item1.json
- data_bags/bag1/item2.json
- data_bags/bag2/
- data_bags/bag2/item1.json
- data_bags/bag2/item2.json
- environments/
- environments/_default.json
- environments/environment1.json
- environments/environment2.json
- nodes/
- nodes/node1.json
- nodes/node2.json
- roles/
- roles/role1.json
- roles/role2.json
- users/
- users/admin.json
- users/user1.json
- users/user2.json
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbooks directory" do
- before { directory "cookbooks" }
- context "when cwd is in cookbooks/" do
- before { cwd "cookbooks" }
-
- it "knife list -Rfp / returns everything" do
- knife("list -Rfp /").should_succeed <<~EOM
- /clients/
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/client1.json
- /clients/client2.json
- ./
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /nodes/
- /nodes/node1.json
- /nodes/node2.json
- /roles/
- /roles/role1.json
- /roles/role2.json
- /users/
- /users/admin.json
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- it "knife list -Rfp .. returns everything" do
- knife("list -Rfp ..").should_succeed <<~EOM
- /clients/
- /clients/chef-validator.json
- /clients/chef-webui.json
- /clients/client1.json
- /clients/client2.json
- ./
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /nodes/
- /nodes/node1.json
- /nodes/node2.json
- /roles/
- /roles/role1.json
- /roles/role2.json
- /users/
- /users/admin.json
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbooks/cookbook2 directory" do
- before { directory "cookbooks/cookbook2" }
-
- context "when cwd is in cookbooks/cookbook2" do
- before { cwd "cookbooks/cookbook2" }
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- metadata.rb
- recipes/
- recipes/default.rb
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbooks directory and a symlinked cookbooks directory", skip: (ChefUtils.windows?) do
- before do
- directory "cookbooks"
- symlink "symlinked", "cookbooks"
- end
-
- context "when cwd is in cookbooks/" do
- before { cwd "cookbooks" }
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- EOM
- end
- end
-
- context "when cwd is in symlinked/" do
- before { cwd "symlinked" }
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- EOM
- end
- end
- end
-
- when_the_repository "has a real_cookbooks directory and a cookbooks symlink to it", skip: (ChefUtils.windows?) do
- before do
- directory "real_cookbooks"
- symlink "cookbooks", "real_cookbooks"
- end
-
- context "when cwd is in real_cookbooks/" do
- before { cwd "real_cookbooks" }
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- EOM
- end
- end
-
- context "when cwd is in cookbooks/" do
- before { cwd "cookbooks" }
-
- it "knife list -Rfp returns cookbooks" do
- knife("list -Rfp").should_succeed <<~EOM
- cookbook1/
- cookbook1/metadata.rb
- cookbook2/
- cookbook2/metadata.rb
- cookbook2/recipes/
- cookbook2/recipes/default.rb
- EOM
- end
- end
- end
- end
- end
-
- context "--local" do
- when_the_repository "is empty" do
- it "knife list --local / returns nothing" do
- knife("list --local /").should_succeed ""
- end
-
- it "knife list /roles returns nothing" do
- knife("list --local /roles").should_fail "ERROR: /roles: No such file or directory\n"
- end
- end
-
- when_the_repository "has a bunch of stuff" do
- before do
- file "clients/client1.json", {}
- file "clients/client2.json", {}
-
- directory "cookbooks/cookbook1" do
- file "metadata.rb", cb_metadata("cookbook1", "1.0.0")
- end
- directory "cookbooks/cookbook2" do
- file "metadata.rb", cb_metadata("cookbook2", "2.0.0")
- file "recipes/default.rb", ""
- end
-
- directory "data_bags" do
- directory "bag1" do
- file "item1.json", {}
- file "item2.json", {}
- end
- directory "bag2" do
- file "item1.json", {}
- file "item2.json", {}
- end
- end
-
- file "environments/environment1.json", {}
- file "environments/environment2.json", {}
- file "nodes/node1.json", {}
- file "nodes/node2.json", {}
-
- file "roles/role1.json", {}
- file "roles/role2.json", {}
- file "users/user1.json", {}
- file "users/user2.json", {}
- end
-
- it "knife list -Rfp / returns everything" do
- knife("list -Rp --local --flat /").should_succeed <<~EOM
- /clients/
- /clients/client1.json
- /clients/client2.json
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes/
- /cookbooks/cookbook2/recipes/default.rb
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/
- /environments/environment1.json
- /environments/environment2.json
- /nodes/
- /nodes/node1.json
- /nodes/node2.json
- /roles/
- /roles/role1.json
- /roles/role2.json
- /users/
- /users/user1.json
- /users/user2.json
- EOM
- end
-
- context "missing file/directory tests" do
- it "knife list --local /blarghle reports missing directory" do
- knife("list --local /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n"
- end
-
- it "knife list /roles/blarghle reports missing directory" do
- knife("list --local /roles/blarghle").should_fail "ERROR: /roles/blarghle: No such file or directory\n"
- end
-
- it "knife list /roles/blarghle/blorghle reports missing directory" do
- knife("list --local /roles/blarghle/blorghle").should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n"
- end
- end
- end
- end
-
- when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
- before do
- organization "foo"
- end
-
- before :each do
- Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
- end
-
- context "and is empty" do
- it "knife list / returns all top level directories" do
- knife("list /").should_succeed <<~EOM
- /acls
- /clients
- /containers
- /cookbook_artifacts
- /cookbooks
- /data_bags
- /environments
- /groups
- /invitations.json
- /members.json
- /nodes
- /org.json
- /policies
- /policy_groups
- /roles
- EOM
- end
-
- it "knife list -R / returns everything" do
- knife("list -R /").should_succeed <<~EOM
- /:
- acls
- clients
- containers
- cookbook_artifacts
- cookbooks
- data_bags
- environments
- groups
- invitations.json
- members.json
- nodes
- org.json
- policies
- policy_groups
- roles
-
- /acls:
- clients
- containers
- cookbook_artifacts
- cookbooks
- data_bags
- environments
- groups
- nodes
- organization.json
- policies
- policy_groups
- roles
-
- /acls/clients:
- foo-validator.json
-
- /acls/containers:
- clients.json
- containers.json
- cookbook_artifacts.json
- cookbooks.json
- data.json
- environments.json
- groups.json
- nodes.json
- policies.json
- policy_groups.json
- roles.json
- sandboxes.json
-
- /acls/cookbook_artifacts:
-
- /acls/cookbooks:
-
- /acls/data_bags:
-
- /acls/environments:
- _default.json
-
- /acls/groups:
- admins.json
- billing-admins.json
- clients.json
- users.json
-
- /acls/nodes:
-
- /acls/policies:
-
- /acls/policy_groups:
-
- /acls/roles:
-
- /clients:
- foo-validator.json
-
- /containers:
- clients.json
- containers.json
- cookbook_artifacts.json
- cookbooks.json
- data.json
- environments.json
- groups.json
- nodes.json
- policies.json
- policy_groups.json
- roles.json
- sandboxes.json
-
- /cookbook_artifacts:
-
- /cookbooks:
-
- /data_bags:
-
- /environments:
- _default.json
-
- /groups:
- admins.json
- billing-admins.json
- clients.json
- users.json
-
- /nodes:
-
- /policies:
-
- /policy_groups:
-
- /roles:
- EOM
- end
- end
-
- it "knife list -R / returns everything" do
- knife("list -R /").should_succeed <<~EOM
- /:
- acls
- clients
- containers
- cookbook_artifacts
- cookbooks
- data_bags
- environments
- groups
- invitations.json
- members.json
- nodes
- org.json
- policies
- policy_groups
- roles
-
- /acls:
- clients
- containers
- cookbook_artifacts
- cookbooks
- data_bags
- environments
- groups
- nodes
- organization.json
- policies
- policy_groups
- roles
-
- /acls/clients:
- foo-validator.json
-
- /acls/containers:
- clients.json
- containers.json
- cookbook_artifacts.json
- cookbooks.json
- data.json
- environments.json
- groups.json
- nodes.json
- policies.json
- policy_groups.json
- roles.json
- sandboxes.json
-
- /acls/cookbook_artifacts:
-
- /acls/cookbooks:
-
- /acls/data_bags:
-
- /acls/environments:
- _default.json
-
- /acls/groups:
- admins.json
- billing-admins.json
- clients.json
- users.json
-
- /acls/nodes:
-
- /acls/policies:
-
- /acls/policy_groups:
-
- /acls/roles:
-
- /clients:
- foo-validator.json
-
- /containers:
- clients.json
- containers.json
- cookbook_artifacts.json
- cookbooks.json
- data.json
- environments.json
- groups.json
- nodes.json
- policies.json
- policy_groups.json
- roles.json
- sandboxes.json
-
- /cookbook_artifacts:
-
- /cookbooks:
-
- /data_bags:
-
- /environments:
- _default.json
-
- /groups:
- admins.json
- billing-admins.json
- clients.json
- users.json
-
- /nodes:
-
- /policies:
-
- /policy_groups:
-
- /roles:
- EOM
- end
-
- context "has plenty of stuff in it" do
- before do
- client "client1", {}
- client "client2", {}
- container "container1", {}
- container "container2", {}
- cookbook "cookbook1", "1.0.0"
- cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } }
- cookbook_artifact "cookbook_artifact1", "1x1"
- cookbook_artifact "cookbook_artifact2", "2x2", { "recipes" => { "default.rb" => "" } }
- data_bag "bag1", { "item1" => {}, "item2" => {} }
- data_bag "bag2", { "item1" => {}, "item2" => {} }
- environment "environment1", {}
- environment "environment2", {}
- group "group1", {}
- group "group2", {}
- node "node1", {}
- node "node2", {}
- org_invite "user1"
- org_member "user2"
- policy "policy1", "1.2.3", {}
- policy "policy2", "1.2.3", {}
- policy "policy2", "1.3.5", {}
- policy_group "policy_group1", { "policies" => { "policy1" => { "revision_id" => "1.2.3" } } }
- policy_group "policy_group2", { "policies" => { "policy2" => { "revision_id" => "1.3.5" } } }
- role "role1", {}
- role "role2", {}
- user "user1", {}
- user "user2", {}
- end
-
- it "knife list -Rfp / returns everything" do
- knife("list -Rfp /").should_succeed <<~EOM
- /acls/
- /acls/clients/
- /acls/clients/client1.json
- /acls/clients/client2.json
- /acls/clients/foo-validator.json
- /acls/containers/
- /acls/containers/clients.json
- /acls/containers/container1.json
- /acls/containers/container2.json
- /acls/containers/containers.json
- /acls/containers/cookbook_artifacts.json
- /acls/containers/cookbooks.json
- /acls/containers/data.json
- /acls/containers/environments.json
- /acls/containers/groups.json
- /acls/containers/nodes.json
- /acls/containers/policies.json
- /acls/containers/policy_groups.json
- /acls/containers/roles.json
- /acls/containers/sandboxes.json
- /acls/cookbook_artifacts/
- /acls/cookbook_artifacts/cookbook_artifact1.json
- /acls/cookbook_artifacts/cookbook_artifact2.json
- /acls/cookbooks/
- /acls/cookbooks/cookbook1.json
- /acls/cookbooks/cookbook2.json
- /acls/data_bags/
- /acls/data_bags/bag1.json
- /acls/data_bags/bag2.json
- /acls/environments/
- /acls/environments/_default.json
- /acls/environments/environment1.json
- /acls/environments/environment2.json
- /acls/groups/
- /acls/groups/admins.json
- /acls/groups/billing-admins.json
- /acls/groups/clients.json
- /acls/groups/group1.json
- /acls/groups/group2.json
- /acls/groups/users.json
- /acls/nodes/
- /acls/nodes/node1.json
- /acls/nodes/node2.json
- /acls/organization.json
- /acls/policies/
- /acls/policies/policy1.json
- /acls/policies/policy2.json
- /acls/policy_groups/
- /acls/policy_groups/policy_group1.json
- /acls/policy_groups/policy_group2.json
- /acls/roles/
- /acls/roles/role1.json
- /acls/roles/role2.json
- /clients/
- /clients/client1.json
- /clients/client2.json
- /clients/foo-validator.json
- /containers/
- /containers/clients.json
- /containers/container1.json
- /containers/container2.json
- /containers/containers.json
- /containers/cookbook_artifacts.json
- /containers/cookbooks.json
- /containers/data.json
- /containers/environments.json
- /containers/groups.json
- /containers/nodes.json
- /containers/policies.json
- /containers/policy_groups.json
- /containers/roles.json
- /containers/sandboxes.json
- /cookbook_artifacts/
- /cookbook_artifacts/cookbook_artifact1-1x1/
- /cookbook_artifacts/cookbook_artifact1-1x1/metadata.rb
- /cookbook_artifacts/cookbook_artifact2-2x2/
- /cookbook_artifacts/cookbook_artifact2-2x2/metadata.rb
- /cookbook_artifacts/cookbook_artifact2-2x2/recipes/
- /cookbook_artifacts/cookbook_artifact2-2x2/recipes/default.rb
- /cookbooks/
- /cookbooks/cookbook1/
- /cookbooks/cookbook1/metadata.rb
- /cookbooks/cookbook2/
- /cookbooks/cookbook2/metadata.rb
- /cookbooks/cookbook2/recipes/
- /cookbooks/cookbook2/recipes/default.rb
- /data_bags/
- /data_bags/bag1/
- /data_bags/bag1/item1.json
- /data_bags/bag1/item2.json
- /data_bags/bag2/
- /data_bags/bag2/item1.json
- /data_bags/bag2/item2.json
- /environments/
- /environments/_default.json
- /environments/environment1.json
- /environments/environment2.json
- /groups/
- /groups/admins.json
- /groups/billing-admins.json
- /groups/clients.json
- /groups/group1.json
- /groups/group2.json
- /groups/users.json
- /invitations.json
- /members.json
- /nodes/
- /nodes/node1.json
- /nodes/node2.json
- /org.json
- /policies/
- /policies/policy1-1.2.3.json
- /policies/policy2-1.2.3.json
- /policies/policy2-1.3.5.json
- /policy_groups/
- /policy_groups/policy_group1.json
- /policy_groups/policy_group2.json
- /roles/
- /roles/role1.json
- /roles/role2.json
- EOM
- end
- end
- end
-end
diff --git a/spec/integration/knife/node_bulk_delete_spec.rb b/spec/integration/knife/node_bulk_delete_spec.rb
deleted file mode 100644
index dcaa71ef58..0000000000
--- a/spec/integration/knife/node_bulk_delete_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node bulk delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some nodes" do
- before do
- node "cons", {}
- node "car", {}
- node "cdr", {}
- node "cat", {}
- end
-
- it "deletes all matching nodes" do
- knife("node bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
- The following nodes will be deleted:
-
- car cat
-
- Are you sure you want to delete these nodes? (Y/N) Deleted node car
- Deleted node cat
- EOM
-
- knife("node list").should_succeed <<~EOM
- cdr
- cons
- EOM
- end
- end
-
-end
diff --git a/spec/integration/knife/node_create_spec.rb b/spec/integration/knife/node_create_spec.rb
deleted file mode 100644
index e8f6d71694..0000000000
--- a/spec/integration/knife/node_create_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "openssl"
-
-describe "knife node create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Created node[bah]\n" }
-
- when_the_chef_server "is empty" do
- it "creates a new node" do
- knife("node create bah").should_succeed out
- end
-
- it "creates a new validator node" do
- knife("node create bah").should_succeed out
- knife("node show bah").should_succeed(/Node Name: bah/)
- end
-
- it "refuses to add an existing node" do
- pending "Knife node create must not blindly overwrite an existing node"
- knife("node create bah").should_succeed out
- expect { knife("node create bah") }.to raise_error(Net::HTTPClientException)
- end
-
- end
-end
diff --git a/spec/integration/knife/node_delete_spec.rb b/spec/integration/knife/node_delete_spec.rb
deleted file mode 100644
index c743d6e03f..0000000000
--- a/spec/integration/knife/node_delete_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some nodes" do
- before do
- node "cons", {}
- node "car", {}
- node "cdr", {}
- node "cat", {}
- end
-
- it "deletes a node" do
- knife("node delete car", input: "Y").should_succeed <<~EOM
- Do you really want to delete car? (Y/N) Deleted node[car]
- EOM
-
- knife("node list").should_succeed <<~EOM
- cat
- cdr
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/node_environment_set_spec.rb b/spec/integration/knife/node_environment_set_spec.rb
deleted file mode 100644
index 16a86dbc30..0000000000
--- a/spec/integration/knife/node_environment_set_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node environment set", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node and an environment" do
- before do
- node "cons", {}
- environment "lisp", {}
- end
-
- it "sets an environment on a node" do
- knife("node environment set cons lisp").should_succeed(/chef_environment:.*lisp/)
- knife("node show cons -a chef_environment").should_succeed <<~EOM
- cons:
- chef_environment: lisp
- EOM
- end
-
- it "with no environment" do
- knife("node environment set adam").should_fail stderr: "FATAL: You must specify a node name and an environment.\n",
- stdout: /^USAGE: knife node environment set NODE ENVIRONMENT\n/
- end
- end
-end
diff --git a/spec/integration/knife/node_from_file_spec.rb b/spec/integration/knife/node_from_file_spec.rb
deleted file mode 100644
index 6f7e0780f0..0000000000
--- a/spec/integration/knife/node_from_file_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node from file", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- # include_context "default config options"
-
- let(:node_dir) { "#{@repository_dir}/nodes" }
-
- when_the_chef_server "is empty" do
- when_the_repository "has some nodes" do
- before do
-
- file "nodes/cons.json", <<~EOM
- {
- "name": "cons",
- "chef_environment": "_default",
- "run_list": [
- "recipe[cons]"
- ]
- ,
- "normal": {
- "tags": [
-
- ]
- }
- }
- EOM
-
- end
-
- it "uploads a single file" do
- knife("node from file #{node_dir}/cons.json").should_succeed stderr: <<~EOM
- Updated Node cons
- EOM
- end
-
- end
- end
-end
diff --git a/spec/integration/knife/node_list_spec.rb b/spec/integration/knife/node_list_spec.rb
deleted file mode 100644
index 8d3bc29a5a..0000000000
--- a/spec/integration/knife/node_list_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some nodes" do
- before do
- node "cons", {}
- node "car", {}
- node "cdr", {}
- node "cat", {}
- end
-
- it "lists all cookbooks" do
- knife("node list").should_succeed <<~EOM
- car
- cat
- cdr
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/node_run_list_add_spec.rb b/spec/integration/knife/node_run_list_add_spec.rb
deleted file mode 100644
index f13e584526..0000000000
--- a/spec/integration/knife/node_run_list_add_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node run list add", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node with no run_list" do
- before do
- node "cons", {}
- end
-
- it "sets the run list" do
- knife("node run list add cons recipe[foo]").should_succeed(/run_list:\s*recipe\[foo\]\n/)
- end
- end
-
- when_the_chef_server "has a node with a run_list" do
- before do
- node "cons", { run_list: ["recipe[bar]"] }
- end
-
- it "appends to the run list" do
- knife("node run list add cons recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m)
- end
-
- it "adds to the run list before the specified item" do
- knife("node run list add cons -b recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[foo\]\n\s*recipe\[bar\]\n/m)
- end
-
- it "adds to the run list after the specified item" do
- knife("node run list add cons -a recipe[bar] recipe[foo]").should_succeed(/run_list:\n\s*recipe\[bar\]\n\s*recipe\[foo\]\n/m)
- end
- end
-end
diff --git a/spec/integration/knife/node_run_list_remove_spec.rb b/spec/integration/knife/node_run_list_remove_spec.rb
deleted file mode 100644
index 55f224b5ac..0000000000
--- a/spec/integration/knife/node_run_list_remove_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node run list remove", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node with a run_list" do
- before do
- node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
- end
-
- it "removes the item from the run list" do
- knife("node run list remove cons recipe[bar]").should_succeed(/run_list:\s*recipe\[foo\]\n/m)
- end
- end
-end
diff --git a/spec/integration/knife/node_run_list_set_spec.rb b/spec/integration/knife/node_run_list_set_spec.rb
deleted file mode 100644
index e642afc1ce..0000000000
--- a/spec/integration/knife/node_run_list_set_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node run list set", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node with a run_list" do
- before do
- node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
- end
-
- it "sets the run list" do
- knife("node run list set cons recipe[bar]").should_succeed(/run_list:\s*recipe\[bar\]\n/m)
- end
-
- it "with no role or recipe" do
- knife("node run list set cons").should_fail stderr: "FATAL: You must supply both a node name and a run list.\n",
- stdout: /^USAGE: knife node run_list set NODE ENTRIES \(options\)/m
- end
- end
-end
diff --git a/spec/integration/knife/node_show_spec.rb b/spec/integration/knife/node_show_spec.rb
deleted file mode 100644
index cf3f166699..0000000000
--- a/spec/integration/knife/node_show_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node with a run_list" do
- before do
- node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
- end
-
- it "shows the node" do
- knife("node show cons").should_succeed(/Run List:\s*recipe\[bar\], recipe\[foo\]\n/m)
- end
- end
-end
diff --git a/spec/integration/knife/raw_spec.rb b/spec/integration/knife/raw_spec.rb
deleted file mode 100644
index ba26def473..0000000000
--- a/spec/integration/knife/raw_spec.rb
+++ /dev/null
@@ -1,297 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/raw"
-require "chef/knife/show"
-require "tiny_server"
-
-describe "knife raw", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has one of each thing" do
- before do
- client "x", "{}"
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => "{}" }
- environment "x", "{}"
- node "x", "{}"
- role "x", "{}"
- user "x", "{}"
- end
-
- it "knife raw /nodes/x returns the node" do
- knife("raw /nodes/x").should_succeed <<~EOM
- {
- "name": "x",
- "json_class": "Chef::Node",
- "chef_type": "node",
- "chef_environment": "_default",
- "override": {
-
- },
- "normal": {
- "tags": [
-
- ]
- },
- "default": {
-
- },
- "automatic": {
-
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife raw /blarghle returns 404" do
- knife("raw /blarghle").should_fail(/ERROR: Server responded with error 404 "Not Found\s*"/)
- end
-
- it "knife raw -m DELETE /roles/x succeeds" do
- knife("raw -m DELETE /roles/x").should_succeed <<~EOM
- {
- "name": "x",
- "description": "",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- knife("show /roles/x.json").should_fail "ERROR: /roles/x.json: No such file or directory\n"
- end
-
- it "knife raw -m PUT -i blah.txt /roles/x succeeds" do
- Tempfile.open("raw_put_input") do |file|
- file.write <<~EOM
- {
- "name": "x",
- "description": "eek",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- file.close
-
- knife("raw -m PUT -i #{file.path} /roles/x").should_succeed <<~EOM
- {
- "name": "x",
- "description": "eek",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- knife("show /roles/x.json").should_succeed <<~EOM
- /roles/x.json:
- {
- "name": "x",
- "description": "eek",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- end
- end
-
- it "knife raw -m POST -i blah.txt /roles succeeds" do
- Tempfile.open("raw_put_input") do |file|
- file.write <<~EOM
- {
- "name": "y",
- "description": "eek",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
- },
- "override_attributes": {
- },
- "run_list": [
-
- ],
- "env_run_lists": {
- }
- }
- EOM
- file.close
-
- knife("raw -m POST -i #{file.path} /roles").should_succeed <<~EOM
- {
- "uri": "#{Chef::Config.chef_server_url}/roles/y"
- }
- EOM
- knife("show /roles/y.json").should_succeed <<~EOM
- /roles/y.json:
- {
- "name": "y",
- "description": "eek",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- end
- end
-
- context "When a server returns raw json" do
- def start_tiny_server(**server_opts)
- @server = TinyServer::Manager.new(**server_opts)
- @server.start
- @api = TinyServer::API.instance
- @api.clear
-
- @api.get("/blah", 200, nil, { "Content-Type" => "application/json" }) do
- '{ "x": "y", "a": "b" }'
- end
- end
-
- def stop_tiny_server
- @server.stop
- @server = @api = nil
- end
-
- before :each do
- Chef::Config.chef_server_url = "http://localhost:9000"
- start_tiny_server
- end
-
- after :each do
- stop_tiny_server
- end
-
- it "knife raw /blah returns the prettified json" do
- knife("raw /blah").should_succeed <<~EOM
- {
- "x": "y",
- "a": "b"
- }
- EOM
- end
-
- it "knife raw --no-pretty /blah returns the raw json" do
- knife("raw --no-pretty /blah").should_succeed <<~EOM
- { "x": "y", "a": "b" }
- EOM
- end
- end
-
- context "When a server returns text" do
- def start_tiny_server(**server_opts)
- @server = TinyServer::Manager.new(**server_opts)
- @server.start
- @api = TinyServer::API.instance
- @api.clear
-
- @api.get("/blah", 200, nil, { "Content-Type" => "text" }) do
- '{ "x": "y", "a": "b" }'
- end
- end
-
- def stop_tiny_server
- @server.stop
- @server = @api = nil
- end
-
- before :each do
- Chef::Config.chef_server_url = "http://localhost:9000"
- start_tiny_server
- end
-
- after :each do
- stop_tiny_server
- end
-
- it "knife raw /blah returns the raw text" do
- knife("raw /blah").should_succeed(<<~EOM)
- { "x": "y", "a": "b" }
- EOM
- end
-
- it "knife raw --no-pretty /blah returns the raw text" do
- knife("raw --no-pretty /blah").should_succeed(<<~EOM)
- { "x": "y", "a": "b" }
- EOM
- end
- end
- end
-end
diff --git a/spec/integration/knife/redirection_spec.rb b/spec/integration/knife/redirection_spec.rb
deleted file mode 100644
index 34d5fe6efc..0000000000
--- a/spec/integration/knife/redirection_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tiny_server"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/list"
-
-describe "redirection", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- def start_tiny_server(real_chef_server_url, **server_opts)
- @server = TinyServer::Manager.new(**server_opts)
- @server.start
- @api = TinyServer::API.instance
- @api.clear
-
- @api.get("/roles", 302, nil, { "Content-Type" => "text", "Location" => "#{real_chef_server_url}/roles" }) do
- end
- end
-
- def stop_tiny_server
- @server.stop
- @server = @api = nil
- end
-
- include_context "default config options"
-
- when_the_chef_server "has a role" do
- before { role "x", {} }
-
- context "and another server redirects to it with 302" do
- before(:each) do
- real_chef_server_url = Chef::Config.chef_server_url
- Chef::Config.chef_server_url = "http://localhost:9000"
- start_tiny_server(real_chef_server_url)
- end
-
- after(:each) do
- stop_tiny_server
- end
-
- it "knife list /roles returns the role" do
- knife("list /roles").should_succeed "/roles/x.json\n"
- end
- end
- end
-end
diff --git a/spec/integration/knife/role_bulk_delete_spec.rb b/spec/integration/knife/role_bulk_delete_spec.rb
deleted file mode 100644
index 6810cebc91..0000000000
--- a/spec/integration/knife/role_bulk_delete_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role bulk delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some roles" do
- before do
- role "cons", {}
- role "car", {}
- role "cdr", {}
- role "cat", {}
- end
-
- it "deletes all matching roles" do
- knife("role bulk delete ^ca.*", input: "Y").should_succeed <<~EOM
- The following roles will be deleted:
-
- car cat
-
- Are you sure you want to delete these roles? (Y/N) Deleted role car
- Deleted role cat
- EOM
-
- knife("role list").should_succeed <<~EOM
- cdr
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/role_create_spec.rb b/spec/integration/knife/role_create_spec.rb
deleted file mode 100644
index 80ef1d9a9f..0000000000
--- a/spec/integration/knife/role_create_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role create", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- let(:out) { "Created role[bah]\n" }
-
- when_the_chef_server "is empty" do
- it "creates a new role" do
- knife("role create bah").should_succeed out
- end
-
- it "refuses to add an existing role" do
- pending "Knife role create must not blindly overwrite an existing role"
- knife("role create bah").should_succeed out
- expect { knife("role create bah") }.to raise_error(Net::HTTPClientException)
- end
-
- end
-end
diff --git a/spec/integration/knife/role_delete_spec.rb b/spec/integration/knife/role_delete_spec.rb
deleted file mode 100644
index c4c6498c51..0000000000
--- a/spec/integration/knife/role_delete_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role delete", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some roles" do
- before do
- role "cons", {}
- role "car", {}
- role "cdr", {}
- role "cat", {}
- end
-
- it "deletes a role" do
- knife("role delete car", input: "Y").should_succeed <<~EOM
- Do you really want to delete car? (Y/N) Deleted role[car]
- EOM
-
- knife("role list").should_succeed <<~EOM
- cat
- cdr
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/role_from_file_spec.rb b/spec/integration/knife/role_from_file_spec.rb
deleted file mode 100644
index 4a2912935c..0000000000
--- a/spec/integration/knife/role_from_file_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role from file", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- # include_context "default config options"
-
- let(:role_dir) { "#{@repository_dir}/roles" }
-
- when_the_chef_server "is empty" do
- when_the_repository "has some roles" do
- before do
-
- file "roles/cons.json", <<~EOM
- {
- "name": "cons",
- "description": "An role",
- "json_class": "Chef::role",
- "chef_type": "role",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- file "roles/car.json", <<~EOM
- {
- "name": "car",
- "description": "A role for list nodes",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- file "roles/cdr.json", <<~EOM
- {
- "name": "cdr",
- "description": "A role for last nodes",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
- "hola": "Amigos!"
- },
- "override_attributes": {
-
- }
- }
- EOM
-
- end
-
- it "uploads a single file" do
- knife("role from file #{role_dir}/cons.json").should_succeed stderr: <<~EOM
- Updated Role cons
- EOM
- end
-
- it "uploads many files" do
- knife("role from file #{role_dir}/cons.json #{role_dir}/car.json #{role_dir}/cdr.json").should_succeed stderr: <<~EOM
- Updated Role cons
- Updated Role car
- Updated Role cdr
- EOM
- end
-
- end
- end
-end
diff --git a/spec/integration/knife/role_list_spec.rb b/spec/integration/knife/role_list_spec.rb
deleted file mode 100644
index 9e4b983698..0000000000
--- a/spec/integration/knife/role_list_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role list", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some roles" do
- before do
- role "cons", {}
- role "car", {}
- role "cdr", {}
- role "cat", {}
- end
-
- it "lists all cookbooks" do
- knife("role list").should_succeed <<~EOM
- car
- cat
- cdr
- cons
- EOM
- end
-
- end
-end
diff --git a/spec/integration/knife/role_show_spec.rb b/spec/integration/knife/role_show_spec.rb
deleted file mode 100644
index dfa989bf69..0000000000
--- a/spec/integration/knife/role_show_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife role show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has some roles" do
- before do
- role "cons", {}
- role "car", {}
- role "cdr", {}
- role "cat", {}
- end
-
- # rubocop:disable Layout/TrailingWhitespace
- it "shows a cookbook" do
- knife("role show cons").should_succeed <<~EOM
- chef_type: role
- default_attributes:
- description:
- env_run_lists:
- json_class: Chef::Role
- name: cons
- override_attributes:
- run_list:
- EOM
- end
- # rubocop:enable Layout/TrailingWhitespace
-
- end
-end
diff --git a/spec/integration/knife/search_node_spec.rb b/spec/integration/knife/search_node_spec.rb
deleted file mode 100644
index 8eaa30f7fa..0000000000
--- a/spec/integration/knife/search_node_spec.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-
-describe "knife node show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has a node with a run_list" do
- before do
- node "cons", { run_list: ["recipe[bar]", "recipe[foo]"] }
- end
-
- it "finds the node" do
- knife("search node name:cons").should_succeed(/Node Name:\s*cons/, stderr: "1 items found\n\n")
- end
-
- it "does not find a node" do
- knife("search node name:snoc").should_fail("", stderr: "0 items found\n\n", exit_code: 1)
- end
- end
-end
diff --git a/spec/integration/knife/show_spec.rb b/spec/integration/knife/show_spec.rb
deleted file mode 100644
index 4bee492e7b..0000000000
--- a/spec/integration/knife/show_spec.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "support/shared/context/config"
-require "chef/knife/show"
-
-describe "knife show", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- include_context "default config options"
-
- when_the_chef_server "has one of each thing" do
- before do
- client "x", "{}"
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => "{}" }
- environment "x", "{}"
- node "x", "{}"
- role "x", "{}"
- user "x", "{}"
- end
-
- when_the_repository "also has one of each thing" do
- before do
- file "clients/x.json", { "foo" => "bar" }
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", { "foo" => "bar" }
- file "environments/_default.json", { "foo" => "bar" }
- file "environments/x.json", { "foo" => "bar" }
- file "nodes/x.json", { "foo" => "bar" }
- file "roles/x.json", { "foo" => "bar" }
- file "users/x.json", { "foo" => "bar" }
- end
-
- it "knife show /cookbooks/x/metadata.rb shows the remote version" do
- knife("show /cookbooks/x/metadata.rb").should_succeed <<~EOM
- /cookbooks/x/metadata.rb:
- name "x"; version "1.0.0"
- EOM
- end
- it "knife show --local /cookbooks/x/metadata.rb shows the local version" do
- knife("show --local /cookbooks/x/metadata.rb").should_succeed <<~EOM
- /cookbooks/x/metadata.rb:
- name "x"; version "1.0.0"
- EOM
- end
- it "knife show /data_bags/x/y.json shows the remote version" do
- knife("show /data_bags/x/y.json").should_succeed <<~EOM
- /data_bags/x/y.json:
- {
- "id": "y"
- }
- EOM
- end
- it "knife show --local /data_bags/x/y.json shows the local version" do
- knife("show --local /data_bags/x/y.json").should_succeed <<~EOM
- /data_bags/x/y.json:
- {
- "foo": "bar"
- }
- EOM
- end
- it "knife show /environments/x.json shows the remote version" do
- knife("show /environments/x.json").should_succeed <<~EOM
- /environments/x.json:
- {
- "name": "x",
- "description": "",
- "cookbook_versions": {
-
- },
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "json_class": "Chef::Environment",
- "chef_type": "environment"
- }
- EOM
- end
- it "knife show --local /environments/x.json shows the local version" do
- knife("show --local /environments/x.json").should_succeed <<~EOM
- /environments/x.json:
- {
- "foo": "bar"
- }
- EOM
- end
- it "knife show /roles/x.json shows the remote version" do
- knife("show /roles/x.json").should_succeed <<~EOM
- /roles/x.json:
- {
- "name": "x",
- "description": "",
- "json_class": "Chef::Role",
- "chef_type": "role",
- "default_attributes": {
-
- },
- "override_attributes": {
-
- },
- "run_list": [
-
- ],
- "env_run_lists": {
-
- }
- }
- EOM
- end
- it "knife show --local /roles/x.json shows the local version" do
- knife("show --local /roles/x.json").should_succeed <<~EOM
- /roles/x.json:
- {
- "foo": "bar"
- }
- EOM
- end
- # show directory
- it "knife show /data_bags/x fails" do
- knife("show /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n"
- end
- it "knife show --local /data_bags/x fails" do
- knife("show --local /data_bags/x").should_fail "ERROR: /data_bags/x: is a directory\n"
- end
- # show nonexistent file
- it "knife show /environments/nonexistent.json fails" do
- knife("show /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
- end
- it "knife show --local /environments/nonexistent.json fails" do
- knife("show --local /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
- end
- end
- end
-
- when_the_chef_server "has a hash with multiple keys" do
- before do
- environment "x", {
- "default_attributes" => { "foo" => "bar" },
- "cookbook_versions" => { "blah" => "= 1.0.0" },
- "override_attributes" => { "x" => "y" },
- "description" => "woo",
- "name" => "x",
- }
- end
- it "knife show shows the attributes in a predetermined order" do
- knife("show /environments/x.json").should_succeed <<~EOM
- /environments/x.json:
- {
- "name": "x",
- "description": "woo",
- "cookbook_versions": {
- "blah": "= 1.0.0"
- },
- "default_attributes": {
- "foo": "bar"
- },
- "override_attributes": {
- "x": "y"
- },
- "json_class": "Chef::Environment",
- "chef_type": "environment"
- }
- EOM
- end
- end
-
- when_the_repository "has an environment with bad JSON" do
- before { file "environments/x.json", "{" }
- it "knife show succeeds" do
- knife("show --local /environments/x.json").should_succeed <<~EOM
- /environments/x.json:
- {
- EOM
- end
- end
-end
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
deleted file mode 100644
index 37cfcefa32..0000000000
--- a/spec/integration/knife/upload_spec.rb
+++ /dev/null
@@ -1,1617 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "support/shared/integration/integration_helper"
-require "chef/knife/upload"
-require "chef/knife/diff"
-require "chef/knife/raw"
-require "chef/json_compat"
-
-describe "knife upload", :workstation do
- include IntegrationSupport
- include KnifeSupport
-
- context "without versioned cookbooks" do
-
- when_the_chef_server "has one of each thing" do
-
- before do
- client "x", {}
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => {} }
- environment "x", {}
- node "x", {}
- role "x", {}
- user "x", {}
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife upload does nothing" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients/chef-validator.json
- D\t/clients/chef-webui.json
- D\t/clients/x.json
- D\t/cookbooks/x
- D\t/data_bags/x
- D\t/environments/_default.json
- D\t/environments/x.json
- D\t/nodes/x.json
- D\t/roles/x.json
- D\t/users/admin.json
- D\t/users/x.json
- EOM
- end
-
- it "knife upload --purge deletes everything" do
- knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
- Deleted extra entry /clients/chef-validator.json (purge is on)
- Deleted extra entry /clients/chef-webui.json (purge is on)
- Deleted extra entry /clients/x.json (purge is on)
- Deleted extra entry /cookbooks/x (purge is on)
- Deleted extra entry /data_bags/x (purge is on)
- Deleted extra entry /environments/x.json (purge is on)
- Deleted extra entry /nodes/x.json (purge is on)
- Deleted extra entry /roles/x.json (purge is on)
- Deleted extra entry /users/admin.json (purge is on)
- Deleted extra entry /users/x.json (purge is on)
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/environments/_default.json
- EOM
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
-
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife upload makes no changes" do
- knife("upload /cookbooks/x").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife upload --purge makes no changes" do
- knife("upload --purge /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", { "description" => "blarghle" }
- end
-
- it "knife upload changes the role" do
- knife("upload /").should_succeed "Updated /roles/x.json\n"
- knife("diff --name-status /").should_succeed ""
- end
- it "knife upload --no-diff does not change the role" do
- knife("upload --no-diff /").should_succeed ""
- knife("diff --name-status /").should_succeed "M\t/roles/x.json\n"
- end
- end
-
- context "except the role file is textually different, but not ACTUALLY different" do
- before do
- file "roles/x.json", <<~EOM
- {
- "chef_type": "role",
- "default_attributes": {
- },
- "env_run_lists": {
- },
- "json_class": "Chef::Role",
- "name": "x",
- "description": "",
- "override_attributes": {
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife upload / does not change anything" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "the role is in ruby" do
- before do
- file "roles/x.rb", <<~EOM
- name "x"
- description "blargle"
- EOM
- end
-
- it "knife upload changes the role" do
- knife("upload /").should_succeed "Updated /roles/x.json\n"
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife upload --no-diff does not change the role" do
- knife("upload --no-diff /").should_succeed ""
- knife("diff --name-status /").should_succeed "M\t/roles/x.rb\n"
- end
- end
-
- context "when cookbook metadata has a self-dependency" do
- before do
- file "cookbooks/x/metadata.rb", "name 'x'; version '1.0.0'; depends 'x'"
- end
-
- it "fails with RuntimeError" do
- expect { knife("upload /cookbooks") }.to raise_error RuntimeError, /Cookbook depends on itself/
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x/blah.rb", ""
- file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0")
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife upload adds the new files" do
- knife("upload /").should_succeed <<~EOM
- Created /clients/y.json
- Updated /cookbooks/x
- Created /cookbooks/y
- Created /data_bags/x/z.json
- Created /data_bags/y
- Created /data_bags/y/zz.json
- Created /environments/y.json
- Created /nodes/y.json
- Created /roles/y.json
- Created /users/y.json
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- D\t/cookbooks/y/metadata.json
- EOM
- end
-
- it "knife upload --no-diff adds the new files" do
- knife("upload --no-diff /").should_succeed <<~EOM
- Created /clients/y.json
- Updated /cookbooks/x
- Created /cookbooks/y
- Created /data_bags/x/z.json
- Created /data_bags/y
- Created /data_bags/y/zz.json
- Created /environments/y.json
- Created /nodes/y.json
- Created /roles/y.json
- Created /users/y.json
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- D\t/cookbooks/y/metadata.json
- EOM
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife upload does nothing" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
-
- it "knife upload --purge deletes nothing" do
- knife("upload --purge /").should_fail <<~EOM
- ERROR: /clients cannot be deleted.
- ERROR: /cookbooks cannot be deleted.
- ERROR: /data_bags cannot be deleted.
- ERROR: /environments cannot be deleted.
- ERROR: /nodes cannot be deleted.
- ERROR: /roles cannot be deleted.
- ERROR: /users cannot be deleted.
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
-
- context "when current directory is top level" do
- before do
- cwd "."
- end
-
- it "knife upload with no parameters reports an error" do
- knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
- end
- end
- end
- end
-
- when_the_chef_server "is empty" do
- when_the_repository "has a data bag item" do
-
- before do
- file "data_bags/x/y.json", { "foo" => "bar" }
- end
-
- it "knife upload of the data bag uploads only the values in the data bag item and no other" do
- knife("upload /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- EOM
- expect(Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false).keys.sort).to eq(%w{foo id})
- end
-
- it "knife upload /data_bags/x /data_bags/x/y.json uploads x once" do
- knife("upload /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- end
- end
-
- when_the_repository "has a data bag item with keys chef_type and data_bag" do
-
- before do
- file "data_bags/x/y.json", { "chef_type" => "aaa", "data_bag" => "bbb" }
- end
-
- it "upload preserves chef_type and data_bag" do
- knife("upload /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- result = Chef::JSONCompat.parse(knife("raw /data/x/y").stdout, create_additions: false)
- expect(result.keys.sort).to eq(%w{chef_type data_bag id})
- expect(result["chef_type"]).to eq("aaa")
- expect(result["data_bag"]).to eq("bbb")
- end
- end
-
- # Test upload of an item when the other end doesn't even have the container
- when_the_repository "has two data bag items" do
- before do
- file "data_bags/x/y.json", {}
- file "data_bags/x/z.json", {}
- end
- it "knife upload of one data bag item itself succeeds" do
- knife("upload /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- A\t/data_bags/x/z.json
- EOM
- end
- end
- end
-
- when_the_chef_server "has three data bag items" do
-
- before do
- data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} }
- end
-
- when_the_repository "has a modified, unmodified, added and deleted data bag item" do
- before do
- file "data_bags/x/added.json", {}
- file "data_bags/x/modified.json", { "foo" => "bar" }
- file "data_bags/x/unmodified.json", {}
- end
-
- it "knife upload of the modified file succeeds" do
- knife("upload /data_bags/x/modified.json").should_succeed <<~EOM
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the unmodified file does nothing" do
- knife("upload /data_bags/x/unmodified.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the added file succeeds" do
- knife("upload /data_bags/x/added.json").should_succeed <<~EOM
- Created /data_bags/x/added.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- EOM
- end
- it "knife upload of the deleted file does nothing" do
- knife("upload /data_bags/x/deleted.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload --purge of the deleted file deletes it" do
- knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the entire data bag uploads everything" do
- knife("upload /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- EOM
- end
- it "knife upload --purge of the entire data bag uploads everything" do
- knife("upload --purge /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- context "when cwd is the /data_bags directory" do
-
- before do
- cwd "data_bags"
- end
-
- it "knife upload fails" do
- knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
- end
-
- it "knife upload --purge . uploads everything" do
- knife("upload --purge .").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- it "knife upload --purge * uploads everything" do
- knife("upload --purge *").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- end
- end
- end
-
- # Cookbook upload is a funny thing ... direct cookbook upload works, but
- # upload of a file is designed not to work at present. Make sure that is the
- # case.
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "x", "1.0.0", { "z.rb" => "" }
- end
-
- when_the_repository "does not have metadata file" do
- before do
- file "cookbooks/x/y.rb", "hi"
- end
-
- it "raises MetadataNotFound exception" do
- expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotFound)
- end
- end
-
- when_the_repository "does not have valid metadata" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata(nil, "1.0.0")
- end
-
- it "raises exception for invalid metadata" do
- expect { knife("upload /cookbooks/x") }.to raise_error(Chef::Exceptions::MetadataNotValid)
- end
- end
-
- when_the_repository "has a modified, extra and missing file for the cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "#modified")
- file "cookbooks/x/y.rb", "hi"
- end
-
- it "knife upload of any individual file fails" do
- knife("upload /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n"
- knife("upload /cookbooks/x/y.rb").should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n"
- knife("upload --purge /cookbooks/x/z.rb").should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n"
- end
-
- # TODO this is a bit of an inconsistency: if we didn't specify --purge,
- # technically we shouldn't have deleted missing files. But ... cookbooks
- # are a special case.
- it "knife upload of the cookbook itself succeeds" do
- knife("upload /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
-
- it "knife upload --purge of the cookbook itself succeeds" do
- knife("upload /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
- when_the_repository "has a missing file for the cookbook" do
-
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- end
-
- it "knife upload of the cookbook succeeds" do
- knife("upload /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
- when_the_repository "has an extra file for the cookbook" do
-
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/z.rb", ""
- file "cookbooks/x/blah.rb", ""
- end
-
- it "knife upload of the cookbook succeeds" do
- knife("upload /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
-
- when_the_repository "has a different file in the cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- end
-
- it "knife upload --freeze freezes the cookbook" do
- knife("upload --freeze /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- # Modify a file and attempt to upload
- file "cookbooks/x/metadata.rb", 'name "x"; version "1.0.0"#different'
- knife("upload /cookbooks/x").should_fail "ERROR: /cookbooks failed to write: Cookbook x is frozen\n"
- end
- end
- end
-
- when_the_chef_server "has a frozen cookbook" do
- before do
- cookbook "frozencook", "1.0.0", {}, frozen: true
- end
-
- when_the_repository "has an update to said cookbook" do
-
- before do
- file "cookbooks/frozencook/metadata.rb", cb_metadata("frozencook", "1.0.0", "# This is different")
- end
-
- it "knife upload fails to upload the frozen cookbook" do
- knife("upload /cookbooks/frozencook").should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n"
- end
- it "knife upload --force uploads the frozen cookbook" do
- knife("upload --force /cookbooks/frozencook").should_succeed <<~EOM
- Updated /cookbooks/frozencook
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/metadata.json", { name: "x", version: "1.0.0" }
- file "cookbooks/x/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the local version" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/metadata.json
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- knife("upload --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/metadata.json
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the local version and generates metadata.json from metadata.rb and uploads it." do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- knife("upload --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the local version generates metadata.json and uploads it." do
- knife("upload --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the local version and generates metadata.json before upload and uploads it." do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- knife("upload --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x/metadata.rb
- D\t/cookbooks/x/onlyin1.0.1.rb
- A\t/cookbooks/x/onlyin1.0.0.rb
- EOM
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the new version" do
- knife("upload --purge /cookbooks/x").should_succeed <<~EOM
- Updated /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before do
- environment "x", {}
- end
-
- when_the_repository "has an environment with bad JSON" do
- before do
- file "environments/x.json", "{"
- end
-
- it "knife upload tries and fails" do
- error1 = <<~EOH
- WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
- {
- (right here) ------^
-
- ERROR: /environments/x.json failed to write: Parse error reading JSON: parse error: premature EOF
- {
- (right here) ------^
- EOH
-
- warn = <<~EOH
- WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
- {
- (right here) ------^
-
- EOH
- knife("upload /environments/x.json").should_fail(error1)
- knife("diff --name-status /environments/x.json").should_succeed("M\t/environments/x.json\n", stderr: warn)
- end
- end
-
- when_the_repository "has the same environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
- it "knife upload fails" do
- knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
- knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n"
- end
- end
-
- when_the_repository "has the same environment with no name in the file" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
- it "knife upload succeeds" do
- knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "is empty" do
-
- when_the_repository "has an environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
- it "knife upload fails" do
- knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
- knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n"
- end
- end
-
- when_the_repository "has an environment with no name in the file" do
-
- before do
- file "environments/x.json", { "description" => "hi" }
- end
- it "knife upload succeeds" do
- knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
-
- when_the_repository "has a data bag with no id in the file" do
- before do
- file "data_bags/bag/x.json", { "foo" => "bar" }
- end
- it "knife upload succeeds" do
- knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
- knife("diff --name-status /data_bags/bag/x.json").should_succeed ""
- end
- end
- end
- when_the_chef_server "is empty" do
- when_the_repository "has a cookbook with an invalid chef_version constraint in it" do
- before do
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'")
- end
- it "knife upload succeeds" do
- knife("upload /cookbooks/x").should_succeed <<~EOM
- Created /cookbooks/x
- EOM
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x/metadata.json
- EOM
- end
- end
- end
- end # without versioned cookbooks
-
- context "with versioned cookbooks" do
- before { Chef::Config[:versioned_cookbooks] = true }
-
- when_the_chef_server "has one of each thing" do
-
- before do
- client "x", {}
- cookbook "x", "1.0.0"
- data_bag "x", { "y" => {} }
- environment "x", {}
- node "x", {}
- role "x", {}
- user "x", {}
- end
-
- when_the_repository "has only top-level directories" do
- before do
- directory "clients"
- directory "cookbooks"
- directory "data_bags"
- directory "environments"
- directory "nodes"
- directory "roles"
- directory "users"
- end
-
- it "knife upload does nothing" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients/chef-validator.json
- D\t/clients/chef-webui.json
- D\t/clients/x.json
- D\t/cookbooks/x-1.0.0
- D\t/data_bags/x
- D\t/environments/_default.json
- D\t/environments/x.json
- D\t/nodes/x.json
- D\t/roles/x.json
- D\t/users/admin.json
- D\t/users/x.json
- EOM
- end
-
- it "knife upload --purge deletes everything" do
- knife("upload --purge /").should_succeed(<<~EOM, stderr: "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
- Deleted extra entry /clients/chef-validator.json (purge is on)
- Deleted extra entry /clients/chef-webui.json (purge is on)
- Deleted extra entry /clients/x.json (purge is on)
- Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
- Deleted extra entry /data_bags/x (purge is on)
- Deleted extra entry /environments/x.json (purge is on)
- Deleted extra entry /nodes/x.json (purge is on)
- Deleted extra entry /roles/x.json (purge is on)
- Deleted extra entry /users/admin.json (purge is on)
- Deleted extra entry /users/x.json (purge is on)
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/environments/_default.json
- EOM
- end
- end
-
- when_the_repository "has an identical copy of each thing" do
- before do
- file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/_default.json", { "description" => "The default Chef environment" }
- file "environments/x.json", {}
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "roles/x.json", {}
- file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
- file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife upload makes no changes" do
- knife("upload /cookbooks/x-1.0.0").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- it "knife upload --purge makes no changes" do
- knife("upload --purge /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
-
- context "except the role file" do
- before do
- file "roles/x.json", { "description" => "blarghle" }
- end
-
- it "knife upload changes the role" do
- knife("upload /").should_succeed "Updated /roles/x.json\n"
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "except the role file is textually different, but not ACTUALLY different" do
-
- before do
- file "roles/x.json", <<~EOM
- {
- "chef_type": "role",
- "default_attributes": {
- },
- "env_run_lists": {
- },
- "json_class": "Chef::Role",
- "name": "x",
- "description": "",
- "override_attributes": {
- },
- "run_list": [
-
- ]
- }
- EOM
- end
-
- it "knife upload / does not change anything" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed ""
- end
- end
-
- context "as well as one extra copy of each thing" do
- before do
- file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "cookbooks/x-1.0.0/blah.rb", ""
- file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0")
- file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0")
- file "data_bags/x/z.json", {}
- file "data_bags/y/zz.json", {}
- file "environments/y.json", {}
- file "nodes/y.json", {}
- file "roles/y.json", {}
- file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
- end
-
- it "knife upload adds the new files" do
- knife("upload /").should_succeed <<~EOM
- Created /clients/y.json
- Updated /cookbooks/x-1.0.0
- Created /cookbooks/x-2.0.0
- Created /cookbooks/y-1.0.0
- Created /data_bags/x/z.json
- Created /data_bags/y
- Created /data_bags/y/zz.json
- Created /environments/y.json
- Created /nodes/y.json
- Created /roles/y.json
- Created /users/y.json
- EOM
- knife("diff --name-status /").should_succeed ""
- end
- end
- end
-
- when_the_repository "is empty" do
- it "knife upload does nothing" do
- knife("upload /").should_succeed ""
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
-
- it "knife upload --purge deletes nothing" do
- knife("upload --purge /").should_fail <<~EOM
- ERROR: /clients cannot be deleted.
- ERROR: /cookbooks cannot be deleted.
- ERROR: /data_bags cannot be deleted.
- ERROR: /environments cannot be deleted.
- ERROR: /nodes cannot be deleted.
- ERROR: /roles cannot be deleted.
- ERROR: /users cannot be deleted.
- EOM
- knife("diff --name-status /").should_succeed <<~EOM
- D\t/clients
- D\t/cookbooks
- D\t/data_bags
- D\t/environments
- D\t/nodes
- D\t/roles
- D\t/users
- EOM
- end
-
- context "when current directory is top level" do
- before do
- cwd "."
- end
- it "knife upload with no parameters reports an error" do
- knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
- end
- end
- end
- end
-
- # Test upload of an item when the other end doesn't even have the container
- when_the_chef_server "is empty" do
- when_the_repository "has two data bag items" do
- before do
- file "data_bags/x/y.json", {}
- file "data_bags/x/z.json", {}
- end
-
- it "knife upload of one data bag item itself succeeds" do
- knife("upload /data_bags/x/y.json").should_succeed <<~EOM
- Created /data_bags/x
- Created /data_bags/x/y.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- A\t/data_bags/x/z.json
- EOM
- end
- end
- end
-
- when_the_chef_server "has three data bag items" do
- before do
- data_bag "x", { "deleted" => {}, "modified" => {}, "unmodified" => {} }
- end
- when_the_repository "has a modified, unmodified, added and deleted data bag item" do
- before do
- file "data_bags/x/added.json", {}
- file "data_bags/x/modified.json", { "foo" => "bar" }
- file "data_bags/x/unmodified.json", {}
- end
-
- it "knife upload of the modified file succeeds" do
- knife("upload /data_bags/x/modified.json").should_succeed <<~EOM
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the unmodified file does nothing" do
- knife("upload /data_bags/x/unmodified.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the added file succeeds" do
- knife("upload /data_bags/x/added.json").should_succeed <<~EOM
- Created /data_bags/x/added.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- EOM
- end
- it "knife upload of the deleted file does nothing" do
- knife("upload /data_bags/x/deleted.json").should_succeed ""
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload --purge of the deleted file deletes it" do
- knife("upload --purge /data_bags/x/deleted.json").should_succeed <<~EOM
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- M\t/data_bags/x/modified.json
- A\t/data_bags/x/added.json
- EOM
- end
- it "knife upload of the entire data bag uploads everything" do
- knife("upload /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- EOM
- knife("diff --name-status /data_bags").should_succeed <<~EOM
- D\t/data_bags/x/deleted.json
- EOM
- end
- it "knife upload --purge of the entire data bag uploads everything" do
- knife("upload --purge /data_bags/x").should_succeed <<~EOM
- Created /data_bags/x/added.json
- Updated /data_bags/x/modified.json
- Deleted extra entry /data_bags/x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- context "when cwd is the /data_bags directory" do
- before do
- cwd "data_bags"
- end
- it "knife upload fails" do
- knife("upload").should_fail "FATAL: You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"\n", stdout: /USAGE/
- end
- it "knife upload --purge . uploads everything" do
- knife("upload --purge .").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- it "knife upload --purge * uploads everything" do
- knife("upload --purge *").should_succeed <<~EOM
- Created x/added.json
- Updated x/modified.json
- Deleted extra entry x/deleted.json (purge is on)
- EOM
- knife("diff --name-status /data_bags").should_succeed ""
- end
- end
- end
- end
-
- # Cookbook upload is a funny thing ... direct cookbook upload works, but
- # upload of a file is designed not to work at present. Make sure that is the
- # case.
- when_the_chef_server "has a cookbook" do
- before do
- cookbook "x", "1.0.0", { "z.rb" => "" }
- end
-
- when_the_repository "has a modified, extra and missing file for the cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "#modified")
- file "cookbooks/x-1.0.0/y.rb", "hi"
- end
-
- it "knife upload of any individual file fails" do
- knife("upload /cookbooks/x-1.0.0/metadata.rb").should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n"
- knife("upload /cookbooks/x-1.0.0/y.rb").should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n"
- knife("upload --purge /cookbooks/x-1.0.0/z.rb").should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n"
- end
-
- # TODO this is a bit of an inconsistency: if we didn't specify --purge,
- # technically we shouldn't have deleted missing files. But ... cookbooks
- # are a special case.
- it "knife upload of the cookbook itself succeeds" do
- knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
-
- it "knife upload --purge of the cookbook itself succeeds" do
- knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_repository "has a missing file for the cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- end
-
- it "knife upload of the cookbook succeeds" do
- knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_repository "has an extra file for the cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x-1.0.0/z.rb", ""
- file "cookbooks/x-1.0.0/blah.rb", ""
- end
-
- it "knife upload of the cookbook succeeds" do
- knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_repository "has a cookbook" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text"
- end
-
- when_the_chef_server "has a later version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife upload /cookbooks uploads the local version" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- M\t/cookbooks/x-1.0.0/onlyin1.0.0.rb
- D\t/cookbooks/x-1.0.1
- EOM
- knife("upload --purge /cookbooks").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook" do
- before do
- cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
- it "knife upload /cookbooks uploads the local version" do
- knife("upload --purge /cookbooks").should_succeed <<~EOM
- Updated /cookbooks/x-1.0.0
- Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has a later version for the cookbook, and no current version" do
- before do
- cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the local version" do
- knife("diff --name-status /cookbooks").should_succeed <<~EOM
- D\t/cookbooks/x-1.0.1
- A\t/cookbooks/x-1.0.0
- EOM
- knife("upload --purge /cookbooks").should_succeed <<~EOM
- Created /cookbooks/x-1.0.0
- Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
-
- when_the_chef_server "has an earlier version for the cookbook, and no current version" do
- before do
- cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
- end
-
- it "knife upload /cookbooks/x uploads the new version" do
- knife("upload --purge /cookbooks").should_succeed <<~EOM
- Created /cookbooks/x-1.0.0
- Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "has an environment" do
- before do
- environment "x", {}
- end
-
- when_the_repository "has the same environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
- it "knife upload fails" do
- knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
- knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n"
- end
- end
-
- when_the_repository "has the same environment with no name in the file" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
- it "knife upload succeeds" do
- knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "is empty" do
-
- when_the_repository "has an environment with the wrong name in the file" do
- before do
- file "environments/x.json", { "name" => "y" }
- end
- it "knife upload fails" do
- knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
- knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n"
- end
- end
-
- when_the_repository "has an environment with no name in the file" do
- before do
- file "environments/x.json", { "description" => "hi" }
- end
- it "knife upload succeeds" do
- knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n"
- knife("diff --name-status /environments/x.json").should_succeed ""
- end
- end
-
- when_the_repository "has a data bag with no id in the file" do
- before do
- file "data_bags/bag/x.json", { "foo" => "bar" }
- end
- it "knife upload succeeds" do
- knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
- knife("diff --name-status /data_bags/bag/x.json").should_succeed ""
- end
- end
- end
-
- when_the_chef_server "is empty" do
- when_the_repository "has a cookbook with an invalid chef_version constraint in it" do
- before do
- file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'")
- end
- it "knife upload succeeds" do
- knife("upload /cookbooks/x-1.0.0").should_succeed <<~EOM
- Created /cookbooks/x-1.0.0
- EOM
- knife("diff --name-status /cookbooks").should_succeed ""
- end
- end
- end
- end # with versioned cookbooks
-
- when_the_chef_server "has a user" do
- before do
- user "x", {}
- end
-
- when_the_repository "has the same user with json_class in it" do
- before do
- file "users/x.json", { "admin" => true, "json_class" => "Chef::WebUIUser" }
- end
- it "knife upload /users/x.json succeeds" do
- knife("upload /users/x.json").should_succeed "Updated /users/x.json\n"
- end
- end
- end
-
- when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
- before do
- user "foo", {}
- user "bar", {}
- user "foobar", {}
- organization "foo", { "full_name" => "Something" }
- end
-
- before :each do
- Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo")
- end
-
- context "and has nothing but a single group named blah" do
- group "blah", {}
-
- when_the_repository "has at least one of each thing" do
-
- before do
- # TODO We have to upload acls for an existing group due to a lack of
- # dependency detection during upload. Fix that!
- file "acls/groups/blah.json", {}
- file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
- file "containers/x.json", {}
- file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
- file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0")
- file "data_bags/x/y.json", {}
- file "environments/x.json", {}
- file "groups/x.json", {}
- file "invitations.json", [ "foo" ]
- file "members.json", [ "bar" ]
- file "org.json", { "full_name" => "wootles" }
- file "nodes/x.json", { "normal" => { "tags" => [] } }
- file "policies/x-1.0.0.json", {}
- file "policies/blah-1.0.0.json", {}
- file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } }
- file "roles/x.json", {}
- end
-
- it "knife upload / uploads everything" do
- knife("upload /").should_succeed <<~EOM
- Updated /acls/groups/blah.json
- Created /clients/x.json
- Created /containers/x.json
- Created /cookbook_artifacts/x-1x1
- Created /cookbooks/x
- Created /data_bags/x
- Created /data_bags/x/y.json
- Created /environments/x.json
- Created /groups/x.json
- Updated /invitations.json
- Updated /members.json
- Created /nodes/x.json
- Updated /org.json
- Created /policies/blah-1.0.0.json
- Created /policies/x-1.0.0.json
- Created /policy_groups/x.json
- Created /roles/x.json
- EOM
- expect(api.get("association_requests").map { |a| a["username"] }).to eq([ "foo" ])
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
- knife("diff --name-status --diff-filter=AMT /").should_succeed ""
- end
-
- context "When the chef server has an identical copy of each thing" do
- before do
- file "invitations.json", [ "foo" ]
- file "members.json", [ "bar" ]
- file "org.json", { "full_name" => "Something" }
-
- # acl_for %w(organizations foo groups blah)
- client "x", {}
- cookbook "x", "1.0.0"
- cookbook_artifact "x", "1x1", "metadata.rb" => cb_metadata("x", "1.0.0")
- container "x", {}
- data_bag "x", { "y" => {} }
- environment "x", {}
- group "x", {}
- org_invite "foo"
- org_member "bar"
- node "x", {}
- policy "x", "1.0.0", {}
- policy "blah", "1.0.0", {}
- policy_group "x", {
- "policies" => {
- "x" => { "revision_id" => "1.0.0" },
- "blah" => { "revision_id" => "1.0.0" },
- },
- }
- role "x", {}
- end
-
- it "knife upload makes no changes" do
- knife("upload /").should_succeed <<~EOM
- Updated /acls/groups/blah.json
- EOM
- end
- end
-
- context "When the chef server has a slightly different copy of the policy revision" do
- before do
- policy "x", "1.0.0", { "run_list" => [ "blah" ] }
- end
-
- it "should fail because policies are not updateable" do
- knife("upload /policies/x-1.0.0.json").should_fail <<~EOM
- ERROR: /policies/x-1.0.0.json cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes.
- EOM
- end
- end
-
- context "When the chef server has a slightly different copy of the cookbook artifact" do
- before do
- cookbook_artifact "x", "1x1", { "recipes" => { "default.rb" => "" } }
- end
-
- it "should fail because cookbook_artifacts cannot be updated" do
- knife("upload /cookbook_artifacts/x-1x1").should_fail <<~EOM
- ERROR: /cookbook_artifacts/x-1x1 cannot be updated: cookbook artifacts are immutable once uploaded.
- EOM
- end
- end
-
- context "When the chef server has a slightly different copy of each thing (except policy revisions)" do
- before do
- # acl_for %w(organizations foo groups blah)
- client "x", { "validator" => true }
- container "x", {}
- cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "" } }
- cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") }
- data_bag "x", { "y" => { "a" => "b" } }
- environment "x", { "description" => "foo" }
- group "x", { "groups" => [ "admin" ] }
- node "x", { "run_list" => [ "blah" ] }
- policy "x", "1.0.0", {}
- policy "x", "1.0.1", {}
- policy "y", "1.0.0", {}
- policy_group "x", {
- "policies" => {
- "x" => { "revision_id" => "1.0.1" },
- "y" => { "revision_id" => "1.0.0" },
- },
- }
- role "x", { "run_list" => [ "blah" ] }
- end
-
- it "knife upload updates everything" do
- knife("upload /").should_succeed <<~EOM
- Updated /acls/groups/blah.json
- Updated /clients/x.json
- Updated /cookbooks/x
- Updated /data_bags/x/y.json
- Updated /environments/x.json
- Updated /groups/x.json
- Updated /invitations.json
- Updated /members.json
- Updated /nodes/x.json
- Updated /org.json
- Created /policies/blah-1.0.0.json
- Updated /policy_groups/x.json
- Updated /roles/x.json
- EOM
- knife("diff --name-status --diff-filter=AMT /").should_succeed ""
- end
- end
- end
-
- when_the_repository "has an org.json that does not change full_name" do
- before do
- file "org.json", { "full_name" => "Something" }
- end
-
- it "knife upload / emits a warning for bar and adds foo and foobar" do
- knife("upload /").should_succeed ""
- expect(api.get("/")["full_name"]).to eq("Something")
- end
- end
-
- when_the_repository "has an org.json that changes full_name" do
- before do
- file "org.json", { "full_name" => "Something Else" }
- end
-
- it "knife upload / emits a warning for bar and adds foo and foobar" do
- knife("upload /").should_succeed "Updated /org.json\n"
- expect(api.get("/")["full_name"]).to eq("Something Else")
- end
- end
-
- context "and has invited foo and bar is already a member" do
- org_invite "foo"
- org_member "bar"
-
- when_the_repository "wants to invite foo, bar and foobar" do
- before do
- file "invitations.json", %w{foo bar foobar}
- end
-
- it "knife upload / emits a warning for bar and invites foobar" do
- knife("upload /").should_succeed "Updated /invitations.json\n", stderr: "WARN: Could not invite bar to organization foo: User bar is already in organization foo\n"
- expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{foo foobar})
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
- end
- end
-
- when_the_repository "wants to make foo, bar and foobar members" do
- before do
- file "members.json", %w{foo bar foobar}
- end
-
- it "knife upload / emits a warning for bar and adds foo and foobar" do
- knife("upload /").should_succeed "Updated /members.json\n"
- expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ])
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo foobar})
- end
- end
-
- when_the_repository "wants to invite foo and have bar as a member" do
- before do
- file "invitations.json", [ "foo" ]
- file "members.json", [ "bar" ]
- end
-
- it "knife upload / does nothing" do
- knife("upload /").should_succeed ""
- expect(api.get("association_requests").map { |a| a["username"] }).to eq([ "foo" ])
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ])
- end
- end
- end
-
- context "and has invited bar and foo" do
- org_invite "bar", "foo"
-
- when_the_repository "wants to invite foo and bar (different order)" do
- before do
- file "invitations.json", %w{foo bar}
- end
-
- it "knife upload / does nothing" do
- knife("upload /").should_succeed ""
- expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{bar foo})
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ ])
- end
- end
- end
-
- context "and has already added bar and foo as members of the org" do
- org_member "bar", "foo"
-
- when_the_repository "wants to add foo and bar (different order)" do
- before do
- file "members.json", %w{foo bar}
- end
-
- it "knife upload / does nothing" do
- knife("upload /").should_succeed ""
- expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ])
- expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo})
- end
- end
- end
- end
- end
-end
diff --git a/spec/integration/ohai/ohai_spec.rb b/spec/integration/ohai/ohai_spec.rb
index af4dd5fe38..29ffc7beab 100644
--- a/spec/integration/ohai/ohai_spec.rb
+++ b/spec/integration/ohai/ohai_spec.rb
@@ -51,11 +51,12 @@ describe "ohai" do
# test succeeds and the other one fails, then it can be some kind of shelling-out
# issue or poor performance due to I/O on starting up ruby to run ohai, etc.
#
- it "the hostname plugin must return in under 2 seconds when called from pure ruby" do
- delta = Benchmark.realtime do
- Ohai::System.new.all_plugins(["hostname"])
- end
- expect(delta).to be < 2
- end
+ # @todo: This is disbled 4.26.2021 so we can ship 17.0
+ # it "the hostname plugin must return in under 2 seconds when called from pure ruby" do
+ # delta = Benchmark.realtime do
+ # Ohai::System.new.all_plugins(["hostname"])
+ # end
+ # expect(delta).to be < 2
+ # end
end
end
diff --git a/spec/integration/recipes/accumulator_spec.rb b/spec/integration/recipes/accumulator_spec.rb
index 329137440c..4edd8d6394 100644
--- a/spec/integration/recipes/accumulator_spec.rb
+++ b/spec/integration/recipes/accumulator_spec.rb
@@ -6,7 +6,7 @@ describe "Accumulators" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -31,6 +31,8 @@ describe "Accumulators" do
before do
directory "cookbooks/x" do
file "resources/email_alias.rb", <<-EOM
+ unified_mode true
+
provides :email_alias
resource_name :email_alias
@@ -54,6 +56,8 @@ describe "Accumulators" do
EOM
file "resources/nested.rb", <<-EOM
+ unified_mode true
+
provides :nested
resource_name :nested
@@ -70,6 +74,8 @@ describe "Accumulators" do
EOM
file "resources/doubly_nested.rb", <<-EOM
+ unified_mode true
+
provides :doubly_nested
resource_name :doubly_nested
@@ -133,6 +139,8 @@ describe "Accumulators" do
before do
directory "cookbooks/x" do
file "resources/email_alias.rb", <<-EOM
+ unified_mode true
+
provides :email_alias
resource_name :email_alias
@@ -156,6 +164,8 @@ describe "Accumulators" do
EOM
file "resources/nested.rb", <<-EOM
+ unified_mode true
+
provides :nested
resource_name :nested
@@ -172,6 +182,8 @@ describe "Accumulators" do
EOM
file "resources/doubly_nested.rb", <<-EOM
+ unified_mode true
+
provides :doubly_nested
resource_name :doubly_nested
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb
index 48f7952af0..5cfbe6ea5d 100644
--- a/spec/integration/recipes/lwrp_inline_resources_spec.rb
+++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb
@@ -6,7 +6,7 @@ describe "LWRPs with inline resources" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -118,6 +118,8 @@ describe "LWRPs with inline resources" do
directory "cookbooks/x" do
file "resources/do_nothing.rb", <<-EOM
+ unified_mode true
+
actions :create, :nothing
default_action :create
EOM
@@ -127,6 +129,8 @@ describe "LWRPs with inline resources" do
EOM
file "resources/my_machine.rb", <<-EOM
+ unified_mode true
+
actions :create, :nothing
default_action :create
EOM
diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb
index b7a0589675..e9299b9018 100644
--- a/spec/integration/recipes/lwrp_spec.rb
+++ b/spec/integration/recipes/lwrp_spec.rb
@@ -6,7 +6,7 @@ describe "LWRPs" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -24,6 +24,8 @@ describe "LWRPs" do
directory "cookbooks/l-w-r-p" do
file "resources/foo.rb", <<~EOM
+ unified_mode true
+
default_action :create
EOM
file "providers/foo.rb", <<~EOM
diff --git a/spec/integration/recipes/notifies_spec.rb b/spec/integration/recipes/notifies_spec.rb
index 7dfa70dfe5..c44e014028 100644
--- a/spec/integration/recipes/notifies_spec.rb
+++ b/spec/integration/recipes/notifies_spec.rb
@@ -22,7 +22,7 @@ describe "notifications" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
let(:chef_client) { "bundle exec chef-client --minimal-ohai" }
when_the_repository "notifies a nameless resource" do
@@ -63,6 +63,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -105,6 +107,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -152,6 +156,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -237,6 +243,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -278,6 +286,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -319,6 +329,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/notifying_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :notifying_test
resource_name :notifying_test
@@ -357,6 +369,8 @@ describe "notifications" do
directory "cookbooks/x" do
file "resources/cloning_test.rb", <<~EOM
+ unified_mode true
+
default_action :run
provides :cloning_test
resource_name :cloning_test
diff --git a/spec/integration/recipes/notifying_block_spec.rb b/spec/integration/recipes/notifying_block_spec.rb
index 7a2c5631c1..15b56c8ce2 100644
--- a/spec/integration/recipes/notifying_block_spec.rb
+++ b/spec/integration/recipes/notifying_block_spec.rb
@@ -23,7 +23,7 @@ describe "notifying_block" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
let(:chef_client) { "bundle exec chef-client --minimal-ohai" }
when_the_repository "notifying_block test one" do
@@ -68,6 +68,7 @@ describe "notifying_block" do
before do
directory "cookbooks/x" do
file "resources/nb_test.rb", <<-EOM
+ unified_mode true
default_action :run
provides :nb_test
resource_name :nb_test
diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb
index e714465a17..c692915afd 100644
--- a/spec/integration/recipes/recipe_dsl_spec.rb
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -204,7 +204,7 @@ describe "Recipe DSL methods" do
another_no_name_thingy3("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy3)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy3)
end
end
@@ -234,7 +234,7 @@ describe "Recipe DSL methods" do
another_no_name_thingy4("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy4)
end
it "and platform_family = foo, another_no_name_thingy4 works" do
@@ -244,7 +244,7 @@ describe "Recipe DSL methods" do
another_no_name_thingy4("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy4)
end
end
@@ -273,7 +273,7 @@ describe "Recipe DSL methods" do
another_no_name_thingy5("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy5)
end
it "the new resource name can be used in a recipe" do
@@ -281,7 +281,7 @@ describe "Recipe DSL methods" do
another_thingy_name_for_another_no_name_thingy5("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy5)
end
end
@@ -310,7 +310,7 @@ describe "Recipe DSL methods" do
another_no_name_thingy6("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy6)
end
it "the new resource name can be used in a recipe" do
@@ -318,7 +318,7 @@ describe "Recipe DSL methods" do
another_thingy_name_for_another_no_name_thingy6("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy6)
end
end
@@ -339,7 +339,7 @@ describe "Recipe DSL methods" do
another_thingy_name_for_another_no_name_thingy7("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy7)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy7)
end
it "and os = blarghle, another_thingy_name_for_another_no_name_thingy7 works" do
@@ -349,7 +349,7 @@ describe "Recipe DSL methods" do
another_thingy_name_for_another_no_name_thingy7("blah") {}
end
expect(recipe.logged_warnings).to eq ""
- expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy7)
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy7)
end
it "the old resource name does not work" do
diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb
index 9bfe86c74e..febac64283 100644
--- a/spec/integration/recipes/resource_action_spec.rb
+++ b/spec/integration/recipes/resource_action_spec.rb
@@ -223,6 +223,10 @@ module ResourceActionSpec
ActionJackson.succeeded = ActionJackson.ruby_block_converged
end
+ action :test1, description: "Original description" do
+ true
+ end
+
def foo_public
"foo_public!"
end
@@ -293,7 +297,12 @@ module ResourceActionSpec
ActionJackalope.jackalope_ran = :access_attribute
ActionJackalope.succeeded = ActionJackson.succeeded
end
+
+ action :test1, description: "An old action with a new description" do
+ super
+ end
end
+
before do
ActionJackalope.jackalope_ran = nil
ActionJackalope.load_current_resource_ran = nil
@@ -344,6 +353,11 @@ module ResourceActionSpec
expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
end
+ it "allows overridden action to have a description separate from the action defined in the base resource" do
+ expect(ActionJackson.action_description(:test1)).to eql "Original description"
+ expect(ActionJackalope.action_description(:test1)).to eql "An old action with a new description"
+ end
+
it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
converge do
action_jackalope "hi" do
diff --git a/spec/integration/recipes/unified_mode_spec.rb b/spec/integration/recipes/unified_mode_spec.rb
index b2c3620fdb..d79d534490 100644
--- a/spec/integration/recipes/unified_mode_spec.rb
+++ b/spec/integration/recipes/unified_mode_spec.rb
@@ -6,7 +6,7 @@ describe "Unified Mode" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
let(:chef_client) { "bundle exec chef-client --minimal-ohai" }
@@ -874,4 +874,74 @@ describe "Unified Mode" do
result.error!
end
end
+
+ when_the_repository "has a resource that uses edit_resource to create a subresource" do
+ before do
+ directory "cookbooks/x" do
+ file "recipes/default.rb", <<~EOM
+ my_resource "doit"
+ EOM
+
+ file "resources/my_resource.rb", <<~EOM
+ unified_mode true
+ provides :my_resource
+
+ action :doit do
+ edit_resource(:log, "name") do
+ message "GOOD"
+ level :warn
+ end
+ end
+ EOM
+ end
+ end
+
+ it "recipes should still have a compile/converge mode" do
+ file "config/client.rb", <<~EOM
+ local_mode true
+ cookbook_path "#{path_to("cookbooks")}"
+ log_level :warn
+ EOM
+
+ result = shell_out("#{chef_client} -c \"#{path_to("config/client.rb")}\" --no-color -F doc -o 'x::default'", cwd: chef_dir)
+ # in recipe mode we should still run normally with a compile/converge mode
+ expect(result.stdout).to include("GOOD")
+ result.error!
+ end
+ end
+
+ when_the_repository "has a resource that uses find_resource to create a subresource" do
+ before do
+ directory "cookbooks/x" do
+ file "recipes/default.rb", <<~EOM
+ my_resource "doit"
+ EOM
+
+ file "resources/my_resource.rb", <<~EOM
+ unified_mode true
+ provides :my_resource
+
+ action :doit do
+ find_resource(:log, "name") do
+ message "GOOD"
+ level :warn
+ end
+ end
+ EOM
+ end
+ end
+
+ it "recipes should still have a compile/converge mode" do
+ file "config/client.rb", <<~EOM
+ local_mode true
+ cookbook_path "#{path_to("cookbooks")}"
+ log_level :warn
+ EOM
+
+ result = shell_out("#{chef_client} -c \"#{path_to("config/client.rb")}\" --no-color -F doc -o 'x::default'", cwd: chef_dir)
+ # in recipe mode we should still run normally with a compile/converge mode
+ expect(result.stdout).to include("GOOD")
+ result.error!
+ end
+ end
end
diff --git a/spec/integration/recipes/use_partial_spec.rb b/spec/integration/recipes/use_partial_spec.rb
index c9f1e8e509..f1d9a8eddc 100644
--- a/spec/integration/recipes/use_partial_spec.rb
+++ b/spec/integration/recipes/use_partial_spec.rb
@@ -22,7 +22,7 @@ describe "notifying_block" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.expand_path("../../../bin", __dir__) }
+ let(:chef_dir) { File.expand_path("../../..", __dir__) }
let(:chef_client) { "bundle exec chef-client --minimal-ohai" }
when_the_repository "has a cookbook with partial resources" do
@@ -37,6 +37,7 @@ describe "notifying_block" do
end
EOM
file "resources/thing.rb", <<-EOM
+ unified_mode true
provides :thing
use "shared_properties"
action_class do
@@ -80,6 +81,8 @@ describe "notifying_block" do
EOM
# this tests relative pathing, including the underscore and including the trailing .rb all work
file "resources/thing.rb", <<-EOM
+ unified_mode true
+
provides :thing
use "../partials/_shared_properties.rb"
action_class do
diff --git a/spec/scripts/ssl-serve.rb b/spec/scripts/ssl-serve.rb
deleted file mode 100644
index b03fe4af42..0000000000
--- a/spec/scripts/ssl-serve.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# ssl-serve.rb
-# USAGE: ruby ssl-serve.rb
-#
-# ssl-serve is a script that serves a local directory over SSL.
-# You can use it to test various HTTP behaviors in chef, like chef-client's
-# `-j` and `-c` options and remote_file with https connections.
-#
-require "pp"
-require "openssl"
-require "webrick"
-require "webrick/https"
-
-$ssl = true
-
-CHEF_SPEC_DATA = File.expand_path("../data", __dir__)
-cert_text = File.read(File.expand_path("ssl/chef-rspec.cert", CHEF_SPEC_DATA))
-cert = OpenSSL::X509::Certificate.new(cert_text)
-key_text = File.read(File.expand_path("ssl/chef-rspec.key", CHEF_SPEC_DATA))
-key = OpenSSL::PKey::RSA.new(key_text)
-
-server_opts = {}
-if $ssl
- server_opts.merge!( { SSLEnable: true,
- SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
- SSLCertificate: cert,
- SSLPrivateKey: key })
-end
-
-# 5 == debug, 3 == warning
-LOGGER = WEBrick::Log.new(STDOUT, 5)
-DEFAULT_OPTIONS = {
- server: "webrick",
- Port: 9000,
- Host: "localhost",
- environment: :none,
- Logger: LOGGER,
- DocumentRoot: File.expand_path("#{Dir.tmpdir}/chef-118-sampledata"),
- #:AccessLog => [] # Remove this option to enable the access log when debugging.
-}.freeze
-
-webrick_opts = DEFAULT_OPTIONS.merge(server_opts)
-pp webrick_opts: webrick_opts
-
-server = WEBrick::HTTPServer.new(webrick_opts)
-trap("INT") { server.shutdown }
-
-server.start
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 17ce1ab5b7..9aafbfa994 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -24,6 +24,9 @@ module Shell
IRB = nil unless defined? IRB
end
+# show the deprecation warnings
+Warning[:deprecated] = true
+
$LOAD_PATH.unshift File.expand_path("..", __dir__)
$LOAD_PATH.unshift File.expand_path("../chef-config/lib", __dir__)
@@ -35,12 +38,6 @@ require "rexml/document"
require "webmock/rspec"
require "chef"
-require "chef/knife"
-
-Dir["lib/chef/knife/**/*.rb"]
- .map { |f| f.gsub("lib/", "") }
- .map { |f| f.gsub(/\.rb$/, "") }
- .each { |f| require f }
require "chef/resource_resolver"
require "chef/provider_resolver"
@@ -82,12 +79,13 @@ require "spec/support/recipe_dsl_helper"
Dir["spec/support/**/*.rb"]
.reject { |f| f =~ %r{^spec/support/platforms} }
.reject { |f| f =~ %r{^spec/support/pedant} }
+ .reject { |f| f =~ %r{^spec/support/shared/integration/knife_support} }
.map { |f| f.gsub(/.rb$/, "") }
.map { |f| f.gsub(%r{spec/}, "") }
.each { |f| require f }
OHAI_SYSTEM = Ohai::System.new
-OHAI_SYSTEM.all_plugins(["platform", "hostname", "languages/powershell"])
+OHAI_SYSTEM.all_plugins(["platform", "hostname", "languages/powershell", "uptime"])
test_node = Chef::Node.new
test_node.automatic["os"] = (OHAI_SYSTEM["os"] || "unknown_os").dup.freeze
@@ -143,8 +141,7 @@ RSpec.configure do |config|
config.filter_run_excluding not_supported_on_windows: true if windows?
config.filter_run_excluding not_supported_on_macos: true if macos?
config.filter_run_excluding macos_only: true unless macos?
- config.filter_run_excluding macos_1013: true unless macos_1013?
- config.filter_run_excluding macos_gte_1014: true unless macos_gte_1014?
+ config.filter_run_excluding not_macos_gte_11: true if macos_gte_11?
config.filter_run_excluding not_supported_on_aix: true if aix?
config.filter_run_excluding not_supported_on_solaris: true if solaris?
config.filter_run_excluding not_supported_on_gce: true if gce?
@@ -159,12 +156,8 @@ RSpec.configure do |config|
config.filter_run_excluding windows_powershell_no_dsc_only: true if windows_powershell_dsc?
config.filter_run_excluding windows_domain_joined_only: true unless windows_domain_joined?
config.filter_run_excluding windows_not_domain_joined_only: true if windows_domain_joined?
- # We think this line was causing rspec tests to not run on the Jenkins windows
- # testers. If we ever fix it we should restore it.
- # config.filter_run_excluding :windows_service_requires_assign_token => true if !STDOUT.isatty && !windows_user_right?("SeAssignPrimaryTokenPrivilege")
- config.filter_run_excluding windows_service_requires_assign_token: true
+ config.filter_run_excluding windows_service_requires_assign_token: true if !STDOUT.isatty && !windows_user_right?("SeAssignPrimaryTokenPrivilege")
config.filter_run_excluding solaris_only: true unless solaris?
- config.filter_run_excluding system_windows_service_gem_only: true unless system_windows_service_gem?
config.filter_run_excluding unix_only: true unless unix?
config.filter_run_excluding linux_only: true unless linux?
config.filter_run_excluding aix_only: true unless aix?
@@ -238,6 +231,14 @@ RSpec.configure do |config|
Chef.reset!
+ # Hack warning:
+ #
+ # Something across gem_installer_spec and mixlib_cli specs are polluting gem state so that the 'unmockening' test in rubygems_spec fails.
+ # This works around that until we can understand root cause.
+ #
+ # To explore the minimal test case around that and see more detailed notes, see branch `mp/broken-gems`
+ Gem.clear_paths
+
Chef::ChefFS::FileSystemCache.instance.reset!
Chef::Config.reset
diff --git a/spec/support/chef_helpers.rb b/spec/support/chef_helpers.rb
index 6a0ca878cd..f11dee47b6 100644
--- a/spec/support/chef_helpers.rb
+++ b/spec/support/chef_helpers.rb
@@ -40,26 +40,10 @@ def make_tmpname(prefix_suffix, n = nil)
path << suffix
end
-# This is a helper to determine if the ruby in the PATH contains
-# win32/service gem. windows_service_manager tests create a windows
-# service that starts with the system ruby and requires this gem.
-def system_windows_service_gem?
- windows_service_gem_check_command = %{ruby -r "win32/daemon" -e ":noop" > #{File::NULL} 2>&1}
- if defined?(Bundler)
- Bundler.with_unbundled_env do
- # This returns true if the gem can be loaded
- system(windows_service_gem_check_command)
- end
- else
- # This returns true if the gem can be loaded
- system(windows_service_gem_check_command)
- end
-end
-
# This is a helper to canonicalize paths that we're using in the file
# tests.
def canonicalize_path(path)
- windows? ? path.tr("/", '\\') : path
+ windows? ? path.tr("/", "\\") : path
end
# Makes a temp directory with a canonical path on any platform.
diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/with_state.rb
index 98e4033e01..1977fb3f6a 100644
--- a/spec/support/lib/chef/resource/with_state.rb
+++ b/spec/support/lib/chef/resource/with_state.rb
@@ -16,7 +16,6 @@
# limitations under the License.
#
-require "chef/knife"
require "chef/json_compat"
class Chef
diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb
index 44de913f8b..c7d01dcebb 100644
--- a/spec/support/lib/chef/resource/zen_follower.rb
+++ b/spec/support/lib/chef/resource/zen_follower.rb
@@ -15,7 +15,6 @@
# limitations under the License.
#
-require "chef/knife"
require "chef/json_compat"
class Chef
diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb
index ba2f950bed..0e0853d0b3 100644
--- a/spec/support/lib/chef/resource/zen_master.rb
+++ b/spec/support/lib/chef/resource/zen_master.rb
@@ -16,7 +16,6 @@
# limitations under the License.
#
-require "chef/knife"
require "chef/json_compat"
class Chef
diff --git a/spec/support/matchers/leak.rb b/spec/support/matchers/leak.rb
index bd59a2755c..89d238c88e 100644
--- a/spec/support/matchers/leak.rb
+++ b/spec/support/matchers/leak.rb
@@ -59,15 +59,13 @@ module Matchers
end
def profiler
- @profiler ||= begin
- if ChefUtils.windows?
- require File.join(__dir__, "..", "platforms", "prof", "win32")
- RSpec::Prof::Win32::Profiler.new
- else
- require File.join(__dir__, "..", "prof", "gc")
- RSpec::Prof::GC::Profiler.new
- end
- end
+ @profiler ||= if ChefUtils.windows?
+ require File.join(__dir__, "..", "platforms", "prof", "win32")
+ RSpec::Prof::Win32::Profiler.new
+ else
+ require File.join(__dir__, "..", "prof", "gc")
+ RSpec::Prof::GC::Profiler.new
+ end
end
end
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index b29c860f30..a3fb95e069 100644
--- a/spec/support/platform_helpers.rb
+++ b/spec/support/platform_helpers.rb
@@ -2,6 +2,7 @@ require "fcntl"
require "chef/mixin/shell_out"
require "ohai/mixin/http_helper"
require "ohai/mixin/gce_metadata"
+require "spec/support/chef_helpers"
class ShellHelpers
extend Chef::Mixin::ShellOut
@@ -92,14 +93,6 @@ def windows_user_right?(right)
Chef::ReservedNames::Win32::Security.get_account_right(ENV["USERNAME"]).include?(right)
end
-def macos_1013?
- macos? && Gem::Requirement.new("~> 10.13.0").satisfied_by?(Gem::Version.new(ohai[:platform_version]))
-end
-
-def macos_gte_1014?
- macos? && Gem::Requirement.new(">= 10.14").satisfied_by?(Gem::Version.new(ohai[:platform_version]))
-end
-
# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system)
def windows64?
windows? && ( ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" || ENV["PROCESSOR_ARCHITEW6432"] == "AMD64" )
@@ -122,6 +115,10 @@ def macos?
RUBY_PLATFORM.include?("darwin")
end
+def macos_gte_11?
+ macos? && !!(ohai[:platform_version].to_i >= 11)
+end
+
def solaris?
RUBY_PLATFORM.include?("solaris")
end
diff --git a/spec/support/shared/functional/execute_resource.rb b/spec/support/shared/functional/execute_resource.rb
index 9d1c29dfac..62f3f19a2b 100644
--- a/spec/support/shared/functional/execute_resource.rb
+++ b/spec/support/shared/functional/execute_resource.rb
@@ -58,7 +58,7 @@ shared_context "a command that can be executed as an alternate user" do
include Chef::Mixin::ShellOut
before do
- shell_out!("icacls \"#{script_output_dir.tr("/", '\\')}\" /grant \"authenticated users:(F)\"")
+ shell_out!("icacls \"#{script_output_dir.tr("/", "\\")}\" /grant \"authenticated users:(F)\"")
end
after do
diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb
deleted file mode 100644
index 890c28de2c..0000000000
--- a/spec/support/shared/functional/win32_service.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-
-require "chef/application/windows_service_manager"
-
-shared_context "using Win32::Service" do
- # Some helper methods.
-
- def test_service_exists?
- ::Win32::Service.exists?("spec-service")
- end
-
- def test_service_state
- ::Win32::Service.status("spec-service").current_state
- end
-
- def service_manager
- Chef::Application::WindowsServiceManager.new(test_service)
- end
-
- def cleanup
- # Uninstall if the test service is installed.
- if test_service_exists?
-
- # We can only uninstall when the service is stopped.
- if test_service_state != "stopped"
- ::Win32::Service.send("stop", "spec-service")
- sleep 1 while test_service_state != "stopped"
- end
-
- ::Win32::Service.delete("spec-service")
- end
-
- # Delete the test_service_file if it exists
- if File.exist?(test_service_file)
- File.delete(test_service_file)
- end
- end
-
- # Definition for the test-service
-
- let(:test_service) do
- {
- service_name: "spec-service",
- service_display_name: "Spec Test Service",
- service_description: "Service for testing Chef::Application::WindowsServiceManager.",
- service_file_path: File.expand_path(File.join(__dir__, "../../platforms/win32/spec_service.rb")),
- delayed_start: true,
- }
- end
-
- # Test service creates a file for us to verify that it is running.
- # Since our test service is running as Local System we should look
- # for the file it creates under SYSTEM temp directory
-
- let(:test_service_file) do
- "#{ENV["SystemDrive"]}\\windows\\temp\\spec_service_file"
- end
-end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index 41f2b46995..c42a04004a 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -22,7 +22,6 @@ require "fileutils"
require "chef/config"
require "chef/json_compat"
require "chef/server_api"
-require "support/shared/integration/knife_support"
require "cheffish/rspec/chef_run_support"
module Cheffish
diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb
index f624a6ae44..dccfd5d027 100644
--- a/spec/support/shared/unit/provider/file.rb
+++ b/spec/support/shared/unit/provider/file.rb
@@ -36,7 +36,7 @@ end
# forwards-vs-reverse slashes on windows sucks
def windows_path
- windows? ? normalized_path.tr('\\', "/") : normalized_path
+ windows? ? normalized_path.tr("\\", "/") : normalized_path
end
# this is all getting a bit stupid, CHEF-4802 cut to remove all this
@@ -479,12 +479,14 @@ shared_examples_for Chef::Provider::File do
it "calls #verify on each verification with tempfile path" do
provider.new_resource.verify windows? ? "REM" : "true"
provider.new_resource.verify windows? ? "REM" : "true"
+ allow(provider).to receive(:contents_changed?).and_return(true)
provider.send(:do_validate_content)
end
it "raises an exception if any verification fails" do
allow(File).to receive(:directory?).with("C:\\Windows\\system32/cmd.exe").and_return(false)
allow(provider).to receive(:tempfile).and_return(tempfile)
+ allow(provider).to receive(:contents_changed?).and_return(true)
provider.new_resource.verify windows? ? "cmd.exe c exit 1" : "false"
provider.new_resource.verify.each do |v|
allow(v).to receive(:verify).and_return(false)
@@ -492,9 +494,21 @@ shared_examples_for Chef::Provider::File do
expect { provider.send(:do_validate_content) }.to raise_error(Chef::Exceptions::ValidationFailed)
end
+ it "does not run verifications when the contents did not change" do
+ allow(File).to receive(:directory?).with("C:\\Windows\\system32/cmd.exe").and_return(false)
+ allow(provider).to receive(:tempfile).and_return(tempfile)
+ allow(provider).to receive(:contents_changed?).and_return(false)
+ provider.new_resource.verify windows? ? "cmd.exe c exit 1" : "false"
+ provider.new_resource.verify.each do |v|
+ expect(v).not_to receive(:verify)
+ end
+ provider.send(:do_validate_content)
+ end
+
it "does not show verification for sensitive resources" do
allow(File).to receive(:directory?).with("C:\\Windows\\system32/cmd.exe").and_return(false)
allow(provider).to receive(:tempfile).and_return(tempfile)
+ allow(provider).to receive(:contents_changed?).and_return(true)
provider.new_resource.sensitive true
provider.new_resource.verify windows? ? "cmd.exe c exit 1" : "false"
provider.new_resource.verify.each do |v|
diff --git a/spec/support/shared/unit/script_resource.rb b/spec/support/shared/unit/script_resource.rb
index 0256112a68..4b51cb7794 100644
--- a/spec/support/shared/unit/script_resource.rb
+++ b/spec/support/shared/unit/script_resource.rb
@@ -47,9 +47,9 @@ shared_examples_for "a script resource" do
end
describe "when executing guards" do
- it "inherits exactly the :cwd, :domain, :environment, :group, :password, :path, :user, and :umask attributes from a parent resource class" do
+ it "inherits exactly the :cwd, :domain, :environment, :group, :password, :path, :user, :umask, and :login attributes from a parent resource class" do
inherited_difference = Chef::Resource::Script.guard_inherited_attributes -
- %i{cwd domain environment group password path user umask}
+ %i{cwd domain environment group password path user umask login}
expect(inherited_difference).to eq([])
end
diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb
deleted file mode 100644
index bce6b19366..0000000000
--- a/spec/unit/application/knife_spec.rb
+++ /dev/null
@@ -1,241 +0,0 @@
-#
-# Author:: AJ Christensen (<aj@junglist.gen.nz>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "#{CHEF_SPEC_DATA}/knife_subcommand/test_yourself"
-
-describe Chef::Application::Knife do
- include SpecHelpers::Knife
-
- before(:all) do
- class NoopKnifeCommand < Chef::Knife
- option :opt_with_default,
- short: "-D VALUE",
- long: "-optwithdefault VALUE",
- default: "default-value"
-
- def run; end
- end
- end
-
- after(:each) do
- # reset some really nasty global state
- NoopKnifeCommand.reset_config_loader!
- end
-
- before(:each) do
- # Prevent code from getting loaded on every test invocation.
- allow(Chef::Knife).to receive(:load_commands)
-
- @knife = Chef::Application::Knife.new
- allow(@knife).to receive(:puts)
- allow(@knife).to receive(:trap)
- allow(Chef::Knife).to receive(:list_commands)
- end
-
- it "should exit 1 and print the options if no arguments are given at all" do
- with_argv([]) do
- expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
- end
- end
-
- it "should exit 2 if run without a sub command" do
- with_argv("--user", "adam") do
- expect(Chef::Log).to receive(:error).with(/you need to pass a sub\-command/i)
- expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) }
- end
- end
-
- it "should run a sub command with the applications command line option prototype" do
- with_argv(*%w{noop knife command with some args}) do
- knife = double(Chef::Knife)
- expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife)
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- end
-
- it "should set the colored output to true by default on windows and true on all other platforms as well" do
- with_argv(*%w{noop knife command}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:color]).to be_truthy
- end
-
- context "validate --format option" do
- it "should set the default format summary" do
- with_argv(*%w{noop knife command}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- expect(@knife.default_config[:format]).to eq("summary")
- end
- end
-
- it "should raise the error for invalid value" do
- with_argv(*%w{noop knife command -F abc}) do
- expect(STDOUT).to receive(:puts).at_least(2).times
- expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) }
- end
- end
- end
-
- context "when given fips flags" do
- context "when Chef::Config[:fips]=false" do
- before do
- # This is required because the chef-fips pipeline does
- # has a default value of true for fips
- Chef::Config[:fips] = false
- end
-
- it "does not initialize fips mode when no flags are passed" do
- with_argv(*%w{noop knife command}) do
- expect(@knife).to receive(:exit).with(0)
- expect(Chef::Config).not_to receive(:enable_fips_mode)
- @knife.run
- expect(Chef::Config[:fips]).to eq(false)
- end
- end
-
- it "overwrites the Chef::Config value when passed --fips" do
- with_argv(*%w{noop knife command --fips}) do
- expect(@knife).to receive(:exit).with(0)
- expect(Chef::Config).to receive(:enable_fips_mode)
- @knife.run
- expect(Chef::Config[:fips]).to eq(true)
- end
- end
- end
-
- context "when Chef::Config[:fips]=true" do
- before do
- Chef::Config[:fips] = true
- end
-
- it "initializes fips mode when passed --fips" do
- with_argv(*%w{noop knife command --fips}) do
- expect(@knife).to receive(:exit).with(0)
- expect(Chef::Config).to receive(:enable_fips_mode)
- @knife.run
- expect(Chef::Config[:fips]).to eq(true)
- end
- end
-
- it "overwrites the Chef::Config value when passed --no-fips" do
- with_argv(*%w{noop knife command --no-fips}) do
- expect(@knife).to receive(:exit).with(0)
- expect(Chef::Config).not_to receive(:enable_fips_mode)
- @knife.run
- expect(Chef::Config[:fips]).to eq(false)
- end
- end
- end
- end
-
- describe "when given a path to the client key" do
- it "expands a relative path relative to the CWD" do
- relative_path = ".chef/client.pem"
- allow(Dir).to receive(:pwd).and_return(CHEF_SPEC_DATA)
- with_argv(*%W{noop knife command -k #{relative_path}}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:client_key]).to eq(File.join(CHEF_SPEC_DATA, relative_path))
- end
-
- it "expands a ~/home/path to the correct full path" do
- home_path = "~/.chef/client.pem"
- with_argv(*%W{noop knife command -k #{home_path}}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:client_key]).to eq(File.join(ENV["HOME"], ".chef/client.pem").gsub((File::ALT_SEPARATOR || '\\'), File::SEPARATOR))
- end
-
- it "does not expand a full path" do
- full_path = if windows?
- "C:/chef/client.pem"
- else
- "/etc/chef/client.pem"
- end
- with_argv(*%W{noop knife command -k #{full_path}}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:client_key]).to eq(full_path)
- end
- end
-
- describe "with environment configuration" do
- before do
- Chef::Config[:environment] = nil
- end
-
- it "should default to no environment" do
- with_argv(*%w{noop knife command}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:environment]).to eq(nil)
- end
-
- it "should load the environment from the config file" do
- config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
- with_argv(*%W{noop knife command -c #{config_file}}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:environment]).to eq("production")
- end
-
- it "should load the environment from the CLI options" do
- with_argv(*%w{noop knife command -E development}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:environment]).to eq("development")
- end
-
- it "should override the config file environment with the CLI environment" do
- config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
- with_argv(*%W{noop knife command -c #{config_file} -E override}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:environment]).to eq("override")
- end
-
- it "should override the config file environment with the CLI environment regardless of order" do
- config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb")
- with_argv(*%W{noop knife command -E override -c #{config_file}}) do
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- expect(Chef::Config[:environment]).to eq("override")
- end
-
- it "should run a sub command with the applications command line option prototype" do
- with_argv(*%w{noop knife command with some args}) do
- knife = double(Chef::Knife)
- expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife)
- expect(@knife).to receive(:exit).with(0)
- @knife.run
- end
- end
- end
-
-end
diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb
index 0824f8b5b3..16f4a7785b 100644
--- a/spec/unit/application/solo_spec.rb
+++ b/spec/unit/application/solo_spec.rb
@@ -108,7 +108,7 @@ describe Chef::Application::Solo do
tarfile = StringIO.new("remote_tarball_content")
target_file = StringIO.new
- expect(app).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile)
+ expect(URI).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile)
expect(File).to receive(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(target_file)
archive = double(Mixlib::Archive)
@@ -202,7 +202,7 @@ describe Chef::Application::Solo do
expect(Chef::Config).to receive(:find_chef_repo_path).and_return(root_path)
app.reconfigure
expect(Chef::Config.key?(:chef_repo_path)).to be_truthy
- expect(Chef::Config[:chef_repo_path]).to eq (root_path)
+ expect(Chef::Config[:chef_repo_path]).to eq(root_path)
end
it "runs chef-client in local mode" do
diff --git a/spec/unit/chef_fs/diff_spec.rb b/spec/unit/chef_fs/diff_spec.rb
index 726adf8e50..98ab41ca61 100644
--- a/spec/unit/chef_fs/diff_spec.rb
+++ b/spec/unit/chef_fs/diff_spec.rb
@@ -20,7 +20,7 @@ require "spec_helper"
require "chef/chef_fs/file_pattern"
require "chef/chef_fs/command_line"
-describe "diff", uses_diff: true do
+describe "diff", uses_diff: true, ruby: ">= 3.0" do
include FileSystemSupport
# Removes the date stamp from the diff and replaces it with ' DATE'
diff --git a/spec/unit/chef_fs/file_system/repository/directory_spec.rb b/spec/unit/chef_fs/file_system/repository/directory_spec.rb
index c8d71f976c..74cddcedb0 100644
--- a/spec/unit/chef_fs/file_system/repository/directory_spec.rb
+++ b/spec/unit/chef_fs/file_system/repository/directory_spec.rb
@@ -64,7 +64,7 @@ describe Chef::ChefFS::FileSystem::Repository::Directory do
end
let(:file_double) do
- double(TestFile, create: true, exists?: false)
+ double(TestFile, create: true, exist?: false, exists?: false)
end
context "#make_child_entry" do
diff --git a/spec/unit/chef_fs/file_system_spec.rb b/spec/unit/chef_fs/file_system_spec.rb
index 99ea6049ff..0d4ae6aa05 100644
--- a/spec/unit/chef_fs/file_system_spec.rb
+++ b/spec/unit/chef_fs/file_system_spec.rb
@@ -20,7 +20,7 @@ require "spec_helper"
require "chef/chef_fs/file_system"
require "chef/chef_fs/file_pattern"
-describe Chef::ChefFS::FileSystem do
+describe Chef::ChefFS::FileSystem, ruby: ">= 3.0" do
include FileSystemSupport
context "with empty filesystem" do
diff --git a/spec/unit/chef_fs/parallelizer_spec.rb b/spec/unit/chef_fs/parallelizer_spec.rb
deleted file mode 100644
index 519a628347..0000000000
--- a/spec/unit/chef_fs/parallelizer_spec.rb
+++ /dev/null
@@ -1,479 +0,0 @@
-require "spec_helper"
-require "chef/chef_fs/parallelizer"
-
-# FIXME: these are disabled on MacOS due to timing issues in our anka build cluster
-# these issues should be fixed and the tests should be re-eenabled. If we are getting
-# omnibus test phases on mac tests which are reasonable and not ~3 hours long, then the
-# condition to avoid this testing on macs can be deleted
-describe Chef::ChefFS::Parallelizer, :not_supported_on_macos do
- before :each do
- @start_time = Time.now
- end
-
- def elapsed_time
- Time.now - @start_time
- end
-
- after :each do
- parallelizer.kill
- end
-
- context "With a Parallelizer with 5 threads" do
- let :parallelizer do
- Chef::ChefFS::Parallelizer.new(5)
- end
-
- def parallelize(inputs, options = {}, &block)
- parallelizer.parallelize(inputs, { main_thread_processing: false }.merge(options), &block)
- end
-
- it "parallel_do creates unordered output as soon as it is available" do
- outputs = []
- parallelizer.parallel_do([0.5, 0.3, 0.1]) do |val|
- sleep val
- outputs << val
- end
- expect(elapsed_time).to be < 0.6
- expect(outputs).to eq([ 0.1, 0.3, 0.5 ])
- end
-
- context "With :ordered => false (unordered output)" do
- it "An empty input produces an empty output" do
- expect(parallelize([], ordered: false) do
- sleep 10
- end.to_a).to eql([])
- expect(elapsed_time).to be < 0.1
- end
-
- it "10 sleep(0.2)s complete within 0.5 seconds" do
- expect(parallelize(1.upto(10), ordered: false) do |i|
- sleep 0.2
- "x"
- end.to_a).to eq(%w{x x x x x x x x x x})
- expect(elapsed_time).to be < 0.5
- end
-
- it "The output comes as soon as it is available" do
- enum = parallelize([0.5, 0.3, 0.1], ordered: false) do |val|
- sleep val
- val
- end
- expect(enum.map do |value|
- expect(elapsed_time).to be < value + 0.1
- value
- end).to eq([ 0.1, 0.3, 0.5 ])
- end
-
- it "An exception in input is passed through but does NOT stop processing" do
- input = TestEnumerable.new(0.5, 0.3, 0.1) do
- raise "hi"
- end
- enum = parallelize(input, ordered: false) { |x| sleep(x); x }
- results = []
- expect { enum.each { |value| results << value } }.to raise_error "hi"
- expect(results).to eq([ 0.1, 0.3, 0.5 ])
- expect(elapsed_time).to be < 0.6
- end
-
- it "Exceptions in output are raised after all processing is done" do
- processed = 0
- enum = parallelize([1, 2, "x", 3], ordered: false) do |x|
- if x == "x"
- sleep 0.1
- raise "hi"
- end
- sleep 0.2
- processed += 1
- x
- end
- results = []
- expect { enum.each { |value| results << value } }.to raise_error "hi"
- expect(results.sort).to eq([ 1, 2, 3 ])
- expect(elapsed_time).to be < 0.3
- expect(processed).to eq(3)
- end
-
- it "Exceptions with :stop_on_exception are raised after all processing is done" do
- processed = 0
- parallelized = parallelize([0.3, 0.3, "x", 0.3, 0.3, 0.3, 0.3, 0.3], ordered: false, stop_on_exception: true) do |x|
- if x == "x"
- sleep(0.1)
- raise "hi"
- end
- sleep(x)
- processed += 1
- x
- end
- expect { parallelized.to_a }.to raise_error "hi"
- expect(processed).to eq(4)
- end
- end
-
- context "With :ordered => true (ordered output)" do
- it "An empty input produces an empty output" do
- expect(parallelize([]) do
- sleep 10
- end.to_a).to eql([])
- expect(elapsed_time).to be < 0.1
- end
-
- it "10 sleep(0.2)s complete within 0.5 seconds" do
- expect(parallelize(1.upto(10), ordered: true) do |i|
- sleep 0.2
- "x"
- end.to_a).to eq(%w{x x x x x x x x x x})
- expect(elapsed_time).to be < 0.5
- end
-
- it "Output comes in the order of the input" do
- enum = parallelize([0.5, 0.3, 0.1]) do |val|
- sleep val
- val
- end.enum_for(:each_with_index)
- expect(enum.next).to eq([ 0.5, 0 ])
- expect(enum.next).to eq([ 0.3, 1 ])
- expect(enum.next).to eq([ 0.1, 2 ])
- expect(elapsed_time).to be < 0.6
- end
-
- it "Exceptions in input are raised in the correct sequence but do NOT stop processing" do
- input = TestEnumerable.new(0.5, 0.3, 0.1) do
- raise "hi"
- end
- results = []
- enum = parallelize(input) { |x| sleep(x); x }
- expect { enum.each { |value| results << value } }.to raise_error "hi"
- expect(elapsed_time).to be < 0.6
- expect(results).to eq([ 0.5, 0.3, 0.1 ])
- end
-
- it "Exceptions in output are raised in the correct sequence and running processes do NOT stop processing" do
- processed = 0
- enum = parallelize([1, 2, "x", 3]) do |x|
- if x == "x"
- sleep(0.1)
- raise "hi"
- end
- sleep(0.2)
- processed += 1
- x
- end
- results = []
- expect { enum.each { |value| results << value } }.to raise_error "hi"
- expect(results).to eq([ 1, 2 ])
- expect(elapsed_time).to be < 0.3
- expect(processed).to eq(3)
- end
-
- it "Exceptions with :stop_on_exception are raised after all processing is done" do
- processed = 0
- parallelized = parallelize([0.3, 0.3, "x", 0.3, 0.3, 0.3, 0.3, 0.3], ordered: false, stop_on_exception: true) do |x|
- if x == "x"
- sleep(0.1)
- raise "hi"
- end
- sleep(x)
- processed += 1
- x
- end
- expect { parallelized.to_a }.to raise_error "hi"
- expect(processed).to eq(4)
- end
- end
-
- it "When the input is slow, output still proceeds" do
- input = TestEnumerable.new do |&block|
- block.call(1)
- sleep 0.1
- block.call(2)
- sleep 0.1
- block.call(3)
- sleep 0.1
- end
- enum = parallelize(input) { |x| x }
- expect(enum.map do |value|
- expect(elapsed_time).to be < (value + 1) * 0.1
- value
- end).to eq([ 1, 2, 3 ])
- end
- end
-
- context "With a Parallelizer with 1 thread" do
- let :parallelizer do
- Chef::ChefFS::Parallelizer.new(1)
- end
-
- context "when the thread is occupied with a job" do
- before :each do
- parallelizer
- started = false
- @occupying_job_finished = occupying_job_finished = [ false ]
- @thread = Thread.new do
- parallelizer.parallelize([0], main_thread_processing: false) do |x|
- started = true
- sleep(0.3)
- occupying_job_finished[0] = true
- end.wait
- end
- sleep(0.01) until started
- end
-
- after :each do
- Thread.kill(@thread)
- end
-
- it "parallelize with :main_thread_processing = true does not block" do
- expect(parallelizer.parallelize([1]) do |x|
- sleep(0.1)
- x
- end.to_a).to eq([ 1 ])
- expect(elapsed_time).to be < 0.2
- end
-
- it "parallelize with :main_thread_processing = false waits for the job to finish" do
- expect(parallelizer.parallelize([1], main_thread_processing: false) do |x|
- sleep(0.1)
- x + 1
- end.to_a).to eq([ 2 ])
- expect(elapsed_time).to be > 0.3
- end
-
- it "resizing the Parallelizer to 0 waits for the job to stop" do
- expect(elapsed_time).to be < 0.2
- parallelizer.resize(0)
- expect(parallelizer.num_threads).to eq(0)
- expect(elapsed_time).to be > 0.25
- expect(@occupying_job_finished).to eq([ true ])
- end
-
- it "stopping the Parallelizer waits for the job to finish" do
- expect(elapsed_time).to be < 0.2
- parallelizer.stop
- expect(parallelizer.num_threads).to eq(0)
- expect(elapsed_time).to be > 0.25
- expect(@occupying_job_finished).to eq([ true ])
- end
-
- it "resizing the Parallelizer to 2 does not stop the job" do
- expect(elapsed_time).to be < 0.2
- parallelizer.resize(2)
- expect(parallelizer.num_threads).to eq(2)
- expect(elapsed_time).to be < 0.2
- sleep(0.3)
- expect(@occupying_job_finished).to eq([ true ])
- end
- end
-
- context "enumerable methods should run efficiently" do
- it ".count does not process anything" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.count).to eq(6)
- expect(outputs_processed).to eq(0)
- expect(input_mapper.num_processed).to eq(6)
- end
-
- it ".count with arguments works normally" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 1, 1, 1, 2, 2, 2, 3, 3, 4)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- x
- end
- expect(enum.count { |x| x > 1 }).to eq(6)
- expect(enum.count(2)).to eq(3)
- expect(outputs_processed).to eq(20)
- expect(input_mapper.num_processed).to eq(20)
- end
-
- it ".first does not enumerate anything other than the first result(s)" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.first).to eq(1)
- expect(enum.first(2)).to eq([1, 2])
- expect(outputs_processed).to eq(3)
- expect(input_mapper.num_processed).to eq(3)
- end
-
- it ".take does not enumerate anything other than the first result(s)" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.take(2)).to eq([1, 2])
- expect(outputs_processed).to eq(2)
- expect(input_mapper.num_processed).to eq(2)
- end
-
- it ".drop does not process anything other than the last result(s)" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.drop(2)).to eq([3, 4, 5, 6])
- expect(outputs_processed).to eq(4)
- expect(input_mapper.num_processed).to eq(6)
- end
-
- if Enumerable.method_defined?(:lazy)
- it ".lazy.take does not enumerate anything other than the first result(s)" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.lazy.take(2).to_a).to eq([1, 2])
- expect(outputs_processed).to eq(2)
- expect(input_mapper.num_processed).to eq(2)
- end
-
- it ".drop does not process anything other than the last result(s)" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- expect(enum.lazy.drop(2).to_a).to eq([3, 4, 5, 6])
- expect(outputs_processed).to eq(4)
- expect(input_mapper.num_processed).to eq(6)
- end
-
- it "lazy enumerable is actually lazy" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- sleep(0.05) # Just enough to yield and get other inputs in the queue
- x
- end
- enum.lazy.take(2)
- enum.lazy.drop(2)
- sleep(0.1)
- expect(outputs_processed).to eq(0)
- expect(input_mapper.num_processed).to eq(0)
- end
- end
- end
-
- context "running enumerable multiple times should function correctly" do
- it ".map twice on the same parallel enumerable returns the correct results and re-processes the input" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- x
- end
- expect(enum.map { |x| x }).to eq([1, 2, 3])
- expect(enum.map { |x| x }).to eq([1, 2, 3])
- expect(outputs_processed).to eq(6)
- expect(input_mapper.num_processed).to eq(6)
- end
-
- it ".first and then .map on the same parallel enumerable returns the correct results and re-processes the input" do
- outputs_processed = 0
- input_mapper = TestEnumerable.new(1, 2, 3)
- enum = parallelizer.parallelize(input_mapper) do |x|
- outputs_processed += 1
- x
- end
- expect(enum.first).to eq(1)
- expect(enum.map { |x| x }).to eq([1, 2, 3])
- expect(outputs_processed).to be >= 4
- expect(input_mapper.num_processed).to be >= 4
- end
-
- it "two simultaneous enumerations throws an exception" do
- enum = parallelizer.parallelize([1, 2, 3]) { |x| x }
- a = enum.enum_for(:each)
- a.next
- expect do
- b = enum.enum_for(:each)
- b.next
- end.to raise_error(RuntimeError, "each() called on parallel enumerable twice simultaneously! Bad mojo")
- end
- end
- end
-
- context "With a Parallelizer with 0 threads" do
- let :parallelizer do
- Chef::ChefFS::Parallelizer.new(0)
- end
-
- context "And main_thread_processing on" do
- it "succeeds in running" do
- expect(parallelizer.parallelize([0.5]) { |x| x * 2 }.to_a).to eq([1])
- end
- end
- end
-
- context "With a Parallelizer with 10 threads" do
- let :parallelizer do
- Chef::ChefFS::Parallelizer.new(10)
- end
-
- it "does not have contention issues with large numbers of inputs" do
- expect(parallelizer.parallelize(1.upto(500)) { |x| x + 1 }.to_a).to eq(2.upto(501).to_a)
- end
-
- it "does not have contention issues with large numbers of inputs with ordering off" do
- expect(parallelizer.parallelize(1.upto(500), ordered: false) { |x| x + 1 }.to_a.sort).to eq(2.upto(501).to_a)
- end
-
- it "does not have contention issues with large numbers of jobs and inputs with ordering off" do
- parallelizers = 0.upto(99).map do
- parallelizer.parallelize(1.upto(500)) { |x| x + 1 }
- end
- outputs = []
- threads = 0.upto(99).map do |i|
- Thread.new { outputs[i] = parallelizers[i].to_a }
- end
- threads.each(&:join)
- outputs.each { |output| expect(output.sort).to eq(2.upto(501).to_a) }
- end
- end
-
- class TestEnumerable
- include Enumerable
-
- def initialize(*values, &block)
- @values = values
- @block = block
- @num_processed = 0
- end
-
- attr_reader :num_processed
-
- def each
- @values.each do |value|
- @num_processed += 1
- yield(value)
- end
- if @block
- @block.call do |value|
- @num_processed += 1
- yield(value)
- end
- end
- end
- end
-end
diff --git a/spec/unit/compliance/fetcher/automate_spec.rb b/spec/unit/compliance/fetcher/automate_spec.rb
index bc2125aaa7..a4cd0c76c3 100644
--- a/spec/unit/compliance/fetcher/automate_spec.rb
+++ b/spec/unit/compliance/fetcher/automate_spec.rb
@@ -21,12 +21,12 @@ describe Chef::Compliance::Fetcher::Automate do
expect(res.target).to eq(expected)
end
- it "raises an exception with no data collector token" do
- Chef::Config[:data_collector].delete(:token)
+ it "should resolve a compliance URL with a @ in the namespace" do
+ res = Chef::Compliance::Fetcher::Automate.resolve("compliance://name@space/profile_name")
- expect {
- Chef::Compliance::Fetcher::Automate.resolve("compliance://namespace/profile_name")
- }.to raise_error(/No data-collector token set/)
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
+ expected = "https://automate.test/compliance/profiles/name@space/profile_name/tar"
+ expect(res.target).to eq(expected)
end
it "includes the data collector token" do
@@ -100,14 +100,6 @@ describe Chef::Compliance::Fetcher::Automate do
expect(res.target).to eq(expected)
end
- it "raises an exception with no data collector token" do
- Chef::Config[:data_collector].delete(:token)
-
- expect {
- Chef::Compliance::Fetcher::Automate.resolve(compliance: "namespace/profile_name")
- }.to raise_error(Inspec::FetcherFailure, /No data-collector token set/)
- end
-
it "includes the data collector token" do
expect(Chef::Compliance::Fetcher::Automate).to receive(:new).with(
"https://automate.test/compliance/profiles/namespace/profile_name/tar",
diff --git a/spec/unit/compliance/reporter/automate_spec.rb b/spec/unit/compliance/reporter/automate_spec.rb
index e0a33892b0..60d630d32b 100644
--- a/spec/unit/compliance/reporter/automate_spec.rb
+++ b/spec/unit/compliance/reporter/automate_spec.rb
@@ -1,6 +1,7 @@
require "spec_helper"
require "json" # For .to_json
+require "chef/compliance/reporter/automate"
describe Chef::Compliance::Reporter::Automate do
let(:reporter) { Chef::Compliance::Reporter::Automate.new(opts) }
@@ -264,11 +265,34 @@ describe Chef::Compliance::Reporter::Automate do
expect(metasearch_stub).to have_been_requested
expect(report_stub).to have_been_requested
end
+ end
- it "does not send report when entity_uuid is missing" do
+ describe "#validate_config!" do
+ it "raises CMPL004 when entity_uuid is not present" do
opts.delete(:entity_uuid)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL004/)
+ end
+
+ it "raises CMPL005 when run_id is not present" do
+ opts.delete(:run_id)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL005/)
+ end
+
+ it "raises CMPL006 when data collector URL is missing" do
+ Chef::Config[:data_collector] = { token: "not_nil", server_url: nil }
reporter = Chef::Compliance::Reporter::Automate.new(opts)
- expect(reporter.send_report(inspec_report)).to eq(false)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL006/)
+ end
+
+ it "raises CMPL006 when data collector token is missing" do
+ Chef::Config[:data_collector] = { token: nil, server_url: "not_nil" }
+ reporter = Chef::Compliance::Reporter::Automate.new(opts)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL006/)
+ end
+
+ it "otherwise passes" do
+ Chef::Config[:data_collector] = { token: "not_nil", server_url: "not_nil" }
+ reporter.validate_config!
end
end
diff --git a/spec/unit/compliance/reporter/chef_server_automate_spec.rb b/spec/unit/compliance/reporter/chef_server_automate_spec.rb
index e45a7157ee..33642dea31 100644
--- a/spec/unit/compliance/reporter/chef_server_automate_spec.rb
+++ b/spec/unit/compliance/reporter/chef_server_automate_spec.rb
@@ -1,7 +1,9 @@
require "spec_helper"
+require "chef/compliance/reporter/chef_server_automate"
describe Chef::Compliance::Reporter::ChefServerAutomate do
before do
+ # Isn't this already done globally in
WebMock.disable_net_connect!
Chef::Config[:client_key] = File.expand_path("../../../data/ssl/private_key.pem", __dir__)
@@ -174,4 +176,22 @@ describe Chef::Compliance::Reporter::ChefServerAutomate do
expect(report_stub).to have_been_requested
end
+
+ describe "#validate_config!" do
+ it "raises CMPL007 when entity_uuid is not present" do
+ opts.delete(:entity_uuid)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL007/)
+ end
+
+ it "raises CMPL008 when run_id is not present" do
+ opts.delete(:run_id)
+ expect { reporter.validate_config! }.to raise_error(/^CMPL008/)
+ end
+
+ it "otherwise passes" do
+ reporter.validate_config!
+ end
+
+ end
+
end
diff --git a/spec/unit/compliance/reporter/compliance_enforcer_spec.rb b/spec/unit/compliance/reporter/compliance_enforcer_spec.rb
index ae63cf0853..3f3ce6286b 100644
--- a/spec/unit/compliance/reporter/compliance_enforcer_spec.rb
+++ b/spec/unit/compliance/reporter/compliance_enforcer_spec.rb
@@ -1,4 +1,5 @@
require "spec_helper"
+require "chef/compliance/reporter/compliance_enforcer"
describe Chef::Compliance::Reporter::AuditEnforcer do
let(:reporter) { Chef::Compliance::Reporter::AuditEnforcer.new }
diff --git a/spec/unit/compliance/runner_spec.rb b/spec/unit/compliance/runner_spec.rb
index 68c8a9553b..e60195b8ec 100644
--- a/spec/unit/compliance/runner_spec.rb
+++ b/spec/unit/compliance/runner_spec.rb
@@ -8,43 +8,96 @@ describe Chef::Compliance::Runner do
described_class.new.tap do |r|
r.node = node
r.run_id = "my_run_id"
- r.recipes = []
end
end
describe "#enabled?" do
- it "is true if the node attributes have audit profiles and the audit cookbook is not present" do
+ context "when the node is not available" do
+ let(:runner) { described_class.new }
+ it "is false because it needs the node to answer that question" do
+ expect(runner).not_to be_enabled
+ end
+ end
+
+ it "is true if the node attributes have audit profiles and the audit cookbook is not present, and the compliance mode attribute is nil" do
+ node.normal["audit"]["profiles"]["ssh"] = { 'compliance': "base/ssh" }
+ node.normal["audit"]["compliance_phase"] = nil
+
+ expect(runner).to be_enabled
+ end
+
+ it "is true if the node attributes have audit profiles and the audit cookbook is not present, and the compliance mode attribute is true" do
node.normal["audit"]["profiles"]["ssh"] = { 'compliance': "base/ssh" }
- runner.recipes = %w{ fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = true
expect(runner).to be_enabled
end
- it "is false if the node attributes have audit profiles and the audit cookbook is present" do
+ it "is false if the node attributes have audit profiles and the audit cookbook is not present, and the compliance mode attribute is false" do
node.normal["audit"]["profiles"]["ssh"] = { 'compliance': "base/ssh" }
- runner.recipes = %w{ audit::default fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = false
expect(runner).not_to be_enabled
end
- it "is false if the node attributes do not have audit profiles and the audit cookbook is not present" do
- node.normal["audit"]["profiles"] = {}
- runner.recipes = %w{ fancy_cookbook::fanciness tacobell::nachos }
+ it "is false if the node attributes have audit profiles and the audit cookbook is present, and the complince mode attribute is nil" do
+ stub_const("::Reporter::ChefAutomate", true)
+ node.normal["audit"]["profiles"]["ssh"] = { 'compliance': "base/ssh" }
+ node.normal["audit"]["compliance_phase"] = nil
expect(runner).not_to be_enabled
end
- it "is false if the node attributes do not have audit profiles and the audit cookbook is present" do
+ it "is true if the node attributes have audit profiles and the audit cookbook is present, and the complince mode attribute is true" do
+ stub_const("::Reporter::ChefAutomate", true)
+ node.normal["audit"]["profiles"]["ssh"] = { 'compliance': "base/ssh" }
+ node.normal["audit"]["compliance_phase"] = true
+
+ expect(runner).to be_enabled
+ end
+
+ it "is false if the node attributes do not have audit profiles and the audit cookbook is not present, and the complince mode attribute is nil" do
node.normal["audit"]["profiles"] = {}
- runner.recipes = %w{ audit::default fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = nil
+
+ expect(runner).not_to be_enabled
+ end
+
+ it "is false if the node attributes do not have audit profiles and the audit cookbook is present, and the complince mode attribute is nil" do
+ stub_const("::Reporter::ChefAutomate", true)
+ node.automatic["recipes"] = %w{ audit::default fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = nil
expect(runner).not_to be_enabled
end
- it "is false if the node attributes do not have audit attributes and the audit cookbook is not present" do
- runner.recipes = %w{ fancy_cookbook::fanciness tacobell::nachos }
+ it "is false if the node attributes do not have audit attributes and the audit cookbook is not present, and the complince mode attribute is nil" do
+ node.automatic["recipes"] = %w{ fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = nil
+
expect(runner).not_to be_enabled
end
+
+ it "is true if the node attributes do not have audit profiles and the audit cookbook is not present, and the complince mode attribute is true" do
+ node.normal["audit"]["profiles"] = {}
+ node.normal["audit"]["compliance_phase"] = true
+
+ expect(runner).to be_enabled
+ end
+
+ it "is true if the node attributes do not have audit profiles and the audit cookbook is present, and the complince mode attribute is true" do
+ stub_const("::Reporter::ChefAutomate", true)
+ node.automatic["recipes"] = %w{ audit::default fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = true
+
+ expect(runner).to be_enabled
+ end
+
+ it "is true if the node attributes do not have audit attributes and the audit cookbook is not present, and the complince mode attribute is true" do
+ node.automatic["recipes"] = %w{ fancy_cookbook::fanciness tacobell::nachos }
+ node.normal["audit"]["compliance_phase"] = true
+ expect(runner).to be_enabled
+ end
end
describe "#inspec_profiles" do
@@ -77,7 +130,7 @@ describe Chef::Compliance::Runner do
expect(runner.inspec_profiles).to eq(expected)
end
- it "raises an error when the profiles are in the old audit-cookbook format" do
+ it "raises a CMPL010 message when the profiles are in the old audit-cookbook format" do
node.normal["audit"]["profiles"] = [
{
name: "Windows 2019 Baseline",
@@ -85,7 +138,7 @@ describe Chef::Compliance::Runner do
},
]
- expect { runner.inspec_profiles }.to raise_error(/profiles specified in an unrecognized format, expected a hash of hashes./)
+ expect { runner.inspec_profiles }.to raise_error(/CMPL010:/)
end
end
@@ -110,4 +163,79 @@ describe Chef::Compliance::Runner do
runner.warn_for_deprecated_config_values!
end
end
+
+ describe "#reporter" do
+ context "chef-server-automate reporter" do
+ it "uses the correct URL when 'server' attribute is set" do
+ Chef::Config[:chef_server_url] = "https://chef_config_url.example.com/my_org"
+ node.normal["audit"]["server"] = "https://server_attribute_url.example.com/application/sub_application"
+
+ reporter = runner.reporter("chef-server-automate")
+
+ expect(reporter).to be_kind_of(Chef::Compliance::Reporter::ChefServerAutomate)
+ expect(reporter.url).to eq(URI("https://server_attribute_url.example.com/application/sub_application/organizations/my_org/data-collector"))
+ end
+
+ it "falls back to chef_server_url for URL when 'server' attribute is not set" do
+ Chef::Config[:chef_server_url] = "https://chef_config_url.example.com/my_org"
+
+ reporter = runner.reporter("chef-server-automate")
+
+ expect(reporter).to be_kind_of(Chef::Compliance::Reporter::ChefServerAutomate)
+ expect(reporter.url).to eq(URI("https://chef_config_url.example.com/organizations/my_org/data-collector"))
+ end
+ end
+
+ end
+
+ describe "#load_and_validate! when compliance is enabled" do
+ before do
+ allow(runner).to receive(:enabled?).and_return(true)
+ end
+
+ it "raises CMPL003 when the reporter is not a supported reporter type" do
+ node.normal["audit"]["reporter"] = [ "invalid" ]
+ expect { runner.load_and_validate! }.to raise_error(/^CMPL003:/)
+ end
+ it "raises CMPL002 if the configured fetcher is not supported" do
+ node.normal["audit"]["fetcher"] = "invalid"
+ expect { runner.load_and_validate! }.to raise_error(/^CMPL002:/)
+ end
+
+ it "validates configured reporters" do
+ node.normal["audit"]["reporter"] = [ "chef-automate" ]
+ reporter_double = double("reporter", validate_config!: nil)
+ expect(runner).to receive(:reporter).with("chef-automate").and_return(reporter_double)
+ runner.load_and_validate!
+ end
+
+ end
+
+ describe "#inspec_opts" do
+ it "does not include chef_node in inputs by default" do
+ node.normal["audit"]["attributes"] = {
+ "tacos" => "lunch",
+ "nachos" => "dinner",
+ }
+
+ inputs = runner.inspec_opts[:inputs]
+
+ expect(inputs["tacos"]).to eq("lunch")
+ expect(inputs.key?("chef_node")).to eq(false)
+ end
+
+ it "includes chef_node in inputs with chef_node_attribute_enabled set" do
+ node.normal["audit"]["chef_node_attribute_enabled"] = true
+ node.normal["audit"]["attributes"] = {
+ "tacos" => "lunch",
+ "nachos" => "dinner",
+ }
+
+ inputs = runner.inspec_opts[:inputs]
+
+ expect(inputs["tacos"]).to eq("lunch")
+ expect(inputs["chef_node"]["audit"]["reporter"]).to eq(%w{json-file cli})
+ expect(inputs["chef_node"]["chef_environment"]).to eq("_default")
+ end
+ end
end
diff --git a/spec/unit/cookbook_site_streaming_uploader_spec.rb b/spec/unit/cookbook_site_streaming_uploader_spec.rb
deleted file mode 100644
index af714094d0..0000000000
--- a/spec/unit/cookbook_site_streaming_uploader_spec.rb
+++ /dev/null
@@ -1,198 +0,0 @@
-#
-# Author:: Xabier de Zuazo (xabier@onddo.com)
-# Copyright:: Copyright 2013-2016, Onddo Labs, SL.
-# 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 "spec_helper"
-
-require "chef/cookbook_site_streaming_uploader"
-
-class FakeTempfile
- def initialize(basename)
- @basename = basename
- end
-
- def close; end
-
- def path
- "#{@basename}.ZZZ"
- end
-
-end
-
-describe Chef::CookbookSiteStreamingUploader do
-
- describe "create_build_dir" do
-
- before(:each) do
- @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
- @loader = Chef::CookbookLoader.new(@cookbook_repo)
- @loader.load_cookbooks
- allow(File).to receive(:unlink)
- end
-
- it "should create the cookbook tmp dir" do
- cookbook = @loader[:openldap]
- files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, "**", "*"), File::FNM_DOTMATCH).count { |file| File.file?(file) }
-
- expect(Tempfile).to receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build"))
- expect(FileUtils).to receive(:mkdir_p).exactly(files_count + 1).times
- expect(FileUtils).to receive(:cp).exactly(files_count).times
- Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
- end
-
- end # create_build_dir
-
- describe "make_request" do
-
- before(:each) do
- @uri = "http://cookbooks.dummy.com/api/v1/cookbooks"
- @secret_filename = File.join(CHEF_SPEC_DATA, "ssl/private_key.pem")
- @rsa_key = File.read(@secret_filename)
- response = Net::HTTPResponse.new("1.0", "200", "OK")
- allow_any_instance_of(Net::HTTP).to receive(:request).and_return(response)
- end
-
- it "should send an http request" do
- expect_any_instance_of(Net::HTTP).to receive(:request)
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename)
- end
-
- it "should read the private key file" do
- expect(File).to receive(:read).with(@secret_filename).and_return(@rsa_key)
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename)
- end
-
- it "should add the authentication signed header" do
- expect_any_instance_of(Mixlib::Authentication::SigningObject).to receive(:sign).and_return({})
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename)
- end
-
- it "should be able to send post requests" do
- post = Net::HTTP::Post.new(@uri, {})
-
- expect(Net::HTTP::Post).to receive(:new).once.and_return(post)
- expect(Net::HTTP::Put).not_to receive(:new)
- expect(Net::HTTP::Get).not_to receive(:new)
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename)
- end
-
- it "should be able to send put requests" do
- put = Net::HTTP::Put.new(@uri, {})
-
- expect(Net::HTTP::Post).not_to receive(:new)
- expect(Net::HTTP::Put).to receive(:new).once.and_return(put)
- expect(Net::HTTP::Get).not_to receive(:new)
- Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename)
- end
-
- it "should be able to receive files to attach as argument" do
- Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, {
- myfile: File.new(File.join(CHEF_SPEC_DATA, "config.rb")), # a dummy file
- })
- end
-
- it "should be able to receive strings to attach as argument" do
- Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, {
- mystring: "Lorem ipsum",
- })
- end
-
- it "should be able to receive strings and files as argument at the same time" do
- Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, {
- myfile1: File.new(File.join(CHEF_SPEC_DATA, "config.rb")),
- mystring1: "Lorem ipsum",
- myfile2: File.new(File.join(CHEF_SPEC_DATA, "config.rb")),
- mystring2: "Dummy text",
- })
- end
-
- end # make_request
-
- describe "StreamPart" do
- before(:each) do
- @file = File.new(File.join(CHEF_SPEC_DATA, "config.rb"))
- @stream_part = Chef::CookbookSiteStreamingUploader::StreamPart.new(@file, File.size(@file))
- end
-
- it "should create a StreamPart" do
- expect(@stream_part).to be_instance_of(Chef::CookbookSiteStreamingUploader::StreamPart)
- end
-
- it "should expose its size" do
- expect(@stream_part.size).to eql(File.size(@file))
- end
-
- it "should read with offset and how_much" do
- content = @file.read(4)
- @file.rewind
- expect(@stream_part.read(0, 4)).to eql(content)
- end
-
- end # StreamPart
-
- describe "StringPart" do
- before(:each) do
- @str = "What a boring string"
- @string_part = Chef::CookbookSiteStreamingUploader::StringPart.new(@str)
- end
-
- it "should create a StringPart" do
- expect(@string_part).to be_instance_of(Chef::CookbookSiteStreamingUploader::StringPart)
- end
-
- it "should expose its size" do
- expect(@string_part.size).to eql(@str.size)
- end
-
- it "should read with offset and how_much" do
- expect(@string_part.read(2, 4)).to eql(@str[2, 4])
- end
-
- end # StringPart
-
- describe "MultipartStream" do
- before(:each) do
- @string1 = "stream1"
- @string2 = "stream2"
- @stream1 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string1)
- @stream2 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string2)
- @parts = [ @stream1, @stream2 ]
-
- @multipart_stream = Chef::CookbookSiteStreamingUploader::MultipartStream.new(@parts)
- end
-
- it "should create a MultipartStream" do
- expect(@multipart_stream).to be_instance_of(Chef::CookbookSiteStreamingUploader::MultipartStream)
- end
-
- it "should expose its size" do
- expect(@multipart_stream.size).to eql(@stream1.size + @stream2.size)
- end
-
- it "should read with how_much" do
- expect(@multipart_stream.read(10)).to eql("#{@string1}#{@string2}"[0, 10])
- end
-
- it "should read receiving destination buffer as second argument (CHEF-4456: Ruby 2 compat)" do
- dst_buf = ""
- @multipart_stream.read(10, dst_buf)
- expect(dst_buf).to eql("#{@string1}#{@string2}"[0, 10])
- end
-
- end # MultipartStream
-
-end
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 01345e32e7..4a964f74de 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -41,7 +41,59 @@ describe Chef::CookbookVersion do
it "has empty metadata" do
expect(cookbook_version.metadata).to eq(Chef::Cookbook::Metadata.new)
end
+ end
+
+ describe "#recipe_yml_filenames_by_name" do
+ let(:cookbook_version) { Chef::CookbookVersion.new("mycb", "/tmp/mycb") }
+
+ def files_for_recipe(extension)
+ [
+ { name: "recipes/default.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipes/default.#{extension}" },
+ { name: "recipes/other.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipes/other.#{extension}" },
+ ]
+ end
+ context "and YAML files present include both a recipes/default.yml and a recipes/default.yaml" do
+ before(:each) do
+ allow(cookbook_version).to receive(:files_for).with("recipes").and_return(
+ [
+ { name: "recipes/default.yml", full_path: "/home/user/repo/cookbooks/test/recipes/default.yml" },
+ { name: "recipes/default.yaml", full_path: "/home/user/repo/cookbooks/test/recipes/default.yaml" },
+ ]
+ )
+ end
+ it "because both are valid and we can't pick, it raises an error that contains the info needed to fix the problem" do
+ expect { cookbook_version.recipe_yml_filenames_by_name }
+ .to raise_error(Chef::Exceptions::AmbiguousYAMLFile, /.*default.yml.*default.yaml.*update the cookbook to remove/)
+ end
+ end
+
+ %w{yml yaml}.each do |extension|
+
+ context "and YAML files are present including a recipes/default.#{extension}" do
+ before(:each) do
+ allow(cookbook_version).to receive(:files_for).with("recipes").and_return(files_for_recipe(extension))
+ end
+
+ context "and manifest does not include a root_files/recipe.#{extension}" do
+ it "returns all YAML recipes with a correct default of default.#{extension}" do
+ expect(cookbook_version.recipe_yml_filenames_by_name).to eq({ "default" => "/home/user/repo/cookbooks/test/recipes/default.#{extension}",
+ "other" => "/home/user/repo/cookbooks/test/recipes/other.#{extension}" })
+ end
+ end
+
+ context "and manifest also includes a root_files/recipe.#{extension}" do
+ let(:root_files) { [{ name: "root_files/recipe.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipe.#{extension}" } ] }
+ before(:each) do
+ allow(cookbook_version.cookbook_manifest).to receive(:root_files).and_return(root_files)
+ end
+ it "returns all YAML recipes with a correct default of recipe.#{extension}" do
+ expect(cookbook_version.recipe_yml_filenames_by_name).to eq({ "default" => "/home/user/repo/cookbooks/test/recipe.#{extension}",
+ "other" => "/home/user/repo/cookbooks/test/recipes/other.#{extension}" })
+ end
+ end
+ end
+ end
end
describe "with a cookbook directory named tatft" do
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
index 9a12804443..2487bd25d7 100644
--- a/spec/unit/data_bag_item_spec.rb
+++ b/spec/unit/data_bag_item_spec.rb
@@ -73,11 +73,11 @@ describe Chef::DataBagItem do
end
it "should accept alphanum.alphanum for the id" do
- expect { data_bag_item.raw_data = { "id" => "foo.bar" } }.not_to raise_error
+ expect { data_bag_item.raw_data = { "id" => "foo.bar" } }.to raise_error(ArgumentError)
end
it "should accept .alphanum for the id" do
- expect { data_bag_item.raw_data = { "id" => ".bozo" } }.not_to raise_error
+ expect { data_bag_item.raw_data = { "id" => ".bozo" } }.to raise_error(ArgumentError)
end
it "should raise an exception if the id contains anything but alphanum/-/_" do
@@ -148,12 +148,7 @@ describe Chef::DataBagItem do
end
it "implements all the methods of Hash" do
- methods = %i{rehash to_hash [] fetch []= store default
- default= default_proc index size length
- empty? each_value each_key each_pair each keys values
- values_at delete delete_if reject! clear
- invert update replace merge! merge has_key? has_value?
- key? value?}
+ methods = Hash.public_instance_methods
methods.each do |m|
expect(data_bag_item).to respond_to(m)
end
diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb
index 2a7ddddd5b..891f9c6616 100644
--- a/spec/unit/data_bag_spec.rb
+++ b/spec/unit/data_bag_spec.rb
@@ -49,7 +49,7 @@ describe Chef::DataBag do
expect { @data_bag.name({}) }.to raise_error(ArgumentError)
end
- [ ".", "-", "_", "1"].each do |char|
+ ["-", "_", "1"].each do |char|
it "should allow a '#{char}' character in the data bag name" do
expect(@data_bag.name("clown#{char}clown")).to eq("clown#{char}clown")
end
diff --git a/spec/unit/data_collector_spec.rb b/spec/unit/data_collector_spec.rb
index 63531663ee..24f8807d2e 100644
--- a/spec/unit/data_collector_spec.rb
+++ b/spec/unit/data_collector_spec.rb
@@ -142,11 +142,17 @@ describe Chef::DataCollector do
def expect_converge_message(keys)
keys["message_type"] = "run_converge"
keys["message_version"] = "1.1.0"
+ # if (keys.key?("node") && !keys["node"].empty?)
+ # expect(rest_client).to receive(:post) do |_a, hash, _b|
+ # require 'pry'; binding.pry
+ # end
+ # else
expect(rest_client).to receive(:post).with(
nil,
hash_including(keys),
{ "Content-Type" => "application/json" }
)
+ # end
end
def resource_has_diff(new_resource, status)
@@ -202,7 +208,7 @@ describe Chef::DataCollector do
end
it "has a node" do
- expect_converge_message("node" => expected_node)
+ expect_converge_message("node" => expected_node.is_a?(Chef::Node) ? expected_node.data_for_save : expected_node)
send_run_failed_or_completed_event
end
@@ -808,6 +814,46 @@ describe Chef::DataCollector do
it_behaves_like "sends a converge message"
end
+ context "when node attributes are block-listed" do
+ let(:status) { "success" }
+ before do
+ Chef::Config[:blocked_default_attributes] = [
+ %w{secret key_to_the_kingdom},
+ ]
+ node.default = {
+ "secret" => { "key_to_the_kingdom" => "under the flower pot to the left of the drawbridge" },
+ "publicinfo" => { "num_flower_pots" => 18 },
+ }
+ end
+
+ it "payload should exclude blocked attributes" do
+ expect(rest_client).to receive(:post) do |_addr, hash, _headers|
+ expect(hash["node"]["default"]).to eq({ "secret" => {}, "publicinfo" => { "num_flower_pots" => 18 } })
+ end
+ send_run_failed_or_completed_event
+ end
+ end
+
+ context "when node attributes are allow-listed" do
+ let(:status) { "success" }
+ before do
+ Chef::Config[:allowed_default_attributes] = [
+ %w{public entrance},
+ ]
+ node.default = {
+ "public" => { "entrance" => "is the drawbridge" },
+ "secret" => { "entrance" => "is the tunnel" },
+ }
+ end
+
+ it "payload should include only allowed attributes" do
+ expect(rest_client).to receive(:post) do |_addr, hash, _headers|
+ expect(hash["node"]["default"]).to eq({ "public" => { "entrance" => "is the drawbridge" } })
+ end
+ send_run_failed_or_completed_event
+ end
+ end
+
end
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
index 4fed3be442..90945c3b80 100644
--- a/spec/unit/dsl/reboot_pending_spec.rb
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -51,18 +51,18 @@ describe Chef::DSL::RebootPending do
end
end
- context "platform is ubuntu" do
+ context "platform_family is debian" do
before do
- allow(recipe).to receive(:platform?).with("ubuntu").and_return(true)
+ allow(recipe).to receive(:platform_family?).with("debian").and_return(true)
end
it "should return true if /var/run/reboot-required exists" do
- allow(File).to receive(:exists?).with("/var/run/reboot-required").and_return(true)
+ allow(File).to receive(:exist?).with("/var/run/reboot-required").and_return(true)
expect(recipe.reboot_pending?).to be_truthy
end
it "should return false if /var/run/reboot-required does not exist" do
- allow(File).to receive(:exists?).with("/var/run/reboot-required").and_return(false)
+ allow(File).to receive(:exist?).with("/var/run/reboot-required").and_return(false)
expect(recipe.reboot_pending?).to be_falsey
end
end
diff --git a/spec/unit/dsl/registry_helper_spec.rb b/spec/unit/dsl/registry_helper_spec.rb
index 34a8c02039..5ef8caa053 100644
--- a/spec/unit/dsl/registry_helper_spec.rb
+++ b/spec/unit/dsl/registry_helper_spec.rb
@@ -21,7 +21,7 @@ require "spec_helper"
describe Chef::Resource::RegistryKey do
- before (:all) do
+ before(:all) do
events = Chef::EventDispatch::Dispatcher.new
node = Chef::Node.new
node.consume_external_attrs(OHAI_SYSTEM.data, {})
diff --git a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
index 9d8fb050da..6693eded03 100644
--- a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
@@ -115,7 +115,7 @@ describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do
# fake code to run through #recipe_snippet
source_file = [ "if true", "var = non_existent", "end" ]
allow(IO).to receive(:readlines).and_return(source_file)
- allow(File).to receive(:exists?).and_return(true)
+ allow(File).to receive(:exist?).and_return(true)
end
it "parses a Windows path" do
@@ -141,7 +141,7 @@ describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do
context "when the recipe file does not exist" do
before do
- allow(File).to receive(:exists?).and_return(false)
+ allow(File).to receive(:exist?).and_return(false)
allow(IO).to receive(:readlines).and_raise(Errno::ENOENT)
end
diff --git a/spec/unit/handler_spec.rb b/spec/unit/handler_spec.rb
index a9820e8a70..ee30113d1c 100644
--- a/spec/unit/handler_spec.rb
+++ b/spec/unit/handler_spec.rb
@@ -38,6 +38,8 @@ describe Chef::Handler do
@run_context = Chef::RunContext.new(@node, {}, @events)
@all_resources = [Chef::Resource::Cat.new("lolz"), Chef::Resource::ZenMaster.new("tzu")]
@all_resources.first.updated_by_last_action true
+ @handler.instance_variable_set(:@all_resources, @all_resources)
+ @handler.instance_variable_set(:@updated_resources, [@all_resources.first])
@run_context.resource_collection.all_resources.replace(@all_resources)
@run_status.run_context = @run_context
@start_time = Time.now
@@ -119,6 +121,8 @@ describe Chef::Handler do
@run_context = Chef::RunContext.new(@node, {}, @events)
@all_resources = [Chef::Resource::Cat.new("foo"), Chef::Resource::ZenMaster.new("moo")]
@all_resources.first.updated_by_last_action true
+ @handler.instance_variable_set(:@all_resources, @all_resources)
+ @handler.instance_variable_set(:@updated_resources, [@all_resources.first])
@run_context.resource_collection.all_resources.replace(@all_resources)
@run_status.run_context = @run_context
@start_time = Time.now
@@ -169,17 +173,19 @@ describe Chef::Handler do
# and this would test the start handler
describe "when running a start handler" do
before do
+ @handler.instance_variable_set(:@all_resources, [])
+ @handler.instance_variable_set(:@updated_resources, [])
@start_time = Time.now
allow(Time).to receive(:now).and_return(@start_time)
@run_status.start_clock
end
it "should not have all resources" do
- expect(@handler.all_resources).to be_falsey
+ expect(@handler.all_resources).to be_empty
end
it "should not have updated resources" do
- expect(@handler.updated_resources).to be_falsey
+ expect(@handler.updated_resources).to be_empty
end
it "has a shortcut for the start time" do
diff --git a/spec/unit/http/ssl_policies_spec.rb b/spec/unit/http/ssl_policies_spec.rb
index 614b5018d1..6fc00b5fd9 100644
--- a/spec/unit/http/ssl_policies_spec.rb
+++ b/spec/unit/http/ssl_policies_spec.rb
@@ -29,91 +29,83 @@ describe "HTTP SSL Policy" do
ENV["SSL_CERT_FILE"] = nil
end
- let(:unconfigured_http_client) { Net::HTTP.new("example.com", 443) }
let(:http_client) do
- unconfigured_http_client.use_ssl = true
- ssl_policy.apply
- unconfigured_http_client
+ ssl_policy_class.apply_to(Net::HTTP.new("example.com"))
end
describe Chef::HTTP::DefaultSSLPolicy do
- let(:ssl_policy) { Chef::HTTP::DefaultSSLPolicy.new(unconfigured_http_client) }
+ let(:ssl_policy_class) { Chef::HTTP::DefaultSSLPolicy }
- describe "when configured with :ssl_verify_mode set to :verify peer" do
- before do
- Chef::Config[:ssl_verify_mode] = :verify_peer
- end
-
- it "configures the HTTP client to use SSL when given a URL with the https protocol" do
- expect(http_client.use_ssl?).to be_truthy
- end
-
- it "sets the OpenSSL verify mode to verify_peer" do
- expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
- end
-
- it "raises a ConfigurationError if :ssl_ca_path is set to a path that doesn't exist" do
- Chef::Config[:ssl_ca_path] = "/dev/null/nothing_here"
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
- end
+ it "raises a ConfigurationError if :ssl_ca_path is set to a path that doesn't exist" do
+ Chef::Config[:ssl_ca_path] = "/dev/null/nothing_here"
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
+ end
- it "should set the CA path if that is set in the configuration" do
- Chef::Config[:ssl_ca_path] = File.join(CHEF_SPEC_DATA, "ssl")
- expect(http_client.ca_path).to eq(File.join(CHEF_SPEC_DATA, "ssl"))
- end
+ it "should set the CA path if that is set in the configuration" do
+ Chef::Config[:ssl_ca_path] = File.join(CHEF_SPEC_DATA, "ssl")
+ expect(http_client.ca_path).to eq(File.join(CHEF_SPEC_DATA, "ssl"))
+ end
- it "raises a ConfigurationError if :ssl_ca_file is set to a file that does not exist" do
- Chef::Config[:ssl_ca_file] = "/dev/null/nothing_here"
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
- end
+ it "raises a ConfigurationError if :ssl_ca_file is set to a file that does not exist" do
+ Chef::Config[:ssl_ca_file] = "/dev/null/nothing_here"
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
+ end
- it "should set the CA file if that is set in the configuration" do
- Chef::Config[:ssl_ca_file] = CHEF_SPEC_DATA + "/ssl/5e707473.0"
- expect(http_client.ca_file).to eq(CHEF_SPEC_DATA + "/ssl/5e707473.0")
- end
+ it "should set the CA file if that is set in the configuration" do
+ Chef::Config[:ssl_ca_file] = CHEF_SPEC_DATA + "/ssl/5e707473.0"
+ expect(http_client.ca_file).to eq(CHEF_SPEC_DATA + "/ssl/5e707473.0")
+ end
- it "should set the custom CA file if SSL_CERT_FILE environment variable is set" do
- ENV["SSL_CERT_FILE"] = CHEF_SPEC_DATA + "/trusted_certs/intermediate.pem"
- expect(http_client.ca_file).to eq(CHEF_SPEC_DATA + "/trusted_certs/intermediate.pem")
- end
+ it "should set the custom CA file if SSL_CERT_FILE environment variable is set" do
+ ENV["SSL_CERT_FILE"] = CHEF_SPEC_DATA + "/trusted_certs/intermediate.pem"
+ expect(http_client.ca_file).to eq(CHEF_SPEC_DATA + "/trusted_certs/intermediate.pem")
+ end
- it "raises a ConfigurationError if SSL_CERT_FILE environment variable is set to a file that does not exist" do
- ENV["SSL_CERT_FILE"] = "/dev/null/nothing_here"
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
- end
+ it "raises a ConfigurationError if SSL_CERT_FILE environment variable is set to a file that does not exist" do
+ ENV["SSL_CERT_FILE"] = "/dev/null/nothing_here"
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
end
- describe "when configured with :ssl_verify_mode set to :verify peer" do
- before do
- @url = URI.parse("https://chef.example.com:4443/")
- Chef::Config[:ssl_verify_mode] = :verify_none
- end
+ it "sets the OpenSSL verify mode to verify_peer when configured with :ssl_verify_mode set to :verify_peer" do
+ Chef::Config[:ssl_verify_mode] = :verify_peer
+ expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
+ end
- it "sets the OpenSSL verify mode to :verify_none" do
- expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
- end
+ it "sets the OpenSSL verify mode to :verify_none when configured with :ssl_verify_mode set to :verify_none" do
+ Chef::Config[:ssl_verify_mode] = :verify_none
+ expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
end
describe "when configured with a client certificate" do
- before { @url = URI.parse("https://chef.example.com:4443/") }
-
it "raises ConfigurationError if the certificate file doesn't exist" do
Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here"
Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/chef-rspec.key"
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError, /ssl_client_cert .* does not exist/)
end
- it "raises ConfigurationError if the certificate file doesn't exist" do
+ it "raises ConfigurationError if the private key file doesn't exist" do
Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/chef-rspec.cert"
Chef::Config[:ssl_client_key] = "/dev/null/nothing_here"
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError, /ssl_client_key .* does not exist/)
end
it "raises a ConfigurationError if one of :ssl_client_cert and :ssl_client_key is set but not both" do
Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here"
Chef::Config[:ssl_client_key] = nil
- expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError, /configure ssl_client_cert and ssl_client_key together/)
+ end
+
+ it "raises a ConfigurationError with a bad cert file" do
+ Chef::Config[:ssl_client_cert] = __FILE__
+ Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/chef-rspec.key"
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError, /Error reading cert file '#{__FILE__}'/)
+ end
+
+ it "raises a ConfigurationError with a bad key file" do
+ Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/chef-rspec.cert"
+ Chef::Config[:ssl_client_key] = __FILE__
+ expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError, /Error reading key file '#{__FILE__}'/)
end
it "configures the HTTP client's cert and private key" do
@@ -122,20 +114,31 @@ describe "HTTP SSL Policy" do
expect(http_client.cert.to_s).to eq(OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.cert")).to_s)
expect(http_client.key.to_s).to eq(OpenSSL::PKey::RSA.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.key")).to_s)
end
- end
- context "when additional certs are located in the trusted_certs dir" do
- let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
- let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+ it "configures the HTTP client's cert and private key with a DER encoded cert" do
+ Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/binary/chef-rspec-der.cert"
+ Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/chef-rspec.key"
+ expect(http_client.cert.to_s).to eq(OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.cert")).to_s)
+ expect(http_client.key.to_s).to eq(OpenSSL::PKey::RSA.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.key")).to_s)
+ end
- let(:additional_pem_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "opscode.pem") }
- let(:additional_pem) { OpenSSL::X509::Certificate.new(File.read(additional_pem_path)) }
+ it "configures the HTTP client's cert and private key with a DER encoded key" do
+ Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/chef-rspec.cert"
+ Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/binary/chef-rspec-der.key"
+ expect(http_client.cert.to_s).to eq(OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.cert")).to_s)
+ expect(http_client.key.to_s).to eq(OpenSSL::PKey::RSA.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.key")).to_s)
+ end
+ end
+ context "when additional certs are located in the trusted_certs dir" do
before do
Chef::Config.trusted_certs_dir = File.join(CHEF_SPEC_DATA, "trusted_certs")
end
it "enables verification of self-signed certificates" do
+ path = File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt")
+ self_signed_crt = OpenSSL::X509::Certificate.new(File.binread(path))
+
expect(http_client.cert_store.verify(self_signed_crt)).to be_truthy
end
@@ -148,39 +151,64 @@ describe "HTTP SSL Policy" do
# If the machine running the test doesn't have ruby SSL configured correctly,
# then the root cert also has to be loaded for the test to succeed.
# The system under test **SHOULD** do both of these things.
+ path = File.join(CHEF_SPEC_DATA, "trusted_certs", "opscode.pem")
+ additional_pem = OpenSSL::X509::Certificate.new(File.binread(path))
+
expect(http_client.cert_store.verify(additional_pem)).to be_truthy
end
- context "and some certs are duplicates" do
- it "skips duplicate certs" do
- # For whatever reason, OpenSSL errors out when adding a
- # cert you already have to the certificate store.
- ssl_policy.set_custom_certs
- ssl_policy.set_custom_certs # should not raise an error
+ it "skips duplicate certs" do
+ # For whatever reason, OpenSSL errors out when adding a
+ # cert you already have to the certificate store.
+ ssl_policy = ssl_policy_class.new(Net::HTTP.new("example.com"))
+ ssl_policy.set_custom_certs
+ ssl_policy.set_custom_certs # should not raise an error
+ end
+
+ it "raises ConfigurationError with a bad cert file in the trusted_certs dir" do
+ ssl_policy = ssl_policy_class.new(Net::HTTP.new("example.com"))
+
+ Dir.mktmpdir do |dir|
+ bad_cert_file = File.join(dir, "bad_cert_file.crt")
+ File.write(bad_cert_file, File.read(__FILE__))
+
+ Chef::Config.trusted_certs_dir = dir
+ expect { ssl_policy.set_custom_certs }.to raise_error(Chef::Exceptions::ConfigurationError, /Error reading cert file/)
end
end
+
+ it "works with binary certs" do
+ Chef::Config.trusted_certs_dir = File.join(CHEF_SPEC_DATA, "ssl", "binary")
+
+ ssl_policy = ssl_policy_class.new(Net::HTTP.new("example.com"))
+ ssl_policy.set_custom_certs
+ end
end
end
describe Chef::HTTP::APISSLPolicy do
- let(:ssl_policy) { Chef::HTTP::APISSLPolicy.new(unconfigured_http_client) }
+ let(:ssl_policy_class) { Chef::HTTP::APISSLPolicy }
- context "when verify_api_cert is set" do
- before do
- Chef::Config[:verify_api_cert] = true
- end
+ it "sets the OpenSSL verify mode to verify_peer when configured with :ssl_verify_mode set to :verify_peer" do
+ Chef::Config[:ssl_verify_mode] = :verify_peer
+ expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
+ end
- it "sets the OpenSSL verify mode to verify_peer" do
- expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
- end
+ it "sets the OpenSSL verify mode to :verify_none when configured with :ssl_verify_mode set to :verify_none" do
+ Chef::Config[:ssl_verify_mode] = :verify_none
+ expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
end
+ it "sets the OpenSSL verify mode to verify_peer when verify_api_cert is set" do
+ Chef::Config[:verify_api_cert] = true
+ expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
+ end
end
describe Chef::HTTP::VerifyPeerSSLPolicy do
- let(:ssl_policy) { Chef::HTTP::VerifyPeerSSLPolicy.new(unconfigured_http_client) }
+ let(:ssl_policy_class) { Chef::HTTP::VerifyPeerSSLPolicy }
it "sets the OpenSSL verify mode to verify_peer" do
expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
@@ -190,7 +218,7 @@ describe "HTTP SSL Policy" do
describe Chef::HTTP::VerifyNoneSSLPolicy do
- let(:ssl_policy) { Chef::HTTP::VerifyNoneSSLPolicy.new(unconfigured_http_client) }
+ let(:ssl_policy_class) { Chef::HTTP::VerifyNoneSSLPolicy }
it "sets the OpenSSL verify mode to verify_peer" do
expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
diff --git a/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb b/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
deleted file mode 100644
index 4d36208be0..0000000000
--- a/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# Author:: Lamont Granquist <lamont@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::Bootstrap::ChefVaultHandler do
-
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
- let(:stdin) { StringIO.new }
- let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
-
- let(:config) { {} }
-
- let(:client) { Chef::ApiClient.new }
-
- let(:chef_vault_handler) do
- chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(config: config, ui: ui)
- chef_vault_handler
- end
-
- context "when there's no vault option" do
- it "should report its not doing anything" do
- expect(chef_vault_handler.doing_chef_vault?).to be false
- end
-
- it "shouldn't do anything" do
- expect(chef_vault_handler).to_not receive(:sanity_check)
- expect(chef_vault_handler).to_not receive(:update_bootstrap_vault_json!)
- chef_vault_handler
- end
- end
-
- context "when setting chef vault items" do
- let(:bootstrap_vault_item) { double("ChefVault::Item") }
-
- before do
- expect(chef_vault_handler).to receive(:require_chef_vault!).at_least(:once)
- expect(bootstrap_vault_item).to receive(:clients).with(client).at_least(:once)
- expect(bootstrap_vault_item).to receive(:save).at_least(:once)
- end
-
- context "from config[:bootstrap_vault_item]" do
- it "sets a single item as a scalar" do
- config[:bootstrap_vault_item] = { "vault" => "item1" }
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets a single item as an array" do
- config[:bootstrap_vault_item] = { "vault" => [ "item1" ] }
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two items as an array" do
- config[:bootstrap_vault_item] = { "vault" => %w{item1 item2} }
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two vaults from different hash keys" do
- config[:bootstrap_vault_item] = { "vault" => %w{item1 item2}, "vault2" => [ "item3" ] }
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
- end
-
- context "from config[:bootstrap_vault_json]" do
- it "sets a single item as a scalar" do
- config[:bootstrap_vault_json] = '{ "vault": "item1" }'
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets a single item as an array" do
- config[:bootstrap_vault_json] = '{ "vault": [ "item1" ] }'
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two items as an array" do
- config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ] }'
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two vaults from different hash keys" do
- config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }'
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
- end
-
- context "from config[:bootstrap_vault_file]" do
-
- def setup_file_contents(json)
- stringio = StringIO.new(json)
- config[:bootstrap_vault_file] = "/foo/bar/baz"
- expect(File).to receive(:read).with(config[:bootstrap_vault_file]).and_return(stringio)
- end
-
- it "sets a single item as a scalar" do
- setup_file_contents('{ "vault": "item1" }')
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets a single item as an array" do
- setup_file_contents('{ "vault": [ "item1" ] }')
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two items as an array" do
- setup_file_contents('{ "vault": [ "item1", "item2" ] }')
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
-
- it "sets two vaults from different hash keys" do
- setup_file_contents('{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }')
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item)
- expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item)
- chef_vault_handler.run(client)
- end
- end
- end
-end
diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb
deleted file mode 100644
index 10edd13882..0000000000
--- a/spec/unit/knife/bootstrap/client_builder_spec.rb
+++ /dev/null
@@ -1,207 +0,0 @@
-#
-# Author:: Lamont Granquist <lamont@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::Bootstrap::ClientBuilder do
-
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
- let(:stdin) { StringIO.new }
- let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
-
- let(:config) { {} }
-
- let(:chef_config) { {} }
-
- let(:node_name) { "bevell.wat" }
-
- let(:rest) { double("Chef::ServerAPI") }
-
- let(:client_builder) do
- client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(config: config, chef_config: chef_config, ui: ui)
- allow(client_builder).to receive(:rest).and_return(rest)
- allow(client_builder).to receive(:node_name).and_return(node_name)
- client_builder
- end
-
- context "#sanity_check!" do
- let(:response_404) { OpenStruct.new(code: "404") }
- let(:exception_404) { Net::HTTPClientException.new("404 not found", response_404) }
-
- context "in cases where the prompting fails" do
- before do
- # should fail early in #run
- expect(client_builder).to_not receive(:create_client!)
- expect(client_builder).to_not receive(:create_node!)
- end
-
- it "exits when the node exists and the user does not want to delete" do
- expect(rest).to receive(:get).with("nodes/#{node_name}")
- expect(ui.stdin).to receive(:readline).and_return("n")
- expect { client_builder.run }.to raise_error(SystemExit)
- end
-
- it "exits when the client exists and the user does not want to delete" do
- expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
- expect(rest).to receive(:get).with("clients/#{node_name}")
- expect(ui.stdin).to receive(:readline).and_return("n")
- expect { client_builder.run }.to raise_error(SystemExit)
- end
- end
-
- context "in cases where the prompting succeeds" do
- before do
- # mock out the rest of #run
- expect(client_builder).to receive(:create_client!)
- expect(client_builder).to receive(:create_node!)
- end
-
- it "when both the client and node do not exist it succeeds" do
- expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
- expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404)
- expect { client_builder.run }.not_to raise_error
- end
-
- it "when we are allowed to delete an old node" do
- expect(rest).to receive(:get).with("nodes/#{node_name}")
- expect(ui.stdin).to receive(:readline).and_return("y")
- expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404)
- expect(rest).to receive(:delete).with("nodes/#{node_name}")
- expect { client_builder.run }.not_to raise_error
- end
-
- it "when we are allowed to delete an old client" do
- expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404)
- expect(rest).to receive(:get).with("clients/#{node_name}")
- expect(ui.stdin).to receive(:readline).and_return("y")
- expect(rest).to receive(:delete).with("clients/#{node_name}")
- expect { client_builder.run }.not_to raise_error
- end
-
- it "when we are are allowed to delete both an old client and node" do
- expect(rest).to receive(:get).with("nodes/#{node_name}")
- expect(rest).to receive(:get).with("clients/#{node_name}")
- expect(ui.stdin).to receive(:readline).twice.and_return("y")
- expect(rest).to receive(:delete).with("nodes/#{node_name}")
- expect(rest).to receive(:delete).with("clients/#{node_name}")
- expect { client_builder.run }.not_to raise_error
- end
- end
- end
-
- context "#create_client!" do
- let(:client) { Chef::ApiClient.new }
-
- before do
- # mock out the rest of #run
- expect(client_builder).to receive(:sanity_check)
- expect(client_builder).to receive(:create_node!)
- end
-
- it "delegates everything to Chef::ApiClient::Registration and sets client" do
- reg_double = double("Chef::ApiClient::Registration")
- expect(Chef::ApiClient::Registration).to receive(:new).with(node_name, client_builder.client_path, http_api: rest).and_return(reg_double)
- expect(reg_double).to receive(:run).and_return(client)
- client_builder.run
- expect(client_builder.client).to eq(client)
- end
-
- end
-
- context "#client_path" do
- it "has a public API for the temporary client.pem file" do
- expect(client_builder.client_path).to match(/#{node_name}.pem/)
- end
- end
-
- context "#create_node!" do
- before do
- # mock out the rest of #run
- expect(client_builder).to receive(:sanity_check)
- expect(client_builder).to receive(:create_client!)
- # mock out default node building steps
- expect(client_builder).to receive(:client_rest).and_return(client_rest)
- expect(Chef::Node).to receive(:new).with(chef_server_rest: client_rest).and_return(node)
- expect(node).to receive(:name).with(node_name)
- expect(node).to receive(:save)
- end
-
- let(:client_rest) { double("Chef::ServerAPI (client)") }
-
- let(:node) { double("Chef::Node") }
-
- it "builds a node with a default run_list of []" do
- expect(node).to receive(:run_list).with([])
- client_builder.run
- end
-
- it "does not add tags by default" do
- allow(node).to receive(:run_list).with([])
- expect(node).to_not receive(:tags)
- client_builder.run
- end
-
- it "adds tags to the node when given" do
- tag_receiver = []
-
- config[:tags] = %w{foo bar}
- allow(node).to receive(:run_list).with([])
- allow(node).to receive(:tags).and_return(tag_receiver)
- client_builder.run
- expect(tag_receiver).to eq %w{foo bar}
- end
-
- it "builds a node when the run_list is a string" do
- config[:run_list] = "role[base],role[app]"
- expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
- client_builder.run
- end
-
- it "builds a node when the run_list is an Array" do
- config[:run_list] = ["role[base]", "role[app]"]
- expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
- client_builder.run
- end
-
- it "builds a node with first_boot_attributes if they're given" do
- config[:first_boot_attributes] = { baz: :quux }
- expect(node).to receive(:normal_attrs=).with({ baz: :quux })
- expect(node).to receive(:run_list).with([])
- client_builder.run
- end
-
- it "builds a node with an environment if its given" do
- config[:environment] = "production"
- expect(node).to receive(:environment).with("production")
- expect(node).to receive(:run_list).with([])
- client_builder.run
- end
-
- it "builds a node with policy_name and policy_group when given" do
- config[:policy_name] = "my-app"
- config[:policy_group] = "staging"
-
- expect(node).to receive(:run_list).with([])
- expect(node).to receive(:policy_name=).with("my-app")
- expect(node).to receive(:policy_group=).with("staging")
-
- client_builder.run
- end
- end
-end
diff --git a/spec/unit/knife/bootstrap/train_connector_spec.rb b/spec/unit/knife/bootstrap/train_connector_spec.rb
deleted file mode 100644
index 4c384100fa..0000000000
--- a/spec/unit/knife/bootstrap/train_connector_spec.rb
+++ /dev/null
@@ -1,244 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "ostruct"
-require "chef/knife/bootstrap/train_connector"
-
-describe Chef::Knife::Bootstrap::TrainConnector do
- let(:protocol) { "mock" }
- let(:family) { "unknown" }
- let(:release) { "unknown" } # version
- let(:name) { "unknown" }
- let(:arch) { "x86_64" }
- let(:connection_opts) { {} } # connection opts
- let(:host_url) { "mock://user1@example.com" }
- let(:mock_connection) { true }
-
- subject do
- # Example groups can still override by setting explicitly it in 'connection_opts'
- tc = Chef::Knife::Bootstrap::TrainConnector.new(host_url, protocol, connection_opts)
- tc
- end
-
- before(:each) do
- if mock_connection
- subject.connect!
- subject.connection.mock_os(
- family: family,
- name: name,
- release: release,
- arch: arch
- )
- end
- end
-
- describe "platform helpers" do
- context "on linux" do
- let(:family) { "debian" }
- let(:name) { "ubuntu" }
- it "reports that it is linux and unix, because that is how train classifies it" do
- expect(subject.unix?).to eq true
- expect(subject.linux?).to eq true
- expect(subject.windows?).to eq false
- end
- end
- context "on unix" do
- let(:family) { "os" }
- let(:name) { "mac_os_x" }
- it "reports only a unix OS" do
- expect(subject.unix?).to eq true
- expect(subject.linux?).to eq false
- expect(subject.windows?).to eq false
- end
- end
- context "on windows" do
- let(:family) { "windows" }
- let(:name) { "windows" }
- it "reports only a windows OS" do
- expect(subject.unix?).to eq false
- expect(subject.linux?).to eq false
- expect(subject.windows?).to eq true
- end
- end
- end
-
- describe "#connect!" do
- it "establishes the connection to the remote host by waiting for it" do
- expect(subject.connection).to receive(:wait_until_ready)
- subject.connect!
- end
- end
-
- describe "#initialize" do
- let(:mock_connection) { false }
-
- context "when provided target is a proper URL" do
- let(:protocol) { "ssh" }
- let(:host_url) { "mock://user1@localhost:2200" }
- it "correctly configures the instance from the URL" do
- expect(subject.config[:backend]).to eq "mock"
- expect(subject.config[:port]).to eq 2200
- expect(subject.config[:host]).to eq "localhost"
- expect(subject.config[:user]).to eq "user1"
- end
-
- context "and conflicting options are given" do
- let(:connection_opts) { { user: "user2", host: "example.com", port: 15 } }
- it "resolves them from the URI" do
- expect(subject.config[:backend]).to eq "mock"
- expect(subject.config[:port]).to eq 2200
- expect(subject.config[:host]).to eq "localhost"
- expect(subject.config[:user]).to eq "user1"
- end
- end
- end
-
- context "when provided target is just a hostname" do
- let(:host_url) { "localhost" }
- let(:protocol) { "mock" }
- it "correctly sets backend protocol from the default" do
- expect(subject.config[:backend]).to eq "mock"
- end
-
- context "and options have been provided that are supported by the transport" do
- let(:protocol) { "ssh" }
- let(:connection_opts) { { port: 15, user: "user2" } }
-
- it "sets hostname and transport from arguments and provided fields from options" do
- expect(subject.config[:backend]).to eq "ssh"
- expect(subject.config[:host]).to eq "localhost"
- expect(subject.config[:user]).to eq "user2"
- expect(subject.config[:port]).to eq 15
- end
-
- end
-
- end
-
- context "when provided target is just a an IP address" do
- let(:host_url) { "127.0.0.1" }
- let(:protocol) { "mock" }
- it "correctly sets backend protocol from the default" do
- expect(subject.config[:backend]).to eq "mock"
- end
- end
- end
-
- describe "#temp_dir" do
- context "under windows" do
- let(:family) { "windows" }
- let(:name) { "windows" }
-
- it "uses the windows command to create the temp dir" do
- expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND
- expect(subject).to receive(:run_command!).with(expected_command)
- .and_return double("result", stdout: "C:/a/path")
- expect(subject.temp_dir).to eq "C:/a/path"
- end
-
- end
- context "under linux and unix-like" do
- let(:family) { "debian" }
- let(:name) { "ubuntu" }
- let(:random) { "wScHX6" }
- let(:dir) { "/tmp/chef_#{random}" }
-
- before do
- allow(SecureRandom).to receive(:alphanumeric).with(6).and_return(random)
- end
-
- context "uses the *nix command to create the temp dir and sets ownership to logged-in" do
- it "with sudo privilege" do
- subject.config[:sudo] = true
- expected_command1 = "mkdir -p '#{dir}'"
- expected_command2 = "chown user1 '#{dir}'"
- expect(subject).to receive(:run_command!).with(expected_command1)
- .and_return double("result", stdout: "\r\n")
- expect(subject).to receive(:run_command!).with(expected_command2)
- .and_return double("result", stdout: "\r\n")
- expect(subject.temp_dir).to eq(dir)
- end
-
- it "without sudo privilege" do
- expected_command = "mkdir -p '#{dir}'"
- expect(subject).to receive(:run_command!).with(expected_command)
- .and_return double("result", stdout: "\r\n")
- expect(subject.temp_dir).to eq(dir)
- end
- end
-
- context "with noise in stderr" do
- it "uses the *nix command to create the temp dir" do
- expected_command = "mkdir -p '#{dir}'"
- expect(subject).to receive(:run_command!).with(expected_command)
- .and_return double("result", stdout: "sudo: unable to resolve host hostname.localhost\r\n" + "#{dir}\r\n")
- expect(subject.temp_dir).to eq(dir)
- end
- end
- end
- end
- context "#upload_file_content!" do
- it "creates a local file with expected content and uploads it" do
- expect(subject).to receive(:upload_file!) do |local_path, remote_path|
- expect(File.read(local_path)).to eq "test data"
- expect(remote_path).to eq "/target/path"
- end
- expect_any_instance_of(Tempfile).to receive(:binmode)
- subject.upload_file_content!("test data", "/target/path")
- end
- end
-
- context "del_file" do
- context "on windows" do
- let(:family) { "windows" }
- let(:name) { "windows" }
- it "deletes the file with a windows command" do
- expect(subject).to receive(:run_command!) do |cmd, &_handler|
- expect(cmd).to match(/Test-Path "deleteme\.txt".*/)
- end
- subject.del_file!("deleteme.txt")
- end
- end
- context "on unix-like" do
- let(:family) { "debian" }
- let(:name) { "ubuntu" }
- it "deletes the file with a windows command" do
- expect(subject).to receive(:run_command!) do |cmd, &_handler|
- expect(cmd).to match(/rm -f "deleteme\.txt".*/)
- end
- subject.del_file!("deleteme.txt")
- end
- end
- end
-
- context "#run_command!" do
- it "raises a RemoteExecutionFailed when the remote execution failed" do
- command_result = double("results", stdout: "", stderr: "failed", exit_status: 1)
- expect(subject).to receive(:run_command).and_return command_result
-
- expect { subject.run_command!("test") }.to raise_error do |e|
- expect(e.hostname).to eq subject.hostname
- expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed
- expect(e.stderr).to eq "failed"
- expect(e.stdout).to eq ""
- expect(e.exit_status).to eq 1
- end
- end
- end
-
-end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
deleted file mode 100644
index 15a51b7f92..0000000000
--- a/spec/unit/knife/bootstrap_spec.rb
+++ /dev/null
@@ -1,2193 +0,0 @@
-#
-# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
-# Copyright:: Copyright 2010-2016, Ian Meyer
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::Bootstrap.load_deps
-
-describe Chef::Knife::Bootstrap do
- let(:bootstrap_template) { nil }
- let(:stderr) { StringIO.new }
- let(:bootstrap_cli_options) { [ ] }
- let(:linux_test) { true }
- let(:windows_test) { false }
- let(:linux_test) { false }
- let(:unix_test) { false }
- let(:ssh_test) { false }
-
- let(:connection) do
- double("TrainConnector",
- windows?: windows_test,
- linux?: linux_test,
- unix?: unix_test)
- end
-
- let(:knife) do
- Chef::Log.logger = Logger.new(StringIO.new)
- Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
-
- k = Chef::Knife::Bootstrap.new(bootstrap_cli_options)
- allow(k.ui).to receive(:stderr).and_return(stderr)
- allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
- allow(k).to receive(:connection).and_return connection
- k.merge_configs
- k
- end
-
- context "#check_license" do
- let(:acceptor) { instance_double(LicenseAcceptance::Acceptor) }
-
- before do
- expect(LicenseAcceptance::Acceptor).to receive(:new).and_return(acceptor)
- end
-
- describe "when a license is not required" do
- it "does not set the chef_license" do
- expect(acceptor).to receive(:license_required?).and_return(false)
- knife.check_license
- expect(Chef::Config[:chef_license]).to eq(nil)
- end
- end
-
- describe "when a license is required" do
- it "sets the chef_license" do
- expect(acceptor).to receive(:license_required?).and_return(true)
- expect(acceptor).to receive(:id_from_mixlib).and_return("id")
- expect(acceptor).to receive(:check_and_persist)
- expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist")
- knife.check_license
- expect(Chef::Config[:chef_license]).to eq("accept-no-persist")
- end
- end
- end
-
- context "#bootstrap_template" do
- it "should default to chef-full" do
- expect(knife.bootstrap_template).to be_a_kind_of(String)
- expect(File.basename(knife.bootstrap_template)).to eq("chef-full")
- end
- end
-
- context "#render_template - when using the chef-full default template" do
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- it "should render client.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.rb <<'EOP'")
- expect(rendered_template).to match("chef_server_url \"https://localhost:443\"")
- expect(rendered_template).to match("validation_client_name \"chef-validator\"")
- expect(rendered_template).to match("log_location STDOUT")
- end
-
- it "should render first-boot.json" do
- expect(rendered_template).to match("cat > /etc/chef/first-boot.json <<'EOP'")
- expect(rendered_template).to match('{"run_list":\[\]}')
- end
-
- context "and encrypted_data_bag_secret was provided" do
- it "should render encrypted_data_bag_secret file" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return("secrets")
- expect(rendered_template).to match("cat > /etc/chef/encrypted_data_bag_secret <<'EOP'")
- expect(rendered_template).to match('{"run_list":\[\]}')
- expect(rendered_template).to match(/secrets/)
- end
- end
- end
-
- context "with --bootstrap-vault-item" do
- let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] }
- it "sets the knife config cli option correctly" do
- expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] })
- end
- end
-
- context "with --bootstrap-preinstall-command" do
- command = "while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do\n echo 'waiting for dpkg lock';\n sleep 1;\n done;"
- let(:bootstrap_cli_options) { [ "--bootstrap-preinstall-command", command ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the preinstall command in the bootstrap template correctly" do
- expect(rendered_template).to match(/command/)
- end
- end
-
- context "with --bootstrap-proxy" do
- let(:bootstrap_cli_options) { [ "--bootstrap-proxy", "1.1.1.1" ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the https_proxy environment variable in the bootstrap template correctly" do
- expect(rendered_template).to match(/https_proxy="1.1.1.1" export https_proxy/)
- end
- end
-
- context "with --bootstrap-no-proxy" do
- let(:bootstrap_cli_options) { [ "--bootstrap-no-proxy", "localserver" ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the https_proxy environment variable in the bootstrap template correctly" do
- expect(rendered_template).to match(/no_proxy="localserver" export no_proxy/)
- end
- end
-
- context "with :bootstrap_template and :template_file cli options" do
- let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "other-template" ] }
-
- it "should select bootstrap template" do
- expect(File.basename(knife.bootstrap_template)).to eq("my-template")
- end
- end
-
- context "when finding templates" do
- context "when :bootstrap_template config is set to a file" do
- context "that doesn't exist" do
- let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" }
-
- it "raises an error" do
- expect { knife.find_template }.to raise_error(Errno::ENOENT)
- end
- end
-
- context "that exists" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
-
- it "loads the given file as the template" do
- expect(Chef::Log).to receive(:trace)
- expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")))
- end
- end
- end
-
- context "when :bootstrap_template config is set to a template name" do
- let(:bootstrap_template) { "example" }
-
- let(:builtin_template_path) { File.expand_path(File.join(__dir__, "../../../lib/chef/knife/bootstrap/templates", "example.erb")) }
-
- let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" }
-
- let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" }
-
- let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" }
-
- def configure_chef_config_dir
- allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config")
- end
-
- def configure_env_home
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path)
- end
-
- def configure_gem_files
- allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ])
- end
-
- before(:each) do
- expect(File).to receive(:exist?).with(bootstrap_template).and_return(false)
- end
-
- context "when file is available everywhere" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(true)
- end
-
- it "should load the template from built-in templates" do
- expect(knife.find_template).to eq(builtin_template_path)
- end
- end
-
- context "when file is available in chef_config_dir" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(true)
-
- it "should load the template from chef_config_dir" do
- knife.find_template.should eq(chef_config_dir_template_path)
- end
- end
- end
-
- context "when file is available in home directory" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(env_home_template_path).and_return(true)
- end
-
- it "should load the template from chef_config_dir" do
- expect(knife.find_template).to eq(env_home_template_path)
- end
- end
-
- context "when file is available in Gem files" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(env_home_template_path).and_return(false)
- expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
- end
-
- it "should load the template from Gem files" do
- expect(knife.find_template).to eq(gem_files_template_path)
- end
- end
-
- context "when file is available in Gem files and home dir doesn't exist" do
- before do
- configure_chef_config_dir
- configure_gem_files
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil)
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
- end
-
- it "should load the template from Gem files" do
- expect(knife.find_template).to eq(gem_files_template_path)
- end
- end
- end
- end
-
- ["-t", "--bootstrap-template"].each do |t|
- context "when #{t} option is given in the command line" do
- it "sets the knife :bootstrap_template config" do
- knife.parse_options([t, "blahblah"])
- knife.merge_configs
- expect(knife.bootstrap_template).to eq("blahblah")
- end
- end
- end
-
- context "with run_list template" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
-
- it "should return an empty run_list" do
- expect(knife.render_template).to eq('{"run_list":[]}')
- end
-
- it "should have role[base] in the run_list" do
- knife.parse_options(["-r", "role[base]"])
- knife.merge_configs
- expect(knife.render_template).to eq('{"run_list":["role[base]"]}')
- end
-
- it "should have role[base] and recipe[cupcakes] in the run_list" do
- knife.parse_options(["-r", "role[base],recipe[cupcakes]"])
- knife.merge_configs
- expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}')
- end
-
- context "with bootstrap_attribute options" do
- let(:jsonfile) do
- file = Tempfile.new (["node", ".json"])
- File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' }
- file
- end
-
- it "should have foo => {bar => baz} in the first_boot from cli" do
- knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
- knife.merge_configs
- expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
- actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
- expect(actual_hash).to eq(expected_hash)
- end
-
- it "should have foo => {bar => baz} in the first_boot from file" do
- knife.parse_options(["--json-attribute-file", jsonfile.path])
- knife.merge_configs
- expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
- actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
- expect(actual_hash).to eq(expected_hash)
- jsonfile.close
- end
-
- it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do
- knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
- knife.parse_options(["--json-attribute-file", jsonfile.path])
- knife.merge_configs
- allow(knife).to receive(:validate_name_args!)
- expect(knife).to receive(:check_license)
-
- expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
- jsonfile.close
- end
- end
- end
-
- context "with hints template" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) }
-
- it "should create a hint file when told to" do
- knife.parse_options(["--hint", "openstack"])
- knife.merge_configs
- expect(knife.render_template).to match(%r{/etc/chef/ohai/hints/openstack.json})
- end
-
- it "should populate a hint file with JSON when given a file to read" do
- allow(::File).to receive(:read).and_return('{ "foo" : "bar" }')
- knife.parse_options(["--hint", "openstack=hints/openstack.json"])
- knife.merge_configs
- expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/)
- end
- end
-
- describe "specifying no_proxy with various entries" do
- subject(:knife) do
- k = described_class.new
- Chef::Config[:knife][:bootstrap_template] = template_file
- allow(k).to receive(:connection).and_return connection
- k.parse_options(options)
- k.merge_configs
- k
- end
-
- let(:options) { ["--bootstrap-no-proxy", setting] }
-
- let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
-
- let(:rendered_template) do
- knife.render_template
- end
-
- context "via --bootstrap-no-proxy" do
- let(:setting) { "api.opscode.com" }
-
- it "renders the client.rb with a single FQDN no_proxy entry" do
- expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com".*/)
- end
- end
-
- context "via --bootstrap-no-proxy multiple" do
- let(:setting) { "api.opscode.com,172.16.10.*" }
-
- it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
- expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com,172.16.10.\*".*/)
- end
- end
-
- context "via --ssl-verify-mode none" do
- let(:options) { ["--node-ssl-verify-mode", "none"] }
-
- it "renders the client.rb with ssl_verify_mode set to :verify_none" do
- expect(rendered_template).to match(/ssl_verify_mode :verify_none/)
- end
- end
-
- context "via --node-ssl-verify-mode peer" do
- let(:options) { ["--node-ssl-verify-mode", "peer"] }
-
- it "renders the client.rb with ssl_verify_mode set to :verify_peer" do
- expect(rendered_template).to match(/ssl_verify_mode :verify_peer/)
- end
- end
-
- context "via --node-ssl-verify-mode all" do
- let(:options) { ["--node-ssl-verify-mode", "all"] }
-
- it "raises error" do
- expect { rendered_template }.to raise_error(RuntimeError)
- end
- end
-
- context "via --node-verify-api-cert" do
- let(:options) { ["--node-verify-api-cert"] }
-
- it "renders the client.rb with verify_api_cert set to true" do
- expect(rendered_template).to match(/verify_api_cert true/)
- end
- end
-
- context "via --no-node-verify-api-cert" do
- let(:options) { ["--no-node-verify-api-cert"] }
-
- it "renders the client.rb with verify_api_cert set to false" do
- expect(rendered_template).to match(/verify_api_cert false/)
- end
- end
- end
-
- describe "specifying the encrypted data bag secret key" do
- let(:secret) { "supersekret" }
- let(:options) { [] }
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
- let(:rendered_template) do
- knife.parse_options(options)
- knife.merge_configs
- knife.render_template
- end
-
- it "creates a secret file" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(rendered_template).to match(/#{secret}/)
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
-
- end
-
- describe "when transferring trusted certificates" do
- let(:trusted_certs_dir) { Chef::Util::PathHelper.cleanpath(File.join(__dir__, "../../data/trusted_certs")) }
-
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- before do
- Chef::Config[:trusted_certs_dir] = trusted_certs_dir
- allow(IO).to receive(:read).and_call_original
- allow(IO).to receive(:read).with(File.expand_path(Chef::Config[:validation_key])).and_return("")
- end
-
- def certificates
- Dir[File.join(trusted_certs_dir, "*.{crt,pem}")]
- end
-
- it "creates /etc/chef/trusted_certs" do
- expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs})
- end
-
- it "copies the certificates in the directory" do
- certificates.each do |cert|
- expect(IO).to receive(:read).with(File.expand_path(cert))
- end
-
- certificates.each do |cert|
- expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'})
- end
- end
-
- context "when :trusted_cets_dir is empty" do
- let(:trusted_certs_dir) { Chef::Util::PathHelper.cleanpath(File.join(__dir__, "../../data/trusted_certs_empty")) }
- it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do
- expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs})
- end
- end
-
- end
-
- context "when doing fips things" do
- let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
- let(:trusted_certs_dir) { Chef::Util::PathHelper.cleanpath(File.join(__dir__, "../../data/trusted_certs")) }
-
- before do
- Chef::Config[:knife][:bootstrap_template] = template_file
- knife.merge_configs
- end
-
- let(:rendered_template) do
- knife.render_template
- end
-
- context "when knife is in fips mode" do
- before do
- Chef::Config[:fips] = true
- end
-
- it "renders 'fips true'" do
- expect(rendered_template).to match("fips")
- end
- end
-
- context "when knife is not in fips mode" do
- before do
- # This is required because the chef-fips pipeline does
- # has a default value of true for fips
- Chef::Config[:fips] = false
- end
-
- it "does not render anything about fips" do
- expect(rendered_template).not_to match("fips")
- end
- end
- end
-
- describe "when transferring client.d" do
-
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- before do
- Chef::Config[:client_d_dir] = client_d_dir
- end
-
- context "when client_d_dir is nil" do
- let(:client_d_dir) { nil }
-
- it "does not create /etc/chef/client.d" do
- expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d})
- end
- end
-
- context "when client_d_dir is set" do
- let(:client_d_dir) do
- Chef::Util::PathHelper.cleanpath(
- File.join(__dir__, "../../data/client.d_00")
- )
- end
-
- it "creates /etc/chef/client.d" do
- expect(rendered_template).to match("mkdir -p /etc/chef/client\.d")
- end
-
- context "a flat directory structure" do
- it "escapes single-quotes" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/02-strings.rb <<'EOP'")
- expect(rendered_template).to match("something '\\\\''/foo/bar'\\\\''")
- end
-
- it "creates a file 00-foo.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'")
- expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228")
- end
- it "creates a file bar" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'")
- expect(rendered_template).to match("1 / 0")
- end
- end
-
- context "a nested directory structure" do
- let(:client_d_dir) do
- Chef::Util::PathHelper.cleanpath(
- File.join(__dir__, "../../data/client.d_01")
- )
- end
- it "creates a file foo/bar.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'")
- expect(rendered_template).to match("1 / 0")
- end
- end
- end
- end
-
- describe "#connection_protocol" do
- let(:host_descriptor) { "example.com" }
- let(:config) { {} }
- let(:knife_connection_protocol) { nil }
- before do
- allow(knife).to receive(:config).and_return config
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- if knife_connection_protocol
- Chef::Config[:knife][:connection_protocol] = knife_connection_protocol
- knife.merge_configs
- end
- end
-
- context "when protocol is part of the host argument" do
- let(:host_descriptor) { "winrm://myhost" }
-
- it "returns the value provided by the host argument" do
- expect(knife.connection_protocol).to eq "winrm"
- end
- end
-
- context "when protocol is provided via the CLI flag" do
- let(:config) { { connection_protocol: "winrm" } }
- it "returns that value" do
- expect(knife.connection_protocol).to eq "winrm"
- end
-
- end
- context "when protocol is provided via the host argument and the CLI flag" do
- let(:host_descriptor) { "ssh://example.com" }
- let(:config) { { connection_protocol: "winrm" } }
-
- it "returns the value provided by the host argument" do
- expect(knife.connection_protocol).to eq "ssh"
- end
- end
-
- context "when no explicit protocol is provided" do
- let(:config) { {} }
- let(:host_descriptor) { "example.com" }
- let(:knife_connection_protocol) { "winrm" }
- it "falls back to knife config" do
- expect(knife.connection_protocol).to eq "winrm"
- end
- context "and there is no knife bootstrap_protocol" do
- let(:knife_connection_protocol) { nil }
- it "falls back to 'ssh'" do
- expect(knife.connection_protocol).to eq "ssh"
- end
- end
- end
-
- end
-
- describe "#validate_protocol!" do
- let(:host_descriptor) { "example.com" }
- let(:config) { {} }
- let(:connection_protocol) { "ssh" }
- before do
- allow(knife).to receive(:config).and_return config
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- end
-
- context "when protocol is provided both in the URL and via --protocol" do
-
- context "and they do not match" do
- let(:connection_protocol) { "ssh" }
- let(:config) { { connection_protocol: "winrm" } }
- it "outputs an error and exits" do
- expect(knife.ui).to receive(:error)
- expect { knife.validate_protocol! }.to raise_error SystemExit
- end
- end
-
- context "and they do match" do
- let(:connection_protocol) { "winrm" }
- let(:config) { { connection_protocol: "winrm" } }
- it "returns true" do
- expect(knife.validate_protocol!).to eq true
- end
- end
- end
-
- context "and the protocol is supported" do
-
- Chef::Knife::Bootstrap::SUPPORTED_CONNECTION_PROTOCOLS.each do |proto|
- let(:connection_protocol) { proto }
- it "returns true for #{proto}" do
- expect(knife.validate_protocol!).to eq true
- end
- end
- end
-
- context "and the protocol is not supported" do
- let(:connection_protocol) { "invalid" }
- it "outputs an error and exits" do
- expect(knife.ui).to receive(:error).with(/Unsupported protocol '#{connection_protocol}'/)
- expect { knife.validate_protocol! }.to raise_error SystemExit
- end
- end
- end
-
- describe "#validate_policy_options!" do
-
- context "when only policy_name is given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } }
-
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
- end
-
- end
-
- context "when only policy_group is given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-group staging } }
-
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
- end
-
- end
-
- context "when both policy_name and policy_group are given, but run list is also given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } }
-
- it "returns an error stating that policyfile and run_list are exclusive" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive")
- end
-
- end
-
- context "when policy_name and policy_group are given with no conflicting options" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } }
-
- it "passes options validation" do
- expect { knife.validate_policy_options! }.to_not raise_error
- end
-
- it "passes them into the bootstrap context" do
- expect(knife.bootstrap_context.first_boot).to have_key(:policy_name)
- expect(knife.bootstrap_context.first_boot).to have_key(:policy_group)
- end
-
- it "ensures that run_list is not set in the bootstrap context" do
- expect(knife.bootstrap_context.first_boot).to_not have_key(:run_list)
- end
-
- end
-
- # https://github.com/chef/chef/issues/4131
- # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it
- # worked before, so make it work now.
- context "when a plugin sets the run list option to nil" do
- before do
- knife.config[:run_list] = nil
- end
-
- it "passes options validation" do
- expect { knife.validate_policy_options! }.to_not raise_error
- end
- end
- end
-
- # TODO - this is the only cli option we validate the _option_ itself -
- # so we'll know if someone accidentally deletes or renames use_sudo_password
- # Is this worht keeping? If so, then it seems we should expand it
- # to cover all options.
- context "validating use_sudo_password option" do
- it "use_sudo_password contains description and long params for help" do
- expect(knife.options).to(have_key(:use_sudo_password)) \
- && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\
- && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq(""))
- end
- end
-
- context "#connection_opts" do
- let(:connection_protocol) { "ssh" }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
- context "behavioral test: " do
- let(:expected_connection_opts) do
- { base_opts: true,
- ssh_identity_opts: true,
- ssh_opts: true,
- gateway_opts: true,
- host_verify_opts: true,
- sudo_opts: true,
- winrm_opts: true }
- end
-
- it "queries and merges only expected configurations" do
- expect(knife).to receive(:base_opts).and_return({ base_opts: true })
- expect(knife).to receive(:host_verify_opts).and_return({ host_verify_opts: true })
- expect(knife).to receive(:gateway_opts).and_return({ gateway_opts: true })
- expect(knife).to receive(:sudo_opts).and_return({ sudo_opts: true })
- expect(knife).to receive(:winrm_opts).and_return({ winrm_opts: true })
- expect(knife).to receive(:ssh_opts).and_return({ ssh_opts: true })
- expect(knife).to receive(:ssh_identity_opts).and_return({ ssh_identity_opts: true })
- expect(knife.connection_opts).to match expected_connection_opts
- end
- end
-
- context "functional test: " do
- context "when protocol is winrm" do
- let(:connection_protocol) { "winrm" }
- # context "and neither CLI nor Chef::Config config entries have been provided"
- # end
- context "and all supported values are provided as Chef::Config entries" do
- before do
- # Set everything to easily identifiable and obviously fake values
- # to verify that Chef::Config is being sourced instead of knife.config
- knife.config = {}
- Chef::Config[:knife][:max_wait] = 9999
- Chef::Config[:knife][:winrm_user] = "winbob"
- Chef::Config[:knife][:winrm_port] = 9999
- Chef::Config[:knife][:ca_trust_file] = "trust.me"
- Chef::Config[:knife][:kerberos_realm] = "realm"
- Chef::Config[:knife][:kerberos_service] = "service"
- Chef::Config[:knife][:winrm_auth_method] = "kerberos" # default is negotiate
- Chef::Config[:knife][:winrm_basic_auth_only] = true
- Chef::Config[:knife][:winrm_no_verify_cert] = true
- Chef::Config[:knife][:session_timeout] = 9999
- Chef::Config[:knife][:winrm_ssl] = true
- Chef::Config[:knife][:winrm_ssl_peer_fingerprint] = "ABCDEF"
- end
-
- context "and no CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "trust.me",
- max_wait_until_ready: 9999,
- operation_timeout: 9999,
- ssl_peer_fingerprint: "ABCDEF",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: true,
- user: "winbob",
- port: 9999,
- self_signed: true,
- ssl: true,
- kerberos_realm: "realm",
- kerberos_service: "service",
- }
- end
-
- it "generates a config hash using the Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
-
- end
-
- context "and some CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "no trust",
- max_wait_until_ready: 9999,
- operation_timeout: 9999,
- ssl_peer_fingerprint: "ABCDEF",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: true,
- user: "microsoftbob",
- port: 12,
- self_signed: true,
- ssl: true,
- kerberos_realm: "realm",
- kerberos_service: "service",
- password: "lobster",
- }
- end
-
- before do
- knife.config[:ca_trust_file] = "no trust"
- knife.config[:connection_user] = "microsoftbob"
- knife.config[:connection_port] = 12
- knife.config[:winrm_port] = "13" # indirectly verify we're not looking for the wrong CLI flag
- knife.config[:connection_password] = "lobster"
- end
-
- it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and all CLI options have been given" do
- before do
- # We'll force kerberos vi knife.config because it
- # causes additional options to populate - make sure
- # Chef::Config is different so we can be sure that we didn't
- # pull in the Chef::Config value
- Chef::Config[:knife][:winrm_auth_method] = "negotiate"
- knife.config[:connection_password] = "blue"
- knife.config[:max_wait] = 1000
- knife.config[:connection_user] = "clippy"
- knife.config[:connection_port] = 1000
- knife.config[:winrm_port] = 1001 # We should not see this value get used
-
- knife.config[:ca_trust_file] = "trust.the.internet"
- knife.config[:kerberos_realm] = "otherrealm"
- knife.config[:kerberos_service] = "otherservice"
- knife.config[:winrm_auth_method] = "kerberos" # default is negotiate
- knife.config[:winrm_basic_auth_only] = false
- knife.config[:winrm_no_verify_cert] = false
- knife.config[:session_timeout] = 1000
- knife.config[:winrm_ssl] = false
- knife.config[:winrm_ssl_peer_fingerprint] = "FEDCBA"
- end
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "trust.the.internet",
- max_wait_until_ready: 1000,
- operation_timeout: 1000,
- ssl_peer_fingerprint: "FEDCBA",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: false,
- user: "clippy",
- port: 1000,
- self_signed: false,
- ssl: false,
- kerberos_realm: "otherrealm",
- kerberos_service: "otherservice",
- password: "blue",
- }
- end
- it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end # with underlying Chef::Config values
-
- context "and no values are provided from Chef::Config or CLI" do
- before do
- # We will use knife's actual config since these tests
- # have assumptions based on CLI default values
- end
- let(:expected_result) do
- {
- logger: Chef::Log,
- operation_timeout: 60,
- self_signed: false,
- ssl: false,
- ssl_peer_fingerprint: nil,
- winrm_basic_auth_only: false,
- winrm_transport: "negotiate",
- }
- end
- it "populates appropriate defaults" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end # winrm
-
- context "when protocol is ssh" do
- let(:connection_protocol) { "ssh" }
- # context "and neither CLI nor Chef::Config config entries have been provided"
- # end
- context "and all supported values are provided as Chef::Config entries" do
- before do
- # Set everything to easily identifiable and obviously fake values
- # to verify that Chef::Config is being sourced instead of knife.config
- knife.config = {}
- Chef::Config[:knife][:max_wait] = 9999
- Chef::Config[:knife][:session_timeout] = 9999
- Chef::Config[:knife][:ssh_user] = "sshbob"
- Chef::Config[:knife][:ssh_port] = 9999
- Chef::Config[:knife][:host_key_verify] = false
- Chef::Config[:knife][:ssh_gateway_identity] = "/gateway.pem"
- Chef::Config[:knife][:ssh_gateway] = "admin@mygateway.local:1234"
- Chef::Config[:knife][:ssh_identity_file] = "/identity.pem"
- Chef::Config[:knife][:use_sudo_password] = false # We have no password.
- end
-
- context "and no CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 9999.0,
- connection_timeout: 9999,
- user: "sshbob",
- bastion_host: "mygateway.local",
- bastion_port: 1234,
- bastion_user: "admin",
- forward_agent: false,
- keys_only: true,
- key_files: ["/identity.pem", "/gateway.pem"],
- sudo: false,
- verify_host_key: "always",
- port: 9999,
- non_interactive: true,
- }
- end
-
- it "generates a correct config hash using the Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and unsupported Chef::Config options are given in Chef::Config, not in CLI" do
- before do
- Chef::Config[:knife][:password] = "blah"
- Chef::Config[:knife][:ssh_password] = "blah"
- Chef::Config[:knife][:preserve_home] = true
- Chef::Config[:knife][:use_sudo] = true
- Chef::Config[:knife][:ssh_forward_agent] = "blah"
- end
- it "does not include the corresponding option in the connection options" do
- knife.merge_configs
- expect(knife.connection_opts.key?(:password)).to eq false
- expect(knife.connection_opts.key?(:ssh_forward_agent)).to eq false
- expect(knife.connection_opts.key?(:use_sudo)).to eq false
- expect(knife.connection_opts.key?(:preserve_home)).to eq false
- end
- end
-
- context "and some CLI options have been given" do
- before do
- knife.config = {}
- knife.config[:connection_user] = "sshalice"
- knife.config[:connection_port] = 12
- knife.config[:ssh_port] = "13" # canary to indirectly verify we're not looking for the wrong CLI flag
- knife.config[:connection_password] = "feta cheese"
- knife.config[:max_wait] = 150
- knife.config[:session_timeout] = 120
- knife.config[:use_sudo] = true
- knife.config[:use_sudo_pasword] = true
- knife.config[:ssh_forward_agent] = true
- end
-
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 150.0, # cli
- connection_timeout: 120, # cli
- user: "sshalice", # cli
- password: "feta cheese", # cli
- bastion_host: "mygateway.local", # Config
- bastion_port: 1234, # Config
- bastion_user: "admin", # Config
- forward_agent: true, # cli
- keys_only: false, # implied false from config password present
- key_files: ["/identity.pem", "/gateway.pem"], # Config
- sudo: true, # ccli
- verify_host_key: "always", # Config
- port: 12, # cli
- non_interactive: true,
- }
- end
-
- it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and all CLI options have been given" do
- before do
- knife.config = {}
- knife.config[:max_wait] = 150
- knife.config[:session_timeout] = 120
- knife.config[:connection_user] = "sshroot"
- knife.config[:connection_port] = 1000
- knife.config[:connection_password] = "blah"
- knife.config[:forward_agent] = true
- knife.config[:use_sudo] = true
- knife.config[:use_sudo_password] = true
- knife.config[:preserve_home] = true
- knife.config[:use_sudo_pasword] = true
- knife.config[:ssh_forward_agent] = true
- knife.config[:ssh_verify_host_key] = true
- knife.config[:ssh_gateway_identity] = "/gateway-identity.pem"
- knife.config[:ssh_gateway] = "me@example.com:10"
- knife.config[:ssh_identity_file] = "/my-identity.pem"
-
- # We'll set these as canaries - if one of these values shows up
- # in a failed test, then the behavior of not pulling from these keys
- # out of knife.config is broken:
- knife.config[:ssh_user] = "do not use"
- knife.config[:ssh_port] = 1001
- end
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 150,
- connection_timeout: 120,
- user: "sshroot",
- password: "blah",
- port: 1000,
- bastion_host: "example.com",
- bastion_port: 10,
- bastion_user: "me",
- forward_agent: true,
- keys_only: false,
- key_files: ["/my-identity.pem", "/gateway-identity.pem"],
- sudo: true,
- sudo_options: "-H",
- sudo_password: "blah",
- verify_host_key: true,
- non_interactive: true,
- }
- end
- it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end
- context "and no values are provided from Chef::Config or CLI" do
- before do
- # We will use knife's actual config since these tests
- # have assumptions based on CLI default values
- config = {}
- end
-
- let(:expected_result) do
- {
- forward_agent: false,
- key_files: [],
- logger: Chef::Log,
- keys_only: false,
- sudo: false,
- verify_host_key: "always",
- non_interactive: true,
- connection_timeout: 60,
- }
- end
- it "populates appropriate defaults" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- end # ssh
- end # functional tests
-
- end # connection_opts
-
- context "#base_opts" do
- let(:connection_protocol) { nil }
-
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for all protocols" do
- context "when password is provided" do
- before do
- knife.config[:connection_port] = 250
- knife.config[:connection_user] = "test"
- knife.config[:connection_password] = "opscode"
- end
-
- let(:expected_opts) do
- {
- port: 250,
- user: "test",
- logger: Chef::Log,
- password: "opscode",
- }
- end
- it "generates the correct options" do
- expect(knife.base_opts).to eq expected_opts
- end
-
- end
-
- context "when password is not provided" do
- before do
- knife.config[:connection_port] = 250
- knife.config[:connection_user] = "test"
- end
-
- let(:expected_opts) do
- {
- port: 250,
- user: "test",
- logger: Chef::Log,
- }
- end
- it "generates the correct options" do
- expect(knife.base_opts).to eq expected_opts
- end
- end
- end
- end
-
- context "#host_verify_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns the expected configuration" do
- knife.config[:winrm_no_verify_cert] = true
- expect(knife.host_verify_opts).to eq( { self_signed: true } )
- end
- it "provides a correct default when no option given" do
- expect(knife.host_verify_opts).to eq( { self_signed: false } )
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns the expected configuration" do
- knife.config[:ssh_verify_host_key] = false
- expect(knife.host_verify_opts).to eq( { verify_host_key: false } )
- end
- it "provides a correct default when no option given" do
- expect(knife.host_verify_opts).to eq( { verify_host_key: "always" } )
- end
- end
- end
-
- # TODO - test keys_only, password, config source behavior
- context "#ssh_identity_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.ssh_identity_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "when an identity file is specified" do
- before do
- knife.config[:ssh_identity_file] = "/identity.pem"
- end
- it "generates the expected configuration" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: true,
- })
- end
- context "and a password is also specified" do
- before do
- knife.config[:connection_password] = "blah"
- end
- it "generates the expected configuration (key, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: false,
- })
- end
- end
-
- context "and a gateway is not specified" do
- context "but a gateway identity file is specified" do
- it "does not include the gateway identity file in keys" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: ["/identity.pem"],
- keys_only: true,
- })
- end
-
- end
-
- end
-
- context "and a gatway is specified" do
- before do
- knife.config[:ssh_gateway] = "example.com"
- end
- context "and a gateway identity file is not specified" do
- it "config includes only identity file and not gateway identity" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: true,
- })
- end
- end
-
- context "and a gateway identity file is also specified" do
- before do
- knife.config[:ssh_gateway_identity] = "/gateway.pem"
- end
-
- it "generates the expected configuration (both keys, keys_only true)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem", "/gateway.pem" ],
- keys_only: true,
- })
- end
- end
- end
- end
-
- context "when no identity file is specified" do
- it "generates the expected configuration (no keys, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq( {
- key_files: [ ],
- keys_only: false,
- })
- end
- context "and a gateway with gateway identity file is specified" do
- before do
- knife.config[:ssh_gateway] = "host"
- knife.config[:ssh_gateway_identity] = "/gateway.pem"
- end
-
- it "generates the expected configuration (gateway key, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/gateway.pem" ],
- keys_only: false,
- })
- end
- end
- end
- end
- end
-
- context "#gateway_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.gateway_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "and ssh_gateway with hostname, user and port provided" do
- before do
- knife.config[:ssh_gateway] = "testuser@gateway:9021"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: "testuser",
- bastion_host: "gateway",
- bastion_port: 9021,
- })
- end
- end
- context "and ssh_gateway with only hostname is given" do
- before do
- knife.config[:ssh_gateway] = "gateway"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: nil,
- bastion_host: "gateway",
- bastion_port: nil,
- })
- end
- end
- context "and ssh_gateway with hostname and user is is given" do
- before do
- knife.config[:ssh_gateway] = "testuser@gateway"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: "testuser",
- bastion_host: "gateway",
- bastion_port: nil,
- })
- end
- end
-
- context "and ssh_gateway with hostname and port is is given" do
- before do
- knife.config[:ssh_gateway] = "gateway:11234"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: nil,
- bastion_host: "gateway",
- bastion_port: 11234,
- })
- end
- end
-
- context "and ssh_gateway is not provided" do
- it "returns an empty hash" do
- expect(knife.gateway_opts).to eq({})
- end
- end
- end
- end
-
- context "#sudo_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.sudo_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "when use_sudo is set" do
- before do
- knife.config[:use_sudo] = true
- end
-
- it "returns a config that enables sudo" do
- expect(knife.sudo_opts).to eq( { sudo: true } )
- end
-
- context "when use_sudo_password is also set" do
- before do
- knife.config[:use_sudo_password] = true
- knife.config[:connection_password] = "opscode"
- end
- it "includes :connection_password value in a sudo-enabled configuration" do
- expect(knife.sudo_opts).to eq({
- sudo: true,
- sudo_password: "opscode",
- })
- end
- end
-
- context "when preserve_home is set" do
- before do
- knife.config[:preserve_home] = true
- end
- it "enables sudo with sudo_option to preserve home" do
- expect(knife.sudo_opts).to eq({
- sudo_options: "-H",
- sudo: true,
- })
- end
- end
- end
-
- context "when use_sudo is not set" do
- before do
- knife.config[:use_sudo_password] = true
- knife.config[:preserve_home] = true
- end
- it "returns configuration for sudo off, ignoring other related options" do
- expect(knife.sudo_opts).to eq( { sudo: false } )
- end
- end
- end
- end
-
- context "#ssh_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- let(:default_opts) do
- {
- non_interactive: true,
- forward_agent: false,
- connection_timeout: 60,
- }
- end
-
- context "by default" do
- it "returns a configuration hash with appropriate defaults" do
- expect(knife.ssh_opts).to eq default_opts
- end
- end
-
- context "when ssh_forward_agent has a value" do
- before do
- knife.config[:ssh_forward_agent] = true
- end
- it "returns a default configuration hash with forward_agent set to true" do
- expect(knife.ssh_opts).to eq(default_opts.merge(forward_agent: true))
- end
- end
- context "when session_timeout has a value" do
- before do
- knife.config[:session_timeout] = 120
- end
- it "returns a default configuration hash with updated timeout value." do
- expect(knife.ssh_opts).to eq(default_opts.merge(connection_timeout: 120))
- end
- end
-
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty has because ssh is not winrm" do
- expect(knife.ssh_opts).to eq({})
- end
- end
-
- end
-
- context "#winrm_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- let(:expected) do
- {
- winrm_transport: "negotiate",
- winrm_basic_auth_only: false,
- ssl: false,
- ssl_peer_fingerprint: nil,
- operation_timeout: 60,
- }
- end
-
- it "generates a correct configuration hash with expected defaults" do
- expect(knife.winrm_opts).to eq expected
- end
-
- context "with ssl_peer_fingerprint" do
- let(:ssl_peer_fingerprint_expected) do
- expected.merge({ ssl_peer_fingerprint: "ABCD" })
- end
-
- before do
- knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
- end
-
- it "generates a correct options hash with ssl_peer_fingerprint from the config provided" do
- expect(knife.winrm_opts).to eq ssl_peer_fingerprint_expected
- end
- end
-
- context "with winrm_ssl" do
- let(:ssl_expected) do
- expected.merge({ ssl: true })
- end
- before do
- knife.config[:winrm_ssl] = true
- end
-
- it "generates a correct options hash with ssl from the config provided" do
- expect(knife.winrm_opts).to eq ssl_expected
- end
- end
-
- context "with winrm_auth_method" do
- let(:winrm_auth_method_expected) do
- expected.merge({ winrm_transport: "freeaccess" })
- end
-
- before do
- knife.config[:winrm_auth_method] = "freeaccess"
- end
-
- it "generates a correct options hash with winrm_transport from the config provided" do
- expect(knife.winrm_opts).to eq winrm_auth_method_expected
- end
- end
-
- context "with ca_trust_file" do
- let(:ca_trust_expected) do
- expected.merge({ ca_trust_path: "/trust.me" })
- end
- before do
- knife.config[:ca_trust_file] = "/trust.me"
- end
-
- it "generates a correct options hash with ca_trust_file from the config provided" do
- expect(knife.winrm_opts).to eq ca_trust_expected
- end
- end
-
- context "with kerberos auth" do
- let(:kerberos_expected) do
- expected.merge({
- kerberos_service: "testsvc",
- kerberos_realm: "TESTREALM",
- winrm_transport: "kerberos",
- })
- end
-
- before do
- knife.config[:winrm_auth_method] = "kerberos"
- knife.config[:kerberos_service] = "testsvc"
- knife.config[:kerberos_realm] = "TESTREALM"
- end
-
- it "generates a correct options hash containing kerberos auth configuration from the config provided" do
- expect(knife.winrm_opts).to eq kerberos_expected
- end
- end
-
- context "with winrm_basic_auth_only" do
- before do
- knife.config[:winrm_basic_auth_only] = true
- end
- let(:basic_auth_expected) do
- expected.merge( { winrm_basic_auth_only: true } )
- end
- it "generates a correct options hash containing winrm_basic_auth_only from the config provided" do
- expect(knife.winrm_opts).to eq basic_auth_expected
- end
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns an empty hash because ssh is not winrm" do
- expect(knife.winrm_opts).to eq({})
- end
- end
- end
- describe "#run" do
- it "performs the steps we expect to run a bootstrap" do
- expect(knife).to receive(:check_license)
- expect(knife).to receive(:validate_name_args!).ordered
- expect(knife).to receive(:validate_protocol!).ordered
- expect(knife).to receive(:validate_first_boot_attributes!).ordered
- expect(knife).to receive(:validate_winrm_transport_opts!).ordered
- expect(knife).to receive(:validate_policy_options!).ordered
- expect(knife).to receive(:winrm_warn_no_ssl_verification).ordered
- expect(knife).to receive(:warn_on_short_session_timeout).ordered
- expect(knife).to receive(:connect!).ordered
- expect(knife).to receive(:register_client).ordered
- expect(knife).to receive(:render_template).and_return "content"
- expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh"
- expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh")
- expect(connection).to receive(:del_file!) # Make sure cleanup happens
-
- knife.run
-
- # Post-run verify expected state changes (not many directly in #run)
- expect($stdout.sync).to eq true
- end
- end
-
- describe "#register_client" do
- let(:vault_handler_mock) { double("ChefVaultHandler") }
- let(:client_builder_mock) { double("ClientBuilder") }
- let(:node_name) { nil }
- before do
- allow(knife).to receive(:chef_vault_handler).and_return vault_handler_mock
- allow(knife).to receive(:client_builder).and_return client_builder_mock
- knife.config[:chef_node_name] = node_name
- end
-
- shared_examples_for "creating the client locally" do
- context "when a valid node name is present" do
- let(:node_name) { "test" }
- before do
- allow(client_builder_mock).to receive(:client).and_return "client"
- allow(client_builder_mock).to receive(:client_path).and_return "/key.pem"
- end
-
- it "runs client_builder and vault_handler" do
- expect(client_builder_mock).to receive(:run)
- expect(vault_handler_mock).to receive(:run).with("client")
- knife.register_client
- end
-
- it "sets the path to the client key in the bootstrap context" do
- allow(client_builder_mock).to receive(:run)
- allow(vault_handler_mock).to receive(:run).with("client")
- knife.register_client
- expect(knife.bootstrap_context.client_pem).to eq "/key.pem"
- end
- end
-
- context "when no valid node name is present" do
- let(:node_name) { nil }
- it "shows an error and exits" do
- expect(knife.ui).to receive(:error)
- expect { knife.register_client }.to raise_error(SystemExit)
- end
- end
- end
- context "when chef_vault_handler says we're using vault" do
- let(:vault_handler_mock) { double("ChefVaultHandler") }
- before do
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return true
- end
- it_behaves_like "creating the client locally"
- end
-
- context "when an non-existant validation key is specified in chef config" do
- before do
- Chef::Config[:validation_key] = "/blah"
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
- allow(File).to receive(:exist?).with(%r{/blah}).and_return false
- end
- it_behaves_like "creating the client locally"
- end
-
- context "when a valid validation key is given and we're doing old-style client creation" do
- before do
- Chef::Config[:validation_key] = "/blah"
- allow(File).to receive(:exist?).with(%r{/blah}).and_return true
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
- end
-
- it "shows a warning message" do
- expect(knife.ui).to receive(:warn).twice
- knife.register_client
- end
- end
- end
-
- describe "#perform_bootstrap" do
- let(:exit_status) { 0 }
- let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message") }
-
- before do
- allow(connection).to receive(:hostname).and_return "testhost"
- end
- it "runs the remote script and logs the output" do
- expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
- expect(knife).to receive(:bootstrap_command)
- .with("/path.sh")
- .and_return("sh /path.sh")
- expect(connection)
- .to receive(:run_command)
- .with("sh /path.sh")
- .and_yield("output here")
- .and_return result_mock
-
- expect(knife.ui).to receive(:msg).with(/testhost/)
- knife.perform_bootstrap("/path.sh")
- end
- context "when the remote command fails" do
- let(:exit_status) { 1 }
- it "shows an error and exits" do
- expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
- expect(knife).to receive(:bootstrap_command)
- .with("/path.sh")
- .and_return("sh /path.sh")
- expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock
- expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit)
- end
- end
- end
-
- describe "#connect!" do
- before do
- # These are not required at run-time because train will handle its own
- # protocol loading. In this case, we're simulating train failures and have to load
- # them ourselves.
- require "net/ssh"
- require "train/transports/ssh"
- end
-
- context "in the normal case" do
- it "connects using the connection_opts and notifies the operator of progress" do
- expect(knife.ui).to receive(:info).with(/Connecting to.*/)
- expect(knife).to receive(:connection_opts).and_return( { opts: "here" })
- expect(knife).to receive(:do_connect).with( { opts: "here" } )
- knife.connect!
- end
- end
-
- context "when a general non-auth-failure occurs" do
- let(:expected_error) { RuntimeError.new }
- before do
- allow(knife).to receive(:do_connect).and_raise(expected_error)
- end
- it "re-raises the exception" do
- expect { knife.connect! }.to raise_error(expected_error)
- end
- end
-
- context "when ssh fingerprint is invalid" do
- let(:expected_error) { Train::Error.new("fingerprint AA:BB is unknown for \"blah,127.0.0.1\"") }
- before do
- allow(knife).to receive(:do_connect).and_raise(expected_error)
- end
- it "warns, prompts to accept, then connects with verify_host_key of accept_new" do
- expect(knife).to receive(:do_connect).and_raise(expected_error)
- expect(knife.ui).to receive(:confirm)
- .with(/.*host 'blah \(127.0.0.1\)'.*AA:BB.*Are you sure you want to continue.*/m)
- .and_return(true)
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:verify_host_key]).to eq :accept_new
- end
- knife.connect!
- end
- end
-
- context "when an auth failure occurs" do
- let(:expected_error) do
- e = Train::Error.new
- actual = Net::SSH::AuthenticationFailed.new
- # Simulate train's nested error - they wrap
- # ssh/network errors in a TrainError.
- allow(e).to receive(:cause).and_return(actual)
- e
- end
-
- let(:expected_error_password_prompt) do
- e = Train::ClientError.new
- reason = :no_ssh_password_or_key_available
- allow(e).to receive(:reason).and_return(reason)
- e
- end
-
- let(:expected_error_password_prompt_winrm) do
- e = RuntimeError.new
- message = "password is a required option"
- allow(e).to receive(:message).and_return(message)
- e
- end
-
- context "and password auth was used" do
- before do
- allow(connection).to receive(:password_auth?).and_return true
- end
-
- it "re-raises the error so as not to resubmit the same failing password" do
- expect(knife).to receive(:do_connect).and_raise(expected_error)
- expect { knife.connect! }.to raise_error(expected_error)
- end
- end
-
- context "and password auth was not used" do
- before do
- allow(connection).to receive(:password_auth?).and_return false
- allow(connection).to receive(:user).and_return "testuser"
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
-
- it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do
- expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt)
- expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
- expect(knife.ui).to receive(:ask).and_return("newpassword")
- # Ensure that we set echo off to prevent showing password on the screen
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:password]).to eq "newpassword"
- end
- knife.connect!
- end
- end
-
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
-
- it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password for" do
- expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt_winrm)
- expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
- expect(knife.ui).to receive(:ask).and_return("newpassword")
- # Ensure that we set echo off to prevent showing password on the screen
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:password]).to eq "newpassword"
- end
- knife.connect!
- end
- end
- end
- end
- end
-
- it "verifies that a server to bootstrap was given as a command line arg" do
- knife.name_args = nil
- expect(knife).to receive(:check_license)
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
- end
-
- describe "#bootstrap_context" do
- context "under Windows" do
- let(:windows_test) { true }
- it "creates a WindowsBootstrapContext" do
- require "chef/knife/core/windows_bootstrap_context"
- expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext
- end
- end
-
- context "under linux" do
- let(:linux_test) { true }
- it "creates a BootstrapContext" do
- require "chef/knife/core/bootstrap_context"
- expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext
- end
- end
- end
-
- describe "#config_value" do
- before do
- knife.config[:test_key_a] = "a from cli"
- knife.config[:test_key_b] = "b from cli"
- Chef::Config[:knife][:test_key_a] = "a from Chef::Config"
- Chef::Config[:knife][:test_key_c] = "c from Chef::Config"
- Chef::Config[:knife][:alt_test_key_c] = "alt c from Chef::Config"
- knife.merge_configs
- Chef::Config[:treat_deprecation_warnings_as_errors] = false
- end
-
- it "returns the Chef::Config value from the cli when the CLI key is set" do
- expect(knife.config_value(:test_key_a, :alt_test_key_c)).to eq "a from cli"
- end
-
- it "returns the Chef::Config value from the alternative key when the CLI key is not set" do
- expect(knife.config_value(:test_key_d, :alt_test_key_c)).to eq "alt c from Chef::Config"
- end
-
- it "returns the default value when the key is not provided by CLI or Chef::Config" do
- expect(knife.config_value(:missing_key, :missing_key, "found")).to eq "found"
- end
- end
-
- describe "#upload_bootstrap" do
- before do
- allow(connection).to receive(:temp_dir).and_return(temp_dir)
- allow(connection).to receive(:normalize_path) { |a| a }
- end
-
- let(:content) { "bootstrap script content" }
- context "under Windows" do
- let(:windows_test) { true }
- let(:temp_dir) { "C:/Temp/bootstrap" }
- it "creates a bat file in the temp dir provided by connection, using given content" do
- expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat")
- expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat"
- end
- end
-
- context "under Linux" do
- let(:linux_test) { true }
- let(:temp_dir) { "/tmp/bootstrap" }
- it "creates a 'sh file in the temp dir provided by connection, using given content" do
- expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh")
- expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh"
- end
- end
- end
-
- describe "#bootstrap_command" do
- context "under Windows" do
- let(:windows_test) { true }
- it "prefixes the command to run under cmd.exe" do
- expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat"
- end
-
- end
- context "under Linux" do
- let(:linux_test) { true }
- it "prefixes the command to run under sh" do
- expect(knife.bootstrap_command("bootstrap")).to eq "sh bootstrap"
- end
- end
- end
-
- describe "#default_bootstrap_template" do
- context "under Windows" do
- let(:windows_test) { true }
- it "is windows-chef-client-msi" do
- expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi"
- end
-
- end
- context "under Linux" do
- let(:linux_test) { true }
- it "is chef-full" do
- expect(knife.default_bootstrap_template).to eq "chef-full"
- end
- end
- end
-
- describe "#do_connect" do
- let(:host_descriptor) { "example.com" }
- let(:connection) { double("TrainConnector") }
- let(:connector_mock) { double("TargetResolver", targets: [ connection ]) }
- before do
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- end
-
- it "creates a TrainConnector and connects it" do
- expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection
- expect(connection).to receive(:connect!)
- knife.do_connect({})
- end
-
- context "when sshd configured with requiretty" do
- let(:pty_err_msg) { "Sudo requires a TTY. Please see the README on how to configure sudo to allow for non-interactive usage." }
- let(:expected_error) { Train::UserError.new(pty_err_msg, :sudo_no_tty) }
- before do
- allow(connection).to receive(:connect!).and_raise(expected_error)
- end
- it "retry with pty true request option" do
- expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return(connection).exactly(2).times
- expect(knife.ui).to receive(:warn).with("#{pty_err_msg} - trying with pty request")
- expect { knife.do_connect({}) }.to raise_error(expected_error)
- end
- end
- end
-
- describe "validate_winrm_transport_opts!" do
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns true" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
- context "with plaintext auth" do
- before do
- knife.config[:winrm_auth_method] = "plaintext"
- end
- context "with ssl" do
- before do
- knife.config[:winrm_ssl] = true
- end
- it "will not error because we won't send anything in plaintext regardless" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "without ssl" do
- before do
- knife.config[:winrm_ssl] = false
- end
- context "and no validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
- end
-
- it "will error because we will generate and send a client key over the wire in plaintext" do
- expect { knife.validate_winrm_transport_opts! }.to raise_error(SystemExit)
- end
-
- end
- context "and a validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
- end
- # TODO - don't we still send validation key?
- it "will not error because we don not send client key over the wire" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- end
- end
-
- context "with other auth" do
- before do
- knife.config[:winrm_auth_method] = "kerberos"
- end
-
- context "and no validation key exists" do
- before do
-
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
- end
-
- it "will not error because we're not using plaintext auth" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "and a validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
- end
-
- it "will not error because a client key won't be sent over the wire in plaintext when a validation key is present" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
-
- end
-
- end
-
- end
-
- describe "#winrm_warn_no_ssl_verification" do
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
- context "winrm_no_verify_cert is set" do
- before do
- knife.config[:winrm_no_verify_cert] = true
- end
-
- context "and ca_trust_file is present" do
- before do
- knife.config[:ca_trust_file] = "file"
- end
-
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
-
- context "and winrm_ssl_peer_fingerprint is present" do
- before do
- knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
- end
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- context "and neither ca_trust_file nor winrm_ssl_peer_fingerprint is present" do
- it "issues a warning" do
- expect(knife.ui).to receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- end
- end
- end
-
- describe "#warn_on_short_session_timeout" do
- let(:session_timeout) { 60 }
-
- before do
- allow(knife).to receive(:session_timeout).and_return(session_timeout)
- end
-
- context "timeout is not set at all" do
- let(:session_timeout) { nil }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
-
- context "timeout is more than 15" do
- let(:session_timeout) { 16 }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
- context "timeout is 15 or less" do
- let(:session_timeout) { 15 }
- it "issues a warning" do
- expect(knife.ui).to receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
- end
-end
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
deleted file mode 100644
index 435eb888aa..0000000000
--- a/spec/unit/knife/client_bulk_delete_spec.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::ClientBulkDelete do
- let(:stdout_io) { StringIO.new }
- let(:stdout) { stdout_io.string }
- let(:stderr_io) { StringIO.new }
- let(:stderr) { stderr_io.string }
-
- let(:knife) do
- k = Chef::Knife::ClientBulkDelete.new
- k.name_args = name_args
- k.config = option_args
- allow(k.ui).to receive(:stdout).and_return(stdout_io)
- allow(k.ui).to receive(:stderr).and_return(stderr_io)
- allow(k.ui).to receive(:confirm).and_return(knife_confirm)
- allow(k.ui).to receive(:confirm_without_exit).and_return(knife_confirm)
- k
- end
-
- let(:name_args) { [ "." ] }
- let(:option_args) { {} }
-
- let(:knife_confirm) { true }
-
- let(:nonvalidator_client_names) { %w{tim dan stephen} }
- let(:nonvalidator_clients) do
- clients = {}
-
- nonvalidator_client_names.each do |client_name|
- client = Chef::ApiClientV1.new
- client.name(client_name)
- allow(client).to receive(:destroy).and_return(true)
- clients[client_name] = client
- end
-
- clients
- end
-
- let(:validator_client_names) { %w{myorg-validator} }
- let(:validator_clients) do
- clients = {}
-
- validator_client_names.each do |validator_client_name|
- validator_client = Chef::ApiClientV1.new
- validator_client.name(validator_client_name)
- allow(validator_client).to receive(:validator).and_return(true)
- allow(validator_client).to receive(:destroy).and_return(true)
- clients[validator_client_name] = validator_client
- end
-
- clients
- end
-
- let(:client_names) { nonvalidator_client_names + validator_client_names }
- let(:clients) do
- nonvalidator_clients.merge(validator_clients)
- end
-
- before(:each) do
- allow(Chef::ApiClientV1).to receive(:list).and_return(clients)
- end
-
- describe "run" do
- describe "without a regex" do
- let(:name_args) { [ ] }
-
- it "should exit if the regex is not provided" do
- expect { knife.run }.to raise_error(SystemExit)
- end
- end
-
- describe "with any clients" do
- it "should get the list of the clients" do
- expect(Chef::ApiClientV1).to receive(:list)
- knife.run
- end
-
- it "should print the name of the clients" do
- knife.run
- client_names.each do |client_name|
- expect(stdout).to include(client_name)
- end
- end
-
- it "should confirm you really want to delete them" do
- expect(knife.ui).to receive(:confirm)
- knife.run
- end
-
- describe "without --delete-validators" do
- it "should mention that validator clients wont be deleted" do
- knife.run
- expect(stdout).to include("The following clients are validators and will not be deleted:")
- info = stdout.index "The following clients are validators and will not be deleted:"
- val = stdout.index "myorg-validator"
- expect(val > info).to be_truthy
- end
-
- it "should only delete nonvalidator clients" do
- nonvalidator_clients.each_value do |c|
- expect(c).to receive(:destroy)
- end
-
- validator_clients.each_value do |c|
- expect(c).not_to receive(:destroy)
- end
-
- knife.run
- end
- end
-
- describe "with --delete-validators" do
- let(:option_args) { { delete_validators: true } }
-
- it "should mention that validator clients will be deleted" do
- knife.run
- expect(stdout).to include("The following validators will be deleted")
- end
-
- it "should confirm twice" do
- expect(knife.ui).to receive(:confirm).once
- expect(knife.ui).to receive(:confirm_without_exit).once
- knife.run
- end
-
- it "should delete all clients" do
- clients.each_value do |c|
- expect(c).to receive(:destroy)
- end
-
- knife.run
- end
- end
- end
-
- describe "with some clients" do
- let(:name_args) { [ "^ti" ] }
-
- it "should only delete clients that match the regex" do
- expect(clients["tim"]).to receive(:destroy)
- expect(clients["stephen"]).not_to receive(:destroy)
- expect(clients["dan"]).not_to receive(:destroy)
- expect(clients["myorg-validator"]).not_to receive(:destroy)
- knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
deleted file mode 100644
index d8b67de101..0000000000
--- a/spec/unit/knife/client_create_spec.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::ClientCreate.load_deps
-
-describe Chef::Knife::ClientCreate do
- let(:stderr) { StringIO.new }
- let(:stdout) { StringIO.new }
-
- let(:default_client_hash) do
- {
- "name" => "adam",
- "validator" => false,
- }
- end
-
- let(:client) do
- Chef::ApiClientV1.new
- end
-
- let(:knife) do
- k = Chef::Knife::ClientCreate.new
- k.name_args = []
- allow(k).to receive(:client).and_return(client)
- allow(k).to receive(:edit_hash).with(client).and_return(client)
- allow(k.ui).to receive(:stderr).and_return(stderr)
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- before do
- allow(client).to receive(:to_s).and_return("client[adam]")
- allow(knife).to receive(:create_client).and_return(client)
- end
-
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- end
-
- describe "run" do
- context "when nothing is passed" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { [] }
- let(:fieldname) { "client name" }
- end
- end
-
- context "when clientname is passed" do
- before do
- knife.name_args = ["adam"]
- end
-
- context "when public_key and prevent_keygen are passed" do
- before do
- knife.config[:public_key] = "some_key"
- knife.config[:prevent_keygen] = true
- end
-
- it "prints the usage" do
- expect(knife).to receive(:show_usage)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "prints a relevant error message" do
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/You cannot pass --public-key and --prevent-keygen/)
- end
- end
-
- it "should create the ApiClient" do
- expect(knife).to receive(:create_client)
- knife.run
- end
-
- it "should print a message upon creation" do
- expect(knife).to receive(:create_client)
- knife.run
- expect(stderr.string).to match(/Created client.*adam/i)
- end
-
- it "should set the Client name" do
- knife.run
- expect(client.name).to eq("adam")
- end
-
- it "by default it is not a validator" do
- knife.run
- expect(client.validator).to be_falsey
- end
-
- it "by default it should set create_key to true" do
- knife.run
- expect(client.create_key).to be_truthy
- end
-
- it "should allow you to edit the data" do
- expect(knife).to receive(:edit_hash).with(client).and_return(client)
- knife.run
- end
-
- describe "with -f or --file" do
- before do
- client.private_key "woot"
- end
-
- it "should write the private key to a file" do
- knife.config[:file] = "/tmp/monkeypants"
- filehandle = double("Filehandle")
- expect(filehandle).to receive(:print).with("woot")
- expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
- knife.run
- end
- end
-
- describe "with -p or --public-key" do
- before do
- knife.config[:public_key] = "some_key"
- allow(File).to receive(:read).and_return("some_key")
- allow(File).to receive(:expand_path)
- end
-
- it "sets the public key" do
- knife.run
- expect(client.public_key).to eq("some_key")
- end
- end
-
- describe "with -k or --prevent-keygen" do
- before do
- knife.config[:prevent_keygen] = true
- end
-
- it "does not set create_key" do
- knife.run
- expect(client.create_key).to be_falsey
- end
- end
-
- describe "with --validator" do
- before do
- knife.config[:validator] = true
- end
-
- it "should create an validator client" do
- knife.run
- expect(client.validator).to be_truthy
- end
- end
- end
- end
-end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
deleted file mode 100644
index 41a83b05e4..0000000000
--- a/spec/unit/knife/client_delete_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::ClientDelete do
- before(:each) do
- @knife = Chef::Knife::ClientDelete.new
- # defaults
- @knife.config = {
- delete_validators: false,
- }
- @knife.name_args = [ "adam" ]
- end
-
- describe "run" do
- it "should delete the client" do
- expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, "adam", "client")
- @knife.run
- end
-
- context "receives multiple clients" do
- let(:clients) { %w{ adam ben charlie } }
-
- before(:each) do
- @knife.name_args = clients
- end
-
- it "deletes all clients" do
- clients.each do |client|
- expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, client, "client")
- end
-
- @knife.run
- end
- end
-
- it "should print usage and exit when a client name is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-
- describe "with a validator" do
- before(:each) do
- allow(Chef::Knife::UI).to receive(:confirm).and_return(true)
- allow(@knife).to receive(:confirm).and_return(true)
- @client = Chef::ApiClientV1.new
- expect(Chef::ApiClientV1).to receive(:load).and_return(@client)
- end
-
- it "should delete non-validator client if --delete-validators is not set" do
- @knife.config[:delete_validators] = false
- expect(@client).to receive(:destroy).and_return(@client)
- expect(@knife).to receive(:msg)
-
- @knife.run
- end
-
- it "should delete non-validator client if --delete-validators is set" do
- @knife.config[:delete_validators] = true
- expect(@client).to receive(:destroy).and_return(@client)
- expect(@knife).to receive(:msg)
-
- @knife.run
- end
-
- it "should not delete validator client if --delete-validators is not set" do
- @client.validator(true)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should delete validator client if --delete-validators is set" do
- @knife.config[:delete_validators] = true
- expect(@client).to receive(:destroy).and_return(@client)
- expect(@knife).to receive(:msg)
-
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb
deleted file mode 100644
index e7c9030883..0000000000
--- a/spec/unit/knife/client_edit_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-require "chef/api_client_v1"
-
-describe Chef::Knife::ClientEdit do
- before(:each) do
- @knife = Chef::Knife::ClientEdit.new
- @knife.name_args = [ "adam" ]
- @knife.config[:disable_editing] = true
- end
-
- describe "run" do
- let(:data) do
- {
- "name" => "adam",
- "validator" => false,
- "admin" => false,
- "chef_type" => "client",
- "create_key" => true,
- }
- end
-
- it "should edit the client" do
- allow(Chef::ApiClientV1).to receive(:load).with("adam").and_return(data)
- expect(@knife).to receive(:edit_hash).with(data).and_return(data)
- @knife.run
- end
-
- it "should print usage and exit when a client name is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-end
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb
deleted file mode 100644
index d1b379a787..0000000000
--- a/spec/unit/knife/client_list_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::ClientList do
- before(:each) do
- @knife = Chef::Knife::ClientList.new
- @knife.name_args = [ "adam" ]
- end
-
- describe "run" do
- it "should list the clients" do
- expect(Chef::ApiClientV1).to receive(:list)
- expect(@knife).to receive(:format_list_for_display)
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb
deleted file mode 100644
index 6b6519d44f..0000000000
--- a/spec/unit/knife/client_reregister_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::ClientReregister do
- before(:each) do
- @knife = Chef::Knife::ClientReregister.new
- @knife.name_args = [ "adam" ]
- @client_mock = double("client_mock", private_key: "foo_key")
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- context "when no client name is given on the command line" do
- before do
- @knife.name_args = []
- end
-
- it "should print usage and exit when a client name is not provided" do
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-
- context "when not configured for file output" do
- it "reregisters the client and prints the key" do
- expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock)
- @knife.run
- expect(@stdout.string).to match( /foo_key/ )
- end
- end
-
- context "when configured for file output" do
- it "should write the private key to a file" do
- expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock)
-
- @knife.config[:file] = "/tmp/monkeypants"
- filehandle = StringIO.new
- expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
- @knife.run
- expect(filehandle.string).to eq("foo_key")
- end
- end
-
-end
diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb
deleted file mode 100644
index 47b4b6ccb0..0000000000
--- a/spec/unit/knife/client_show_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::ClientShow do
- before(:each) do
- @knife = Chef::Knife::ClientShow.new
- @knife.name_args = [ "adam" ]
- @client_mock = double("client_mock")
- end
-
- describe "run" do
- it "should list the client" do
- expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(@client_mock)
- expect(@knife).to receive(:format_for_display).with(@client_mock)
- @knife.run
- end
-
- it "should pretty print json" do
- @knife.config[:format] = "json"
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- fake_client_contents = { "foo" => "bar", "baz" => "qux" }
- expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(fake_client_contents)
- @knife.run
- expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n")
- end
-
- it "should print usage and exit when a client name is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-end
diff --git a/spec/unit/knife/configure_client_spec.rb b/spec/unit/knife/configure_client_spec.rb
deleted file mode 100644
index b104718c89..0000000000
--- a/spec/unit/knife/configure_client_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::ConfigureClient do
- before do
- @knife = Chef::Knife::ConfigureClient.new
- Chef::Config[:chef_server_url] = "https://chef.example.com"
- Chef::Config[:validation_client_name] = "chef-validator"
- Chef::Config[:validation_key] = "/etc/chef/validation.pem"
-
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- end
-
- describe "run" do
- it "should print usage and exit when a directory is not provided" do
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal).with(/must provide the directory/)
- expect do
- @knife.run
- end.to raise_error SystemExit
- end
-
- describe "when specifing a directory" do
- before do
- @knife.name_args = ["/home/bob/.chef"]
- @client_file = StringIO.new
- @validation_file = StringIO.new
- expect(File).to receive(:open).with("/home/bob/.chef/client.rb", "w")
- .and_yield(@client_file)
- expect(File).to receive(:open).with("/home/bob/.chef/validation.pem", "w")
- .and_yield(@validation_file)
- expect(IO).to receive(:read).and_return("foo_bar_baz")
- end
-
- it "should recursively create the directory" do
- expect(FileUtils).to receive(:mkdir_p).with("/home/bob/.chef")
- @knife.run
- end
-
- it "should write out the config file" do
- allow(FileUtils).to receive(:mkdir_p)
- @knife.run
- expect(@client_file.string).to match %r{chef_server_url\s+'https\://chef\.example\.com'}
- expect(@client_file.string).to match(/validation_client_name\s+'chef-validator'/)
- end
-
- it "should write out the validation.pem file" do
- allow(FileUtils).to receive(:mkdir_p)
- @knife.run
- expect(@validation_file.string).to match(/foo_bar_baz/)
- end
-
- it "should print information on what is being configured" do
- allow(FileUtils).to receive(:mkdir_p)
- @knife.run
- expect(@stderr.string).to match(/creating client configuration/i)
- expect(@stderr.string).to match(/writing client\.rb/i)
- expect(@stderr.string).to match(/writing validation\.pem/i)
- end
- end
- end
-
-end
diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb
deleted file mode 100644
index 7d6c840d1f..0000000000
--- a/spec/unit/knife/configure_spec.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-require "spec_helper"
-
-describe Chef::Knife::Configure do
- before do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::Configure.new
- @rest_client = double("null rest client", post: { result: :true })
- allow(@knife).to receive(:rest).and_return(@rest_client)
-
- @out = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@out)
- @knife.config[:config_file] = "/home/you/.chef/knife.rb"
-
- @in = StringIO.new("\n" * 7)
- allow(@knife.ui).to receive(:stdin).and_return(@in)
-
- @err = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@err)
-
- allow(Ohai::System).to receive(:new).and_return(ohai)
- end
-
- let(:fqdn) { "foo.example.org" }
-
- let(:ohai) do
- o = {}
- allow(o).to receive(:all_plugins).with(%w{ os hostname fqdn })
- o[:fqdn] = fqdn
- o
- end
-
- let(:default_admin_key) { "/etc/chef-server/admin.pem" }
- let(:default_admin_key_win32) { File.expand_path(default_admin_key) }
-
- let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" }
- let(:default_validator_key_win32) { File.expand_path(default_validator_key) }
-
- let(:default_server_url) { "https://#{fqdn}/organizations/myorg" }
-
- it "asks the user for the URL of the chef server" do
- @knife.ask_user_for_config
- expect(@out.string).to match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]"))
- expect(@knife.chef_server).to eq(default_server_url)
- end
-
- it "asks the user for the clientname they want for the new client if -i is specified" do
- @knife.config[:initial] = true
- allow(Etc).to receive(:getlogin).and_return("a-new-user")
- @knife.ask_user_for_config
- expect(@out.string).to match(Regexp.escape("Please enter a name for the new user: [a-new-user]"))
- expect(@knife.new_client_name).to eq(Etc.getlogin)
- end
-
- it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do
- @knife.config[:initial] = true
- @knife.config[:node_name] = "testnode"
- allow(Etc).to receive(:getlogin).and_return("a-new-user")
- @knife.ask_user_for_config
- expect(@out.string).not_to match(Regexp.escape("Please enter a name for the new user"))
- expect(@knife.new_client_name).to eq("testnode")
- end
-
- it "asks the user for the existing API username or clientname if -i is not specified" do
- allow(Etc).to receive(:getlogin).and_return("a-new-user")
- @knife.ask_user_for_config
- expect(@out.string).to match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]"))
- expect(@knife.new_client_name).to eq(Etc.getlogin)
- end
-
- it "asks the user for the existing admin client's name if -i is specified" do
- @knife.config[:initial] = true
- @knife.ask_user_for_config
- expect(@out.string).to match(Regexp.escape("Please enter the existing admin name: [admin]"))
- expect(@knife.admin_client_name).to eq("admin")
- end
-
- it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do
- @knife.config[:initial] = true
- @knife.config[:admin_client_name] = "my-webui"
- @knife.ask_user_for_config
- expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin:"))
- expect(@knife.admin_client_name).to eq("my-webui")
- end
-
- it "should not ask the user for the existing admin client's name if -i is not specified" do
- @knife.ask_user_for_config
- expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin: [admin]"))
- expect(@knife.admin_client_name).not_to eq("admin")
- end
-
- it "asks the user for the location of the existing admin key if -i is specified" do
- @knife.config[:initial] = true
- @knife.ask_user_for_config
- expect(@out.string).to match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]"))
- if windows?
- expect(@knife.admin_client_key.capitalize).to eq(default_admin_key_win32.capitalize)
- else
- expect(@knife.admin_client_key).to eq(default_admin_key)
- end
- end
-
- it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do
- @knife.config[:initial] = true
- @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem"
- @knife.ask_user_for_config
- expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key:"))
- if windows?
- expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
- else
- expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem")
- end
- end
-
- it "should not ask the user for the location of the existing admin key if -i is not specified" do
- @knife.ask_user_for_config
- expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]"))
- if windows?
- expect(@knife.admin_client_key).not_to eq(default_admin_key_win32)
- else
- expect(@knife.admin_client_key).not_to eq(default_admin_key)
- end
- end
-
- it "should not ask the user for anything if -i and all other properties are specified" do
- @knife.config[:initial] = true
- @knife.config[:chef_server_url] = "http://localhost:5000"
- @knife.config[:node_name] = "testnode"
- @knife.config[:admin_client_name] = "my-webui"
- @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem"
- @knife.config[:client_key] = "/home/you/a-new-user.pem"
- allow(Etc).to receive(:getlogin).and_return("a-new-user")
-
- @knife.ask_user_for_config
- expect(@out.string).to match(/\s*/)
-
- expect(@knife.new_client_name).to eq("testnode")
- expect(@knife.chef_server).to eq("http://localhost:5000")
- expect(@knife.admin_client_name).to eq("my-webui")
- if windows?
- expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
- expect(@knife.new_client_key).to match %r{^[A-Za-z]:/home/you/a-new-user\.pem$}
- else
- expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem")
- expect(@knife.new_client_key).to eq("/home/you/a-new-user.pem")
- end
- end
-
- it "writes the new data to a config file" do
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef")
- allow(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials")
- allow(File).to receive(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem")
- allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key)
- expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef")
- config_file = StringIO.new
- expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w").and_yield config_file
- @knife.config[:repository] = "/home/you/chef-repo"
- @knife.run
- expect(config_file.string).to match(/^client_name\s+=\s+'#{Etc.getlogin}'$/)
- expect(config_file.string).to match(%r{^client_key\s+=\s+'/home/you/.chef/#{Etc.getlogin}.pem'$})
- expect(config_file.string).to match(/^chef_server_url\s+=\s+'#{default_server_url}'$/)
- end
-
- it "creates a new client when given the --initial option" do
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef").and_return("/home/you/.chef")
- expect(File).to receive(:expand_path).with("/home/you/.chef/credentials").and_return("/home/you/.chef/credentials")
- expect(File).to receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem")
- allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key)
- Chef::Config[:node_name] = "webmonkey.example.com"
-
- user_command = Chef::Knife::UserCreate.new
- expect(user_command).to receive(:run)
-
- allow(Etc).to receive(:getlogin).and_return("a-new-user")
-
- allow(Chef::Knife::UserCreate).to receive(:new).and_return(user_command)
- expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef")
- expect(::File).to receive(:open).with("/home/you/.chef/credentials", "w")
- @knife.config[:initial] = true
- @knife.config[:user_password] = "blah"
- @knife.run
- expect(user_command.name_args).to eq(Array("a-new-user"))
- expect(user_command.config[:user_password]).to eq("blah")
- expect(user_command.config[:admin]).to be_truthy
- expect(user_command.config[:file]).to eq("/home/you/.chef/a-new-user.pem")
- expect(user_command.config[:yes]).to be_truthy
- expect(user_command.config[:disable_editing]).to be_truthy
- end
-end
diff --git a/spec/unit/knife/cookbook_bulk_delete_spec.rb b/spec/unit/knife/cookbook_bulk_delete_spec.rb
deleted file mode 100644
index 3527d39bd8..0000000000
--- a/spec/unit/knife/cookbook_bulk_delete_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::CookbookBulkDelete do
- before(:each) do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::CookbookBulkDelete.new
- @knife.config = { print_after: nil }
- @knife.name_args = ["."]
- @stdout = StringIO.new
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- allow(@knife.ui).to receive(:confirm).and_return(true)
- @cookbooks = {}
- %w{cheezburger pizza lasagna}.each do |cookbook_name|
- cookbook = Chef::CookbookVersion.new(cookbook_name)
- @cookbooks[cookbook_name] = cookbook
- end
- @rest = double("Chef::ServerAPI")
- allow(@rest).to receive(:get).and_return(@cookbooks)
- allow(@rest).to receive(:delete).and_return(true)
- allow(@knife).to receive(:rest).and_return(@rest)
- allow(Chef::CookbookVersion).to receive(:list).and_return(@cookbooks)
-
- end
-
- describe "when there are several cookbooks on the server" do
- before do
- @cheezburger = { "cheezburger" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-cheez", "version" => "1.0.0" }] } }
- allow(@rest).to receive(:get).with("cookbooks/cheezburger").and_return(@cheezburger)
- @pizza = { "pizza" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-pizza", "version" => "2.0.0" }] } }
- allow(@rest).to receive(:get).with("cookbooks/pizza").and_return(@pizza)
- @lasagna = { "lasagna" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-lasagna", "version" => "3.0.0" }] } }
- allow(@rest).to receive(:get).with("cookbooks/lasagna").and_return(@lasagna)
- end
-
- it "should print the cookbooks you are about to delete" do
- expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down)
- @knife.run
- expect(@stdout.string).to match(/#{expected}/)
- end
-
- it "should confirm you really want to delete them" do
- expect(@knife.ui).to receive(:confirm)
- @knife.run
- end
-
- it "should delete each cookbook" do
- { "cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => "3.0.0" }.each do |cookbook_name, version|
- expect(@rest).to receive(:delete).with("cookbooks/#{cookbook_name}/#{version}")
- end
- @knife.run
- end
-
- it "should only delete cookbooks that match the regex" do
- @knife.name_args = ["cheezburger"]
- expect(@rest).to receive(:delete).with("cookbooks/cheezburger/1.0.0")
- @knife.run
- end
- end
-
- it "should exit if the regex is not provided" do
- @knife.name_args = []
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
-end
diff --git a/spec/unit/knife/cookbook_delete_spec.rb b/spec/unit/knife/cookbook_delete_spec.rb
deleted file mode 100644
index f2aa7e1be0..0000000000
--- a/spec/unit/knife/cookbook_delete_spec.rb
+++ /dev/null
@@ -1,239 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::CookbookDelete do
- before(:each) do
- @knife = Chef::Knife::CookbookDelete.new
- @knife.name_args = ["foobar"]
- @knife.cookbook_name = "foobar"
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- end
-
- describe "run" do
- it "should print usage and exit when a cookbook name is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "when specifying a cookbook name" do
- it "should delete the cookbook without a specific version" do
- expect(@knife).to receive(:delete_without_explicit_version)
- @knife.run
- end
-
- describe "and a version" do
- it "should delete the specific version of the cookbook" do
- @knife.name_args << "1.0.0"
- expect(@knife).to receive(:delete_explicit_version)
- @knife.run
- end
- end
-
- describe "with -a or --all" do
- it "should delete all versions of the cookbook" do
- @knife.config[:all] = true
- expect(@knife).to receive(:delete_all_versions)
- @knife.run
- end
- end
-
- describe "with -p or --purge" do
- it "should prompt to purge the files" do
- @knife.config[:purge] = true
- expect(@knife).to receive(:confirm)
- .with(/.+Are you sure you want to purge files.+/)
- expect(@knife).to receive(:delete_without_explicit_version)
- @knife.run
- end
- end
- end
- end
-
- describe "delete_explicit_version" do
- it "should delete the specific cookbook version" do
- @knife.cookbook_name = "foobar"
- @knife.version = "1.0.0"
- expect(@knife).to receive(:delete_object).with(Chef::CookbookVersion,
- "foobar version 1.0.0",
- "cookbook").and_yield
- expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0")
- @knife.delete_explicit_version
- end
- end
-
- describe "delete_all_versions" do
- it "should prompt to delete all versions of the cookbook" do
- @knife.cookbook_name = "foobar"
- expect(@knife).to receive(:confirm).with("Do you really want to delete all versions of foobar")
- expect(@knife).to receive(:delete_all_without_confirmation)
- @knife.delete_all_versions
- end
- end
-
- describe "delete_all_without_confirmation" do
- it "should delete all versions without confirmation" do
- versions = ["1.0.0", "1.1.0"]
- expect(@knife).to receive(:available_versions).and_return(versions)
- versions.each do |v|
- expect(@knife).to receive(:delete_version_without_confirmation).with(v)
- end
- @knife.delete_all_without_confirmation
- end
- end
-
- describe "delete_without_explicit_version" do
- it "should exit if there are no available versions" do
- expect(@knife).to receive(:available_versions).and_return(nil)
- expect { @knife.delete_without_explicit_version }.to raise_error(SystemExit)
- end
-
- it "should delete the version if only one is found" do
- expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"])
- expect(@knife).to receive(:delete_explicit_version)
- @knife.delete_without_explicit_version
- end
-
- it "should ask which version(s) to delete if multiple are found" do
- expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "1.1.0"])
- expect(@knife).to receive(:ask_which_versions_to_delete).and_return(["1.0.0", "1.1.0"])
- expect(@knife).to receive(:delete_versions_without_confirmation).with(["1.0.0", "1.1.0"])
- @knife.delete_without_explicit_version
- end
- end
-
- describe "available_versions" do
- before(:each) do
- @rest_mock = double("rest")
- expect(@knife).to receive(:rest).and_return(@rest_mock)
- @cookbook_data = { "foobar" => { "versions" => [{ "version" => "1.0.0" },
- { "version" => "1.1.0" },
- { "version" => "2.0.0" } ] },
- }
- end
-
- it "should return the list of versions of the cookbook" do
- expect(@rest_mock).to receive(:get).with("cookbooks/foobar").and_return(@cookbook_data)
- expect(@knife.available_versions).to eq(["1.0.0", "1.1.0", "2.0.0"])
- end
-
- it "should raise if an error other than HTTP 404 is returned" do
- exception = Net::HTTPClientException.new("500 Internal Server Error", "500")
- expect(@rest_mock).to receive(:get).and_raise(exception)
- expect { @knife.available_versions }.to raise_error Net::HTTPClientException
- end
-
- describe "if the cookbook can't be found" do
- before(:each) do
- expect(@rest_mock).to receive(:get)
- .and_raise(Net::HTTPClientException.new("404 Not Found", "404"))
- end
-
- it "should print an error" do
- @knife.available_versions
- expect(@stderr.string).to match(/error.+cannot find a cookbook named foobar/i)
- end
-
- it "should return nil" do
- expect(@knife.available_versions).to eq(nil)
- end
- end
- end
-
- describe "ask_which_version_to_delete" do
- before(:each) do
- allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"])
- end
-
- it "should prompt the user to select a version" do
- prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m
- expect(@knife).to receive(:ask_question).with(prompt).and_return("1")
- @knife.ask_which_versions_to_delete
- end
-
- it "should print an error and exit if a version wasn't specified" do
- expect(@knife).to receive(:ask_question).and_return("")
- expect(@knife.ui).to receive(:error).with(/no versions specified/i)
- expect { @knife.ask_which_versions_to_delete }.to raise_error(SystemExit)
- end
-
- it "should print an error if an invalid choice was selected" do
- expect(@knife).to receive(:ask_question).and_return("100")
- expect(@knife.ui).to receive(:error).with(/100 is not a valid choice/i)
- @knife.ask_which_versions_to_delete
- end
-
- it "should return the selected versions" do
- expect(@knife).to receive(:ask_question).and_return("1, 3")
- expect(@knife.ask_which_versions_to_delete).to eq(["1.0.0", "2.0.0"])
- end
-
- it "should return all of the versions if 'all' was selected" do
- expect(@knife).to receive(:ask_question).and_return("4")
- expect(@knife.ask_which_versions_to_delete).to eq([:all])
- end
- end
-
- describe "delete_version_without_confirmation" do
- it "should delete the cookbook version" do
- expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0")
- @knife.delete_version_without_confirmation("1.0.0")
- end
-
- it "should output that the cookbook was deleted" do
- allow(@knife).to receive(:delete_request)
- @knife.delete_version_without_confirmation("1.0.0")
- expect(@stderr.string).to match(/deleted cookbook\[foobar\]\[1.0.0\]/im)
- end
-
- describe "with --print-after" do
- it "should display the cookbook data" do
- object = ""
- @knife.config[:print_after] = true
- allow(@knife).to receive(:delete_request).and_return(object)
- expect(@knife).to receive(:format_for_display).with(object)
- @knife.delete_version_without_confirmation("1.0.0")
- end
- end
- end
-
- describe "delete_versions_without_confirmation" do
- it "should delete each version without confirmation" do
- versions = ["1.0.0", "1.1.0"]
- versions.each do |v|
- expect(@knife).to receive(:delete_version_without_confirmation).with(v)
- end
- @knife.delete_versions_without_confirmation(versions)
- end
-
- describe "with -a or --all" do
- it "should delete all versions without confirmation" do
- versions = [:all]
- expect(@knife).to receive(:delete_all_without_confirmation)
- @knife.delete_versions_without_confirmation(versions)
- end
- end
- end
-
-end
diff --git a/spec/unit/knife/cookbook_download_spec.rb b/spec/unit/knife/cookbook_download_spec.rb
deleted file mode 100644
index c8903dea5b..0000000000
--- a/spec/unit/knife/cookbook_download_spec.rb
+++ /dev/null
@@ -1,255 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::CookbookDownload do
- before(:each) do
- @knife = Chef::Knife::CookbookDownload.new
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- end
-
- describe "run" do
- it "should print usage and exit when a cookbook name is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal).with(/must specify a cookbook name/)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should exit with a fatal error when there is no cookbook on the server" do
- @knife.name_args = ["foobar", nil]
- expect(@knife).to receive(:determine_version).and_return(nil)
- expect(@knife.ui).to receive(:fatal).with("No such cookbook found")
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "with a cookbook name" do
- before(:each) do
- @knife.name_args = ["foobar"]
- @knife.config[:download_directory] = "/var/tmp/chef"
- @rest_mock = double("rest")
- allow(@knife).to receive(:rest).and_return(@rest_mock)
-
- expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0")
- .and_return(cookbook)
- end
-
- let(:manifest_data) do
- {
- all_files: [
- {
- "path" => "recipes/foo.rb",
- "name" => "recipes/foo.rb",
- "url" => "http://example.org/files/foo.rb",
- },
- {
- "path" => "recipes/bar.rb",
- "name" => "recipes/bar.rb",
- "url" => "http://example.org/files/bar.rb",
- },
- {
- "path" => "templates/default/foo.erb",
- "name" => "templates/foo.erb",
- "url" => "http://example.org/files/foo.erb",
- },
- {
- "path" => "templates/default/bar.erb",
- "name" => "templates/bar.erb",
- "url" => "http://example.org/files/bar.erb",
- },
- {
- "path" => "attributes/default.rb",
- "name" => "attributes/default.rb",
- "url" => "http://example.org/files/default.rb",
- },
- ],
- }
- end
-
- let(:cookbook) do
- cb = Chef::CookbookVersion.new("foobar")
- cb.version = "1.0.0"
- cb.manifest = manifest_data
- cb
- end
-
- describe "and no version" do
- let(:manifest_data) { { all_files: [] } }
- it "should determine which version to download" do
- expect(@knife).to receive(:determine_version).and_return("1.0.0")
- expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false)
- @knife.run
- end
- end
-
- describe "and a version" do
- before(:each) do
- @knife.name_args << "1.0.0"
- @files = manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq
- @files_mocks = {}
- @files.map { |f| File.basename(f) }.flatten.uniq.each do |f|
- @files_mocks[f] = double("#{f}_mock")
- allow(@files_mocks[f]).to receive(:path).and_return("/var/tmp/#{f}")
- end
- end
-
- it "should print an error and exit if the cookbook download directory already exists" do
- expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true)
- expect(@knife.ui).to receive(:fatal).with(%r{/var/tmp/chef/foobar-1\.0\.0 exists}i)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "when downloading the cookbook" do
- before(:each) do
- @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir|
- expect(FileUtils).to receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}")
- .at_least(:once)
- end
-
- @files_mocks.each_pair do |file, mock|
- expect(@rest_mock).to receive(:streaming_request).with("http://example.org/files/#{file}")
- .and_return(mock)
- end
-
- @files.each do |f|
- expect(FileUtils).to receive(:mv)
- .with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}")
- end
- end
-
- it "should download the cookbook when the cookbook download directory doesn't exist" do
- expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(false)
- @knife.run
- %w{attributes recipes templates}.each do |segment|
- expect(@stderr.string).to match(/downloading #{segment}/im)
- end
- expect(@stderr.string).to match(/downloading foobar cookbook version 1\.0\.0/im)
- expect(@stderr.string).to match %r{cookbook downloaded to /var/tmp/chef/foobar-1\.0\.0}im
- end
-
- describe "with -f or --force" do
- it "should remove the existing the cookbook download directory if it exists" do
- @knife.config[:force] = true
- expect(File).to receive(:exist?).with("/var/tmp/chef/foobar-1.0.0").and_return(true)
- expect(FileUtils).to receive(:rm_rf).with("/var/tmp/chef/foobar-1.0.0")
- @knife.run
- end
- end
- end
-
- end
- end
-
- end
-
- describe "determine_version" do
-
- it "should return nil if there are no versions" do
- expect(@knife).to receive(:available_versions).and_return(nil)
- expect(@knife.determine_version).to eq(nil)
- expect(@knife.version).to eq(nil)
- end
-
- it "should return and set the version if there is only one version" do
- expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"])
- expect(@knife.determine_version).to eq("1.0.0")
- expect(@knife.version).to eq("1.0.0")
- end
-
- it "should ask which version to download and return it if there is more than one" do
- expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "2.0.0"])
- expect(@knife).to receive(:ask_which_version).and_return("1.0.0")
- expect(@knife.determine_version).to eq("1.0.0")
- end
-
- describe "with -N or --latest" do
- it "should return and set the version to the latest version" do
- @knife.config[:latest] = true
- expect(@knife).to receive(:available_versions).at_least(:once)
- .and_return(["1.0.0", "1.1.0", "2.0.0"])
- @knife.determine_version
- expect(@knife.version.to_s).to eq("2.0.0")
- end
- end
- end
-
- describe "available_versions" do
- before(:each) do
- @knife.cookbook_name = "foobar"
- end
-
- it "should return nil if there are no versions" do
- expect(Chef::CookbookVersion).to receive(:available_versions)
- .with("foobar")
- .and_return(nil)
- expect(@knife.available_versions).to eq(nil)
- end
-
- it "should return the available versions" do
- expect(Chef::CookbookVersion).to receive(:available_versions)
- .with("foobar")
- .and_return(["1.1.0", "2.0.0", "1.0.0"])
- expect(@knife.available_versions).to eq([Chef::Version.new("1.0.0"),
- Chef::Version.new("1.1.0"),
- Chef::Version.new("2.0.0")])
- end
-
- it "should avoid multiple API calls to the server" do
- expect(Chef::CookbookVersion).to receive(:available_versions)
- .once
- .with("foobar")
- .and_return(["1.1.0", "2.0.0", "1.0.0"])
- @knife.available_versions
- @knife.available_versions
- end
- end
-
- describe "ask_which_version" do
- before(:each) do
- @knife.cookbook_name = "foobar"
- allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"])
- end
-
- it "should prompt the user to select a version" do
- prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m
- expect(@knife).to receive(:ask_question).with(prompt).and_return("1")
- @knife.ask_which_version
- end
-
- it "should set the version to the user's selection" do
- expect(@knife).to receive(:ask_question).and_return("1")
- @knife.ask_which_version
- expect(@knife.version).to eq("1.0.0")
- end
-
- it "should print an error and exit if a version wasn't specified" do
- expect(@knife).to receive(:ask_question).and_return("")
- expect(@knife.ui).to receive(:error).with(/is not a valid value/i)
- expect { @knife.ask_which_version }.to raise_error(SystemExit)
- end
-
- it "should print an error if an invalid choice was selected" do
- expect(@knife).to receive(:ask_question).and_return("100")
- expect(@knife.ui).to receive(:error).with(/'100' is not a valid value/i)
- expect { @knife.ask_which_version }.to raise_error(SystemExit)
- end
- end
-
-end
diff --git a/spec/unit/knife/cookbook_list_spec.rb b/spec/unit/knife/cookbook_list_spec.rb
deleted file mode 100644
index 4cf806c6f0..0000000000
--- a/spec/unit/knife/cookbook_list_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::CookbookList do
- before do
- @knife = Chef::Knife::CookbookList.new
- @rest_mock = double("rest")
- allow(@knife).to receive(:rest).and_return(@rest_mock)
- @cookbook_names = %w{apache2 mysql}
- @base_url = "https://server.example.com/cookbooks"
- @cookbook_data = {}
- @cookbook_names.each do |item|
- @cookbook_data[item] = { "url" => "#{@base_url}/#{item}",
- "versions" => [{ "version" => "1.0.1",
- "url" => "#{@base_url}/#{item}/1.0.1" }] }
- end
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should display the latest version of the cookbooks" do
- expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=1")
- .and_return(@cookbook_data)
- @knife.run
- @cookbook_names.each do |item|
- expect(@stdout.string).to match(/#{item}\s+1\.0\.1/)
- end
- end
-
- it "should query cookbooks for the configured environment" do
- @knife.config[:environment] = "production"
- expect(@rest_mock).to receive(:get)
- .with("/environments/production/cookbooks?num_versions=1")
- .and_return(@cookbook_data)
- @knife.run
- end
-
- describe "with -w or --with-uri" do
- it "should display the cookbook uris" do
- @knife.config[:with_uri] = true
- allow(@rest_mock).to receive(:get).and_return(@cookbook_data)
- @knife.run
- @cookbook_names.each do |item|
- pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/
- expect(@stdout.string).to match pattern
- end
- end
- end
-
- describe "with -a or --all" do
- before do
- @cookbook_names.each do |item|
- @cookbook_data[item]["versions"] << { "version" => "1.0.0",
- "url" => "#{@base_url}/#{item}/1.0.0" }
- end
- end
-
- it "should display all versions of the cookbooks" do
- @knife.config[:all_versions] = true
- expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=all")
- .and_return(@cookbook_data)
- @knife.run
- @cookbook_names.each do |item|
- expect(@stdout.string).to match(/#{item}\s+1\.0\.1\s+1\.0\.0/)
- end
- end
- end
-
- end
-end
diff --git a/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/spec/unit/knife/cookbook_metadata_from_file_spec.rb
deleted file mode 100644
index f9bbffae2d..0000000000
--- a/spec/unit/knife/cookbook_metadata_from_file_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Copyright:: Copyright (c) Chef Software Inc.
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::CookbookMetadataFromFile do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb"))
- @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json"))
- @knife = Chef::Knife::CookbookMetadataFromFile.new
- @knife.name_args = [ @src ]
- allow(@knife).to receive(:to_json_pretty).and_return(true)
- @md = Chef::Cookbook::Metadata.new
- allow(Chef::Cookbook::Metadata).to receive(:new).and_return(@md)
- allow($stdout).to receive(:write)
- end
-
- after do
- if File.exist?(@tgt)
- File.unlink(@tgt)
- end
- end
-
- describe "run" do
- it "should print usage and exit when a FILE is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal).with(/You must specify the FILE./)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should determine cookbook name from path" do
- expect(@md).to receive(:name).with(no_args)
- expect(@md).to receive(:name).with("quick_start")
- @knife.run
- end
-
- it "should load the metadata source" do
- expect(@md).to receive(:from_file).with(@src)
- @knife.run
- end
-
- it "should write out the metadata to the correct location" do
- expect(File).to receive(:open).with(@tgt, "w")
- @knife.run
- end
-
- it "should generate json from the metadata" do
- expect(Chef::JSONCompat).to receive(:to_json_pretty).with(@md)
- @knife.run
- end
-
- end
-end
diff --git a/spec/unit/knife/cookbook_metadata_spec.rb b/spec/unit/knife/cookbook_metadata_spec.rb
deleted file mode 100644
index 732cf78421..0000000000
--- a/spec/unit/knife/cookbook_metadata_spec.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2011-2016, Thomas Bishop
-# 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 "spec_helper"
-
-describe Chef::Knife::CookbookMetadata do
- let(:knife) do
- knife = Chef::Knife::CookbookMetadata.new
- knife.name_args = ["foobar"]
- knife
- end
-
- let(:cookbook_dir) { Dir.mktmpdir }
-
- let(:stdout) { StringIO.new }
-
- let(:stderr) { StringIO.new }
-
- before(:each) do
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(knife.ui).to receive(:stderr).and_return(stderr)
- end
-
- def create_metadata_rb(**kwargs)
- name = kwargs[:name]
- Dir.mkdir("#{cookbook_dir}/#{name}")
- File.open("#{cookbook_dir}/#{name}/metadata.rb", "w+") do |f|
- kwargs.each do |key, value|
- if value.is_a?(Array)
- f.puts "#{key} #{value.map { |v| "\"#{v}\"" }.join(", ")}"
- else
- f.puts "#{key} \"#{value}\""
- end
- end
- end
- end
-
- def create_metadata_json(**kwargs)
- name = kwargs[:name]
- Dir.mkdir("#{cookbook_dir}/#{name}")
- File.open("#{cookbook_dir}/#{name}/metadata.json", "w+") do |f|
- f.write(FFI_Yajl::Encoder.encode(kwargs))
- end
- end
-
- def create_invalid_json
- Dir.mkdir("#{cookbook_dir}/foobar")
- File.open("#{cookbook_dir}/foobar/metadata.json", "w+") do |f|
- f.write <<-EOH
- { "version": "1.0.0", {ImInvalid}}
- EOH
- end
- end
-
- describe "run" do
- it "should print an error and exit if a cookbook name was not provided" do
- knife.name_args = []
- expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "should print an error and exit if an empty cookbook name was provided" do
- knife.name_args = [""]
- expect(knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "should generate the metadata for the cookbook" do
- expect(knife).to receive(:generate_metadata).with("foobar")
- knife.run
- end
-
- describe "with -a or --all" do
- before(:each) do
- Chef::Config[:cookbook_path] = cookbook_dir
- knife.config[:all] = true
- create_metadata_rb(name: "foo", version: "1.0.0")
- create_metadata_rb(name: "bar", version: "2.0.0")
- expect(knife).to receive(:generate_metadata).with("foo").and_call_original
- expect(knife).to receive(:generate_metadata).with("bar").and_call_original
- end
-
- it "should generate the metadata for each cookbook" do
- expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original
- knife.run
- expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im
- expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im
- end
-
- it "with -o or --cookbook_path should look in the provided path and generate cookbook metadata" do
- Chef::Config[:cookbook_path] = "/dev/null"
- knife.config[:cookbook_path] = cookbook_dir
- expect(Chef::CookbookLoader).to receive(:new).with(cookbook_dir).and_call_original
- knife.run
- expect(stderr.string).to match %r{generating metadata for foo from #{cookbook_dir}/foo/metadata\.rb}im
- expect(stderr.string).to match %r{generating metadata for bar from #{cookbook_dir}/bar/metadata\.rb}im
- end
- end
-
- end
-
- describe "generate_metadata" do
- before(:each) do
- Chef::Config[:cookbook_path] = cookbook_dir
- end
-
- it "should generate the metadata from metadata.rb if it exists" do
- create_metadata_rb(name: "foobar", version: "1.0.0")
- expect(knife).to receive(:generate_metadata_from_file).with("foobar", "#{cookbook_dir}/foobar/metadata.rb").and_call_original
- knife.run
- expect(File.exist?("#{cookbook_dir}/foobar/metadata.json")).to be true
- json = FFI_Yajl::Parser.parse(IO.read("#{cookbook_dir}/foobar/metadata.json"))
- expect(json["name"]).to eql("foobar")
- expect(json["version"]).to eql("1.0.0")
- end
-
- it "should validate the metadata json if metadata.rb does not exist" do
- create_metadata_json(name: "foobar", version: "1.0.0")
- expect(knife).to receive(:validate_metadata_json).with(cookbook_dir, "foobar").and_call_original
- knife.run
- end
- end
-
- describe "validation errors" do
- before(:each) do
- Chef::Config[:cookbook_path] = cookbook_dir
- end
-
- it "should fail for obsolete operators in metadata.rb" do
- create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", ">> 0.2" ])
- expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
- end
-
- it "should fail for obsolete format in metadata.rb (sadly)" do
- create_metadata_rb(name: "foobar", version: "1.0.0", depends: [ "foo:bar", "> 0.2", "< 1.0" ])
- expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
- end
-
- it "should fail for obsolete operators in metadata.json" do
- create_metadata_json(name: "foobar", version: "1.0.0", dependencies: { "foo:bar" => ">> 0.2" })
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im)
- end
-
- it "should not fail for unknown field in metadata.rb" do
- create_metadata_rb(name: "sounders", version: "2.0.0", beats: "toronto")
- expect(Chef::Cookbook::Metadata).not_to receive(:validate_json)
- expect { knife.run }.not_to raise_error
- expect(stderr.string).to eql("")
- end
-
- it "should not fail for unknown field in metadata.json" do
- create_metadata_json(name: "sounders", version: "2.0.0", beats: "toronto")
- expect { knife.run }.not_to raise_error
- expect(stderr.string).to eql("")
- end
-
- it "should fail on unparsable json" do
- create_invalid_json
- expect { knife.run }.to raise_error(Chef::Exceptions::JSON::ParseError)
- end
- end
-end
diff --git a/spec/unit/knife/cookbook_show_spec.rb b/spec/unit/knife/cookbook_show_spec.rb
deleted file mode 100644
index defc243de3..0000000000
--- a/spec/unit/knife/cookbook_show_spec.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::CookbookShow do
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- allow(knife).to receive(:rest).and_return(rest)
- allow(knife).to receive(:pretty_print).and_return(true)
- allow(knife).to receive(:output).and_return(true)
- allow(Chef::CookbookVersion).to receive(:load).and_return(cb)
- end
-
- let(:knife) do
- knife = Chef::Knife::CookbookShow.new
- knife.config = {}
- knife.name_args = [ "cookbook_name" ]
- knife
- end
-
- let(:cb) do
- cb = Chef::CookbookVersion.new("cookbook_name")
- cb.manifest = manifest
- cb
- end
-
- let(:rest) { double(Chef::ServerAPI) }
-
- let(:content) { "Example recipe text" }
-
- let(:manifest) do
- {
- "all_files" => [
- {
- name: "recipes/default.rb",
- path: "recipes/default.rb",
- checksum: "1234",
- url: "http://example.org/files/default.rb",
- },
- ],
- }
- end
-
- describe "run" do
- describe "with 0 arguments: help" do
- it "should should print usage and exit when given no arguments" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
- end
-
- describe "with 1 argument: versions" do
- let(:response) do
- {
- "cookbook_name" => {
- "url" => "http://url/cookbooks/cookbook_name",
- "versions" => [
- { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" },
- { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" },
- { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" },
- ],
- },
- }
- end
-
- it "should show the raw cookbook data" do
- expect(rest).to receive(:get).with("cookbooks/cookbook_name").and_return(response)
- expect(knife).to receive(:format_cookbook_list_for_display).with(response)
- knife.run
- end
-
- it "should respect the user-supplied environment" do
- knife.config[:environment] = "foo"
- expect(rest).to receive(:get).with("environments/foo/cookbooks/cookbook_name").and_return(response)
- expect(knife).to receive(:format_cookbook_list_for_display).with(response)
- knife.run
- end
- end
-
- describe "with 2 arguments: name and version" do
- before do
- knife.name_args << "0.1.0"
- end
-
- let(:output) do
- { "cookbook_name" => "cookbook_name",
- "name" => "cookbook_name-0.0.0",
- "frozen?" => false,
- "version" => "0.0.0",
- "metadata" => {
- "name" => nil,
- "description" => "",
- "eager_load_libraries" => true,
- "long_description" => "",
- "maintainer" => "",
- "maintainer_email" => "",
- "license" => "All rights reserved",
- "platforms" => {},
- "dependencies" => {},
- "providing" => {},
- "recipes" => {},
- "version" => "0.0.0",
- "source_url" => "",
- "issues_url" => "",
- "privacy" => false,
- "chef_versions" => [],
- "ohai_versions" => [],
- "gems" => [],
- },
- "recipes" =>
- [{ "name" => "recipes/default.rb",
- "path" => "recipes/default.rb",
- "checksum" => "1234",
- "url" => "http://example.org/files/default.rb" }],
- }
- end
-
- it "should show the specific part of a cookbook" do
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(knife).to receive(:output).with(output)
- knife.run
- end
- end
-
- describe "with 3 arguments: name, version, and segment" do
- before(:each) do
- knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ]
- end
-
- it "should print the json of the part" do
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(knife).to receive(:output).with(cb.files_for("recipes"))
- knife.run
- end
- end
-
- describe "with 4 arguments: name, version, segment and filename" do
- before(:each) do
- knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ]
- end
-
- it "should print the raw result of the request (likely a file!)" do
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(rest).to receive(:streaming_request).with("http://example.org/files/default.rb").and_return(StringIO.new(content))
- expect(knife).to receive(:pretty_print).with(content)
- knife.run
- end
- end
-
- describe "with 4 arguments: name, version, segment and filename -- with specificity" do
- before(:each) do
- knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ]
- cb.manifest = {
- "all_files" => [
- {
- name: "files/afile.rb",
- path: "files/host-examplehost.example.org/afile.rb",
- checksum: "1111",
- specificity: "host-examplehost.example.org",
- url: "http://example.org/files/1111",
- },
- {
- name: "files/afile.rb",
- path: "files/ubuntu-9.10/afile.rb",
- checksum: "2222",
- specificity: "ubuntu-9.10",
- url: "http://example.org/files/2222",
- },
- {
- name: "files/afile.rb",
- path: "files/ubuntu/afile.rb",
- checksum: "3333",
- specificity: "ubuntu",
- url: "http://example.org/files/3333",
- },
- {
- name: "files/afile.rb",
- path: "files/default/afile.rb",
- checksum: "4444",
- specificity: "default",
- url: "http://example.org/files/4444",
- },
- ],
- }
-
- end
-
- describe "with --fqdn" do
- it "should pass the fqdn" do
- knife.config[:platform] = "example_platform"
- knife.config[:platform_version] = "1.0"
- knife.config[:fqdn] = "examplehost.example.org"
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(rest).to receive(:streaming_request).with("http://example.org/files/1111").and_return(StringIO.new(content))
- expect(knife).to receive(:pretty_print).with(content)
- knife.run
- end
- end
-
- describe "and --platform" do
- it "should pass the platform" do
- knife.config[:platform] = "ubuntu"
- knife.config[:platform_version] = "1.0"
- knife.config[:fqdn] = "differenthost.example.org"
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(rest).to receive(:streaming_request).with("http://example.org/files/3333").and_return(StringIO.new(content))
- expect(knife).to receive(:pretty_print).with(content)
- knife.run
- end
- end
-
- describe "and --platform-version" do
- it "should pass the platform" do
- knife.config[:platform] = "ubuntu"
- knife.config[:platform_version] = "9.10"
- knife.config[:fqdn] = "differenthost.example.org"
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(rest).to receive(:streaming_request).with("http://example.org/files/2222").and_return(StringIO.new(content))
- expect(knife).to receive(:pretty_print).with(content)
- knife.run
- end
- end
-
- describe "with none of the arguments, it should use the default" do
- it "should pass them all" do
- expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb)
- expect(rest).to receive(:streaming_request).with("http://example.org/files/4444").and_return(StringIO.new(content))
- expect(knife).to receive(:pretty_print).with(content)
- knife.run
- end
- end
-
- end
- end
-end
diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb
deleted file mode 100644
index dbed8b8a67..0000000000
--- a/spec/unit/knife/cookbook_upload_spec.rb
+++ /dev/null
@@ -1,364 +0,0 @@
-#
-# Author:: Matthew Kent (<mkent@magoazul.com>)
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-require "chef/cookbook_uploader"
-require "timeout"
-
-describe Chef::Knife::CookbookUpload do
- let(:cookbook) do
- cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
- cookbook
- end
-
- let(:cookbooks_by_name) do
- { cookbook.name => cookbook }
- end
-
- let(:cookbook_loader) do
- cookbook_loader = cookbooks_by_name.dup
- allow(cookbook_loader).to receive(:merged_cookbooks).and_return([])
- allow(cookbook_loader).to receive(:load_cookbooks).and_return(cookbook_loader)
- allow(cookbook_loader).to receive(:compile_metadata).and_return(nil)
- allow(cookbook_loader).to receive(:freeze_versions).and_return(nil)
- cookbook_loader
- end
-
- let(:cookbook_uploader) { double(upload_cookbooks: nil) }
-
- let(:output) { StringIO.new }
-
- let(:name_args) { ["test_cookbook"] }
-
- let(:knife) do
- k = Chef::Knife::CookbookUpload.new
- k.name_args = name_args
- allow(k.ui).to receive(:stdout).and_return(output)
- allow(k.ui).to receive(:stderr).and_return(output)
- k
- end
-
- before(:each) do
- allow(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader)
- allow(Chef::CookbookLoader).to receive(:copy_to_tmp_dir_from_array).and_yield(cookbook_loader)
- end
-
- describe "with --concurrency" do
- it "should upload cookbooks with predefined concurrency" do
- allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
- knife.config[:concurrency] = 3
- test_cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah")
- allow(cookbook_loader).to receive(:each).and_yield("test_cookbook", test_cookbook)
- allow(cookbook_loader).to receive(:cookbook_names).and_return(["test_cookbook"])
- expect(Chef::CookbookUploader).to receive(:new)
- .with( kind_of(Array), { force: nil, concurrency: 3 })
- .and_return(double("Chef::CookbookUploader", upload_cookbooks: true))
- knife.run
- end
- end
-
- describe "run" do
- before(:each) do
- allow(Chef::CookbookUploader).to receive_messages(new: cookbook_uploader)
- allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({})
- end
-
- it "should print usage and exit when a cookbook name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- describe "when specifying cookbook without metadata.rb or metadata.json" do
- let(:name_args) { ["test_cookbook1"] }
- let(:cookbook) do
- cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(false)
- cookbook
- end
-
- it "should upload the cookbook" do
- expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotFound)
- end
- end
-
- describe "when name attribute in metadata not set" do
- let(:name_args) { ["test_cookbook1"] }
-
- let(:cookbook) do
- cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(nil)
- cookbook
- end
-
- it "should upload the cookbook" do
- expect { knife.run }.to raise_error(Chef::Exceptions::MetadataNotValid)
- end
- end
-
- describe "when specifying a cookbook name" do
- it "should upload the cookbook" do
- expect(knife).to receive(:upload).once
- knife.run
- end
-
- it "should report on success" do
- expect(knife).to receive(:upload).once
- expect(knife.ui).to receive(:info).with(/Uploaded 1 cookbook/)
- knife.run
- end
- end
-
- describe "when specifying the same cookbook name twice" do
- it "should upload the cookbook only once" do
- knife.name_args = %w{test_cookbook test_cookbook}
- expect(knife).to receive(:upload).once
- knife.run
- end
- end
-
- describe "when specifying a cookbook name among many" do
- let(:name_args) { ["test_cookbook1"] }
-
- let(:cookbook) do
- cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
- cookbook
- end
-
- let(:cookbooks_by_name) do
- { cookbook.name => cookbook }
- end
-
- it "should read only one cookbook" do
- expect(cookbook_loader).to receive(:[]).once.with("test_cookbook1").and_call_original
- knife.run
- end
-
- it "should not read all cookbooks" do
- expect(cookbook_loader).to receive(:load_cookbooks)
- knife.run
- end
-
- it "should upload only one cookbook" do
- expect(knife).to receive(:upload).exactly(1).times
- knife.run
- end
- end
-
- # This is testing too much. We should break it up.
- describe "when specifying a cookbook name with dependencies" do
- let(:name_args) { ["test_cookbook2"] }
-
- let(:test_cookbook1) do
- cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
- cookbook
- end
-
- let(:test_cookbook2) do
- c = Chef::CookbookVersion.new("test_cookbook2")
- c.metadata.depends("test_cookbook3")
- allow(c).to receive(:has_metadata_file?).and_return(true)
- allow(c.metadata).to receive(:name).and_return(c.name)
- c
- end
-
- let(:test_cookbook3) do
- c = Chef::CookbookVersion.new("test_cookbook3")
- c.metadata.depends("test_cookbook1")
- c.metadata.depends("test_cookbook2")
- allow(c).to receive(:has_metadata_file?).and_return(true)
- allow(c.metadata).to receive(:name).and_return(c.name)
- c
- end
-
- let(:cookbooks_by_name) do
- { "test_cookbook1" => test_cookbook1,
- "test_cookbook2" => test_cookbook2,
- "test_cookbook3" => test_cookbook3 }
- end
-
- it "should upload all dependencies once" do
- knife.config[:depends] = true
- allow(knife).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2 test_cookbook3})
- expect(knife).to receive(:upload).exactly(3).times
- expect do
- Timeout.timeout(5) do
- knife.run
- end
- end.not_to raise_error
- end
- end
-
- describe "when specifying a cookbook name with missing dependencies" do
- let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") }
-
- before(:each) do
- cookbook.metadata.depends("dependency")
- allow(cookbook_loader).to receive(:[]) do |ckbk|
- { "test_cookbook" => cookbook,
- "dependency" => cookbook_dependency }[ckbk]
- end
- allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook})
- @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
- knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
- end
-
- it "should exit and not upload the cookbook" do
- expect(cookbook_loader).to receive(:[]).once.with("test_cookbook")
- expect(cookbook_uploader).not_to receive(:upload_cookbooks)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "should output a message for a single missing dependency" do
- expect { knife.run }.to raise_error(SystemExit)
- expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
- expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
- expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'")
- end
-
- it "should output a message for a multiple missing dependencies which are concatenated" do
- cookbook_dependency2 = Chef::CookbookVersion.new("dependency2")
- cookbook.metadata.depends("dependency2")
- allow(cookbook_loader).to receive(:[]) do |ckbk|
- { "test_cookbook" => cookbook,
- "dependency" => cookbook_dependency,
- "dependency2" => cookbook_dependency2 }[ckbk]
- end
- allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook})
- expect { knife.run }.to raise_error(SystemExit)
- expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently")
- expect(@stderr.string).to include("being uploaded and cannot be found on the server.")
- expect(@stderr.string).to include("The missing cookbook(s) are:")
- expect(@stderr.string).to include("'dependency' version '>= 0.0.0'")
- expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'")
- end
- end
-
- it "should freeze the version of the cookbooks if --freeze is specified" do
- knife.config[:freeze] = true
- expect(cookbook_loader).to receive(:freeze_versions).once
- knife.run
- end
-
- describe "with -a or --all" do
- before(:each) do
- knife.config[:all] = true
- end
-
- context "when cookbooks exist in the cookbook path" do
- let(:test_cookbook1) do
- cookbook = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
- cookbook
- end
-
- let(:test_cookbook2) do
- cookbook = Chef::CookbookVersion.new("test_cookbook2", "/tmp/blah")
- allow(cookbook).to receive(:has_metadata_file?).and_return(true)
- allow(cookbook.metadata).to receive(:name).and_return(cookbook.name)
- cookbook
- end
-
- before(:each) do
- allow(cookbook_loader).to receive(:each).and_yield("test_cookbook1", test_cookbook1).and_yield("test_cookbook2", test_cookbook2)
- allow(cookbook_loader).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2})
- end
-
- it "should upload all cookbooks" do
- expect(knife).to receive(:upload).once
- knife.run
- end
-
- it "should report on success" do
- expect(knife).to receive(:upload).once
- expect(knife.ui).to receive(:info).with(/Uploaded all cookbooks/)
- knife.run
- end
-
- it "should update the version constraints for an environment" do
- allow(knife).to receive(:assert_environment_valid!).and_return(true)
- knife.config[:environment] = "production"
- expect(knife).to receive(:update_version_constraints).once
- knife.run
- end
- end
-
- context "when no cookbooks exist in the cookbook path" do
- before(:each) do
- allow(cookbook_loader).to receive(:each)
- end
-
- it "should not upload any cookbooks" do
- expect(knife).to_not receive(:upload)
- knife.run
- end
-
- context "when cookbook path is an array" do
- it "should warn users that no cookbooks exist" do
- cookbook_path = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks"
- knife.config[:cookbook_path] = [cookbook_path, "/home/user/cookbooks"]
- expect(knife.ui).to receive(:warn).with("Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path].join(", ")}'. Use --cookbook-path to specify the desired path.")
- knife.run
- end
- end
-
- context "when cookbook path is a string" do
- it "should warn users that no cookbooks exist" do
- knife.config[:cookbook_path] = windows? ? "C:/chef-repo/cookbooks" : "/chef-repo/cookbooks"
- expect(knife.ui).to receive(:warn).with(
- "Could not find any cookbooks in your cookbook path: '#{knife.config[:cookbook_path]}'. Use --cookbook-path to specify the desired path."
- )
- knife.run
- end
- end
- end
- end
-
- describe "when a frozen cookbook exists on the server" do
- it "should fail to replace it" do
- exception = Chef::Exceptions::CookbookFrozen.new
- expect(cookbook_uploader).to receive(:upload_cookbooks)
- .and_raise(exception)
- allow(knife.ui).to receive(:error)
- expect(knife.ui).to receive(:error).with(exception)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "should not update the version constraints for an environment" do
- allow(knife).to receive(:assert_environment_valid!).and_return(true)
- knife.config[:environment] = "production"
- allow(knife).to receive(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
- expect(knife.ui).to receive(:error).with(/Failed to upload 1 cookbook/)
- expect(knife.ui).to receive(:warn).with(/Not updating version constraints/)
- expect(knife).not_to receive(:update_version_constraints)
- expect { knife.run }.to raise_error(SystemExit)
- end
- end
- end # run
-end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
deleted file mode 100644
index a55047a739..0000000000
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ /dev/null
@@ -1,287 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/core/bootstrap_context"
-
-describe Chef::Knife::Core::BootstrapContext do
- let(:config) { { foo: :bar, color: true } }
- let(:run_list) { Chef::RunList.new("recipe[tmux]", "role[base]") }
- let(:chef_config) do
- {
- config_log_level: "info",
- config_log_location: "/tmp/log",
- validation_key: File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"),
- chef_server_url: "http://chef.example.com:4444",
- validation_client_name: "chef-validator-testing",
- }
- end
-
- let(:secret) { nil }
-
- subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) }
-
- it "initializes with Chef 11 parameters" do
- expect { described_class.new(config, run_list, chef_config) }.not_to raise_error
- end
-
- it "runs chef with the first-boot.json with no environment" do
- expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json"
- end
-
- describe "when in verbosity mode" do
- let(:config) { { verbosity: 2, color: true } }
- it "adds '-l debug' when verbosity is >= 2" do
- expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug"
- end
- end
-
- describe "when no color value has been set in config" do
- let(:config) { { color: false } }
- it "adds '--no-color' when color is false" do
- expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color"
- end
- end
-
- it "reads the validation key" do
- expect(bootstrap_context.validation_key).to eq IO.read(File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"))
- end
-
- it "generates the config file data" do
- expected = <<~EXPECTED
- chef_server_url "http://chef.example.com:4444"
- validation_client_name "chef-validator-testing"
- log_level :info
- log_location "/tmp/log"
- # Using default node name (fqdn)
- EXPECTED
- expect(bootstrap_context.config_content).to eq expected
- end
-
- describe "when chef_license is set" do
- let(:chef_config) { { chef_license: "accept-no-persist" } }
- it "sets chef_license in the generated config file" do
- expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"")
- end
- end
-
- describe "when file_cache_path is set" do
- let(:chef_config) { { file_cache_path: "/home/opscode/cache" } }
- it "sets file_cache_path in the generated config file" do
- expect(bootstrap_context.config_content).to include("file_cache_path \"/home/opscode/cache\"")
- end
- end
-
- describe "when file_backup_path is set" do
- let(:chef_config) { { file_backup_path: "/home/opscode/backup" } }
- it "sets file_backup_path in the generated config file" do
- expect(bootstrap_context.config_content).to include("file_backup_path \"/home/opscode/backup\"")
- end
- end
-
- describe "alternate chef-client path" do
- let(:chef_config) { { chef_client_path: "/usr/local/bin/chef-client" } }
- it "runs chef-client from another path when specified" do
- expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json"
- end
- end
-
- describe "validation key path that contains a ~" do
- let(:chef_config) { { validation_key: "~/my.key" } }
- it "reads the validation key when it contains a ~" do
- expect(File).to receive(:exist?).with(File.expand_path("my.key", ENV["HOME"])).and_return(true)
- expect(IO).to receive(:read).with(File.expand_path("my.key", ENV["HOME"]))
- bootstrap_context.validation_key
- end
- end
-
- describe "when an explicit node name is given" do
- let(:config) { { chef_node_name: "foobar.example.com" } }
- it "sets the node name in the client.rb" do
- expect(bootstrap_context.config_content).to match(/node_name "foobar\.example\.com"/)
- end
- end
-
- describe "when bootstrapping into a specific environment" do
- let(:config) { { environment: "prodtastic", color: true } }
- it "starts chef in the configured environment" do
- expect(bootstrap_context.start_chef).to eq("chef-client -j /etc/chef/first-boot.json -E prodtastic")
- end
- end
-
- describe "when tags are given" do
- let(:config) { { tags: [ "unicorn" ] } }
- it "adds the attributes to first_boot" do
- expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list, tags: ["unicorn"] }))
- end
- end
-
- describe "when JSON attributes are given" do
- let(:config) { { first_boot_attributes: { baz: :quux } } }
- it "adds the attributes to first_boot" do
- expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ baz: :quux, run_list: run_list }))
- end
- end
-
- describe "when JSON attributes are NOT given" do
- it "sets first_boot equal to run_list" do
- expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ run_list: run_list }))
- end
- end
-
- describe "when policy_name and policy_group are present in config" do
-
- let(:config) { { policy_name: "my_app_server", policy_group: "staging" } }
-
- it "includes them in the first_boot data and excludes run_list" do
- expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ policy_name: "my_app_server", policy_group: "staging" }))
- end
-
- end
-
- describe "when an encrypted_data_bag_secret is provided" do
- let(:secret) { "supersekret" }
- it "reads the encrypted_data_bag_secret" do
- expect(bootstrap_context.encrypted_data_bag_secret).to eq "supersekret"
- end
- end
-
- describe "to support compatibility with existing templates" do
- it "sets the @config instance variable" do
- expect(bootstrap_context.instance_variable_get(:@config)).to eq config
- end
-
- it "sets the @run_list instance variable" do
- expect(bootstrap_context.instance_variable_get(:@run_list)).to eq run_list
- end
- end
-
- describe "ssl_verify_mode" do
- it "isn't set in the config_content by default" do
- expect(bootstrap_context.config_content).not_to include("ssl_verify_mode")
- end
-
- describe "when configured via the config hash" do
- let(:config) { { node_ssl_verify_mode: "none" } }
-
- it "uses the config value" do
- expect(bootstrap_context.config_content).to include("ssl_verify_mode :verify_none")
- end
- end
- end
-
- describe "fips mode" do
- before do
- chef_config[:fips] = true
- end
-
- it "sets fips mode in the client.rb" do
- expect(bootstrap_context.config_content).to match(/fips true/)
- end
- end
-
- describe "verify_api_cert" do
- it "isn't set in the config_content by default" do
- expect(bootstrap_context.config_content).not_to include("verify_api_cert")
- end
-
- describe "when configured via the config hash" do
- let(:config) { { node_verify_api_cert: true } }
-
- it "uses config value" do
- expect(bootstrap_context.config_content).to include("verify_api_cert true")
- end
- end
- end
-
- describe "#config_log_location" do
- context "when config_log_location is nil" do
- let(:chef_config) { { config_log_location: nil } }
- it "sets the default config_log_location in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq "STDOUT"
- end
- end
-
- context "when config_log_location is empty" do
- let(:chef_config) { { config_log_location: "" } }
- it "sets the default config_log_location in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq "STDOUT"
- end
- end
-
- context "when config_log_location is :win_evt" do
- let(:chef_config) { { config_log_location: :win_evt } }
- it "raise error when config_log_location is :win_evt " do
- expect { bootstrap_context.get_log_location }.to raise_error("The value :win_evt is not supported for config_log_location on Linux Platforms \n")
- end
- end
-
- context "when config_log_location is :syslog" do
- let(:chef_config) { { config_log_location: :syslog } }
- it "sets the config_log_location value as :syslog in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq ":syslog"
- end
- end
-
- context "When config_log_location is STDOUT" do
- let(:chef_config) { { config_log_location: STDOUT } }
- it "Sets the config_log_location value as STDOUT in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq "STDOUT"
- end
- end
-
- context "when config_log_location is STDERR" do
- let(:chef_config) { { config_log_location: STDERR } }
- it "sets the config_log_location value as STDERR in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq "STDERR"
- end
- end
-
- context "when config_log_location is a path" do
- let(:chef_config) { { config_log_location: "/tmp/ChefLogFile" } }
- it "sets the config_log_location path in the client.rb" do
- expect(bootstrap_context.get_log_location).to eq "\"/tmp/ChefLogFile\""
- end
- end
-
- end
-
- describe "#version_to_install" do
- context "when bootstrap_version is provided" do
- let(:config) { { bootstrap_version: "awesome" } }
-
- it "returns bootstrap_version" do
- expect(bootstrap_context.version_to_install).to eq "awesome"
- end
- end
-
- context "when bootstrap_version is not provided" do
- let(:config) { { channel: "stable" } }
- it "returns the currently running major version out of Chef::VERSION" do
- expect(bootstrap_context.version_to_install).to eq Chef::VERSION.split(".").first
- end
- end
-
- context "and channel is other than stable" do
- let(:config) { { channel: "unstable" } }
- it "returns the version string 'latest'" do
- expect(bootstrap_context.version_to_install).to eq "latest"
- end
- end
- end
-end
diff --git a/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/spec/unit/knife/core/cookbook_scm_repo_spec.rb
deleted file mode 100644
index 316cbdfaa6..0000000000
--- a/spec/unit/knife/core/cookbook_scm_repo_spec.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/core/cookbook_scm_repo"
-
-describe Chef::Knife::CookbookSCMRepo do
- before do
- @repo_path = File.join(CHEF_SPEC_DATA, "cookbooks")
- @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
- @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
- @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "master")
-
- @branch_list = Mixlib::ShellOut.new
- @branch_list.stdout.replace(<<-BRANCHES)
- chef-vendor-apache2
- chef-vendor-build-essential
- chef-vendor-dynomite
- chef-vendor-ganglia
- chef-vendor-graphite
- chef-vendor-python
- chef-vendor-absent-new
- BRANCHES
- end
-
- it "has a path to the cookbook repo" do
- expect(@cookbook_repo.repo_path).to eq(@repo_path)
- end
-
- it "has a default branch" do
- expect(@cookbook_repo.default_branch).to eq("master")
- end
-
- describe "when sanity checking the repo" do
- it "exits when the directory does not exist" do
- expect(::File).to receive(:directory?).with(@repo_path).and_return(false)
- expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
- end
-
- describe "and the repo dir exists" do
- before do
- allow(::File).to receive(:directory?).with(@repo_path).and_return(true)
- end
-
- it "exits when there is no git repo" do
- allow(::File).to receive(:directory?).with(/.*\.git/).and_return(false)
- expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
- end
-
- describe "and the repo is a git repo" do
- before do
- allow(::File).to receive(:directory?).with(File.join(@repo_path, ".git")).and_return(true)
- end
-
- it "exits when the default branch doesn't exist" do
- @nobranches = Mixlib::ShellOut.new.tap { |s| s.stdout.replace "\n" }
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@nobranches)
- expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
- end
-
- describe "and the default branch exists" do
- before do
- @master_branch = Mixlib::ShellOut.new
- @master_branch.stdout.replace "* master\n"
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@master_branch)
- end
-
- it "exits when the git repo is dirty" do
- @dirty_status = Mixlib::ShellOut.new
- @dirty_status.stdout.replace(<<-DIRTY)
- M chef/lib/chef/knife/cookbook_site_install.rb
- DIRTY
- expect(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@dirty_status)
- expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit)
- end
-
- describe "and the repo is clean" do
- before do
- @clean_status = Mixlib::ShellOut.new.tap { |s| s.stdout.replace("\n") }
- allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", cwd: @repo_path).and_return(@clean_status)
- end
-
- it "passes the sanity check" do
- @cookbook_repo.sanity_check
- end
-
- end
- end
- end
- end
- end
-
- it "resets to default state by checking out the default branch" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git checkout master", cwd: @repo_path)
- @cookbook_repo.reset_to_default_state
- end
-
- it "determines if a the pristine copy branch exists" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
- expect(@cookbook_repo.branch_exists?("chef-vendor-apache2")).to be_truthy
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
- expect(@cookbook_repo.branch_exists?("chef-vendor-nginx")).to be_falsey
- end
-
- it "determines if a the branch not exists correctly without substring search" do
- expect(@cookbook_repo).to receive(:shell_out!).twice.with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
- expect(@cookbook_repo).not_to be_branch_exists("chef-vendor-absent")
- expect(@cookbook_repo).to be_branch_exists("chef-vendor-absent-new")
- end
-
- describe "when the pristine copy branch does not exist" do
- it "prepares for import by creating the pristine copy branch" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
- expect(@cookbook_repo).to receive(:shell_out!).with("git checkout -b chef-vendor-nginx", cwd: @repo_path)
- @cookbook_repo.prepare_to_import("nginx")
- end
- end
-
- describe "when the pristine copy branch does exist" do
- it "prepares for import by checking out the pristine copy branch" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", cwd: @repo_path).and_return(@branch_list)
- expect(@cookbook_repo).to receive(:shell_out!).with("git checkout chef-vendor-apache2", cwd: @repo_path)
- @cookbook_repo.prepare_to_import("apache2")
- end
- end
-
- describe "when the pristine copy branch was not updated by the changes" do
- before do
- @updates = Mixlib::ShellOut.new
- @updates.stdout.replace("\n")
- allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates)
- end
-
- it "shows no changes in the pristine copy" do
- expect(@cookbook_repo.updated?("apache2")).to be_falsey
- end
-
- it "does nothing to finalize the updates" do
- expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_falsey
- end
- end
-
- describe "when the pristine copy branch was updated by the changes" do
- before do
- @updates = Mixlib::ShellOut.new
- @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n")
- allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", cwd: @repo_path).and_return(@updates)
- end
-
- it "shows changes in the pristine copy" do
- expect(@cookbook_repo.updated?("apache2")).to be_truthy
- end
-
- it "commits the changes to the repo and tags the commit" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git add apache2", cwd: @repo_path)
- expect(@cookbook_repo).to receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", cwd: @repo_path)
- expect(@cookbook_repo).to receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", cwd: @repo_path)
- expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_truthy
- end
- end
-
- describe "when a custom default branch is specified" do
- before do
- @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, default_branch: "develop")
- end
-
- it "resets to default state by checking out the default branch" do
- expect(@cookbook_repo).to receive(:shell_out!).with("git checkout develop", cwd: @repo_path)
- @cookbook_repo.reset_to_default_state
- end
- end
-end
diff --git a/spec/unit/knife/core/gem_glob_loader_spec.rb b/spec/unit/knife/core/gem_glob_loader_spec.rb
deleted file mode 100644
index b7ff3ae96e..0000000000
--- a/spec/unit/knife/core/gem_glob_loader_spec.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::SubcommandLoader::GemGlobLoader do
- let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) }
- let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") }
- let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") }
-
- before do
- allow(ChefUtils).to receive(:windows?) { false }
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
- end
-
- after do
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
- end
-
- it "builds a list of the core subcommand file require paths" do
- expect(loader.subcommand_files).not_to be_empty
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
- end
- end
-
- it "finds files installed via rubygems" do
- expect(loader.find_subcommands_via_rubygems).to include("chef/knife/node_create")
- loader.find_subcommands_via_rubygems.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) }
- end
-
- it "finds files from latest version of installed gems" do
- gems = [ double("knife-ec2-0.5.12") ]
- gem_files = [
- "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb",
- "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb",
- ]
- expect($LOAD_PATH).to receive(:map).and_return([])
- if Gem::Specification.respond_to? :latest_specs
- expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
- expect(gems[0]).to receive(:matches_for_glob).with(%r{chef/knife/\*\.rb\{(.*),\.rb,(.*)\}}).and_return(gem_files)
- else
- expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
- expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"])
- expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12")
- expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files)
- end
- expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
- expect(loader.subcommand_files.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
- end
-
- it "finds files using a dirglob when rubygems is not available" do
- expect(loader.find_subcommands_via_dirglob).to include("chef/knife/node_create")
- loader.find_subcommands_via_dirglob.each_value { |abs_path| expect(abs_path).to match(%r{chef/knife/.+}) }
- end
-
- it "finds user-specific subcommands in the user's ~/.chef directory" do
- expected_command = File.join(home, ".chef", "plugins", "knife", "example_home_subcommand.rb")
- expect(loader.site_subcommands).to include(expected_command)
- end
-
- it "finds repo specific subcommands by searching for a .chef directory" do
- expected_command = File.join(CHEF_SPEC_DATA, "knife-site-subcommands", "plugins", "knife", "example_subcommand.rb")
- expect(loader.site_subcommands).to include(expected_command)
- end
-
- # https://github.com/chef/chef-dk/issues/227
- #
- # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
- # of the source, but there can be one or more versions of chef also installed
- # as a gem. If the gem install contains a command that doesn't exist in the
- # source tree of the "primary" chef install, it can be loaded and cause an
- # error. We also want to ensure that we only load builtin commands from the
- # "primary" chef install.
- context "when a different version of chef is also installed as a gem" do
-
- let(:all_found_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
-
- # We use the fake version 1.0.0 because that version doesn't exist,
- # which ensures it won't ever equal "chef-#{Chef::VERSION}"
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
-
- # Test that we don't accept a version number that is different only in
- # trailing characters, e.g. we are running Chef 12.0.0 but there is a
- # Chef 12.0.0.rc.0 gem also:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
-
- # Test that we ignore the platform suffix when checking for different
- # gem versions.
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
- # ...but don't ignore the .rc / .dev parts in the case when we have
- # platform suffixes
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
-
- # This command is "extra" compared to what's in the embedded/apps/chef install:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
-
- # These are fake commands that have names designed to test that the
- # regex is strict enough
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
-
- # In a real scenario, we'd use rubygems APIs to only select the most
- # recent gem, but for this test we want to check that we're doing the
- # right thing both when the plugin version matches and does not match
- # the current chef version. Looking at
- # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and
- # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want
- # to test these two cases.
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb",
- ]
- end
-
- let(:expected_valid_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb",
- ]
- end
-
- before do
- expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
- expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
- end
-
- it "ignores commands from the non-matching gem install" do
- expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
- end
-
- end
-
- describe "finding 3rd party plugins" do
- let(:env_home) { "/home/alice" }
- let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
-
- before do
- env_dup = ENV.to_hash
- allow(ENV).to receive(:[]) { |key| env_dup[key] }
- allow(ENV).to receive(:[]).with("HOME").and_return(env_home)
- end
-
- it "searches rubygems for plugins" do
- if Gem::Specification.respond_to?(:latest_specs)
- expect(Gem::Specification).to receive(:latest_specs).and_call_original
- else
- expect(Gem.source_index).to receive(:latest_specs).and_call_original
- end
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
- end
- end
-
- context "and HOME environment variable is not set" do
- before do
- allow(ENV).to receive(:[]).with("HOME").and_return(nil)
- end
-
- it "searches rubygems for plugins" do
- if Gem::Specification.respond_to?(:latest_specs)
- expect(Gem::Specification).to receive(:latest_specs).and_call_original
- else
- expect(Gem.source_index).to receive(:latest_specs).and_call_original
- end
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(%r{chef/knife/.*|plugins/knife/.*})
- end
- end
- end
- end
-end
diff --git a/spec/unit/knife/core/hashed_command_loader_spec.rb b/spec/unit/knife/core/hashed_command_loader_spec.rb
deleted file mode 100644
index c88656945b..0000000000
--- a/spec/unit/knife/core/hashed_command_loader_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::SubcommandLoader::HashedCommandLoader do
- before do
- allow(ChefUtils).to receive(:windows?) { false }
- end
-
- let(:plugin_manifest) do
- {
- "_autogenerated_command_paths" => {
- "plugins_paths" => {
- "cool_a" => ["/file/for/plugin/a"],
- "cooler_b" => ["/file/for/plugin/b"],
- },
- "plugins_by_category" => {
- "cool" => [
- "cool_a",
- ],
- "cooler" => [
- "cooler_b",
- ],
- },
- },
- }
- end
-
- let(:loader) do
- Chef::Knife::SubcommandLoader::HashedCommandLoader.new(
- File.join(CHEF_SPEC_DATA, "knife-site-subcommands"),
- plugin_manifest
- )
- end
-
- describe "#list_commands" do
- before do
- allow(File).to receive(:exist?).and_return(true)
- end
-
- it "lists all commands by category when no argument is given" do
- expect(loader.list_commands).to eq({ "cool" => ["cool_a"], "cooler" => ["cooler_b"] })
- end
-
- it "lists only commands in the given category when a category is given" do
- expect(loader.list_commands("cool")).to eq({ "cool" => ["cool_a"] })
- end
-
- context "when the plugin path is invalid" do
- before do
- expect(File).to receive(:exist?).with("/file/for/plugin/b").and_return(false)
- end
-
- it "lists all commands by category when no argument is given" do
- expect(Chef::Log).to receive(:error).with(/There are plugin files specified in the knife cache that cannot be found/)
- expect(Chef::Log).to receive(:error).with("Missing files:\n\t/file/for/plugin/b")
- expect(loader.list_commands).to eq({})
- end
- end
- end
-
- describe "#subcommand_files" do
- it "lists all the files" do
- expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"])
- end
- end
-
- describe "#load_commands" do
- before do
- allow(Kernel).to receive(:load).and_return(true)
- end
-
- it "returns false for non-existant commands" do
- expect(loader.load_command(["nothere"])).to eq(false)
- end
-
- it "loads the correct file and returns true if the command exists" do
- allow(File).to receive(:exist?).and_return(true)
- expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true)
- expect(loader.load_command(["cool_a"])).to eq(true)
- end
- end
-
- describe "#subcommand_for_args" do
- it "returns the subcommands for an exact match" do
- expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b")
- end
-
- it "finds the right subcommand even when _'s are elided" do
- expect(loader.subcommand_for_args(%w{cooler b})).to eq("cooler_b")
- end
-
- it "returns nil if the the subcommand isn't in our manifest" do
- expect(loader.subcommand_for_args(["cooler c"])).to eq(nil)
- end
- end
-end
diff --git a/spec/unit/knife/core/node_editor_spec.rb b/spec/unit/knife/core/node_editor_spec.rb
deleted file mode 100644
index 4eac07c313..0000000000
--- a/spec/unit/knife/core/node_editor_spec.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-#
-# Author:: Jordan Running (<jr@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/core/node_editor"
-
-describe Chef::Knife::NodeEditor do
- let(:node_data) do
- { "name" => "test_node",
- "chef_environment" => "production",
- "automatic" => { "foo" => "bar" },
- "default" => { "alpha" => { "bravo" => "charlie", "delta" => "echo" } },
- "normal" => { "alpha" => { "bravo" => "hotel" }, "tags" => [] },
- "override" => { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } },
- "policy_name" => nil,
- "policy_group" => nil,
- "run_list" => %w{role[comedy] role[drama] recipe[mystery]},
- }
- end
-
- let(:node) { Chef::Node.from_hash(node_data) }
-
- let(:ui) { double "ui" }
- let(:base_config) { { editor: "cat" } }
- let(:config) { base_config.merge(all_attributes: false) }
-
- subject { described_class.new(node, ui, config) }
-
- describe "#view" do
- it "returns a Hash with only the name, chef_environment, normal, " +
- "policy_name, policy_group, and run_list properties" do
- expected = node_data.select do |key,|
- %w{ name chef_environment normal
- policy_name policy_group run_list }.include?(key)
- end
-
- expect(subject.view).to eq(expected)
- end
-
- context "when config[:all_attributes] == true" do
- let(:config) { base_config.merge(all_attributes: true) }
-
- it "returns a Hash with all of the node's properties" do
- expect(subject.view).to eq(node_data)
- end
- end
- end
-
- describe "#apply_updates" do
- context "when the node name is changed" do
- before(:each) do
- allow(ui).to receive(:warn)
- allow(ui).to receive(:confirm).and_return(true)
- end
-
- it "emits a warning and prompts for confirmation" do
- data = subject.view.merge("name" => "foo_new_name_node")
- updated_node = subject.apply_updates(data)
-
- expect(ui).to have_received(:warn)
- .with "Changing the name of a node results in a new node being " +
- "created, test_node will not be modified or removed."
-
- expect(ui).to have_received(:confirm)
- .with("Proceed with creation of new node")
-
- expect(updated_node).to be_a(Chef::Node)
- end
- end
-
- context "when config[:all_attributes] == false" do
- let(:config) { base_config.merge(all_attributes: false) }
-
- let(:updated_data) do
- subject.view.merge(
- "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
- "policy_name" => "mypolicy",
- "policy_group" => "prod",
- "run_list" => %w{role[drama] recipe[mystery]}
- )
- end
-
- it "returns a node with run_list and normal_attrs changed" do
- updated_node = subject.apply_updates(updated_data)
- expect(updated_node).to be_a(Chef::Node)
-
- # Expected to have been changed
- expect(updated_node.normal_attrs).to eql(updated_data["normal"])
- expect(updated_node.policy_name).to eql(updated_data["policy_name"])
- expect(updated_node.policy_group).to eql(updated_data["policy_group"])
- expect(updated_node.chef_environment).to eql(updated_data["policy_group"])
- expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"])
-
- # Expected not to have changed
- expect(updated_node.default_attrs).to eql(node.default_attrs)
- expect(updated_node.override_attrs).to eql(node.override_attrs)
- expect(updated_node.automatic_attrs).to eql(node.automatic_attrs)
- end
- end
-
- context "when config[:all_attributes] == true" do
- let(:config) { base_config.merge(all_attributes: true) }
-
- let(:updated_data) do
- subject.view.merge(
- "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } },
- "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
- "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } },
- "policy_name" => "mypolicy",
- "policy_group" => "prod",
- "run_list" => %w{role[drama] recipe[mystery]}
- )
- end
-
- it "returns a node with all editable properties changed" do
- updated_node = subject.apply_updates(updated_data)
- expect(updated_node).to be_a(Chef::Node)
-
- expect(updated_node.chef_environment).to eql(updated_data["policy_group"])
- expect(updated_node.automatic_attrs).to eql(updated_data["automatic"])
- expect(updated_node.normal_attrs).to eql(updated_data["normal"])
- expect(updated_node.default_attrs).to eql(updated_data["default"])
- expect(updated_node.override_attrs).to eql(updated_data["override"])
- expect(updated_node.policy_name).to eql(updated_data["policy_name"])
- expect(updated_node.policy_group).to eql(updated_data["policy_group"])
- expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"])
- end
- end
- end
-
- describe "#updated?" do
- context "before the node has been edited" do
- it "returns false" do
- expect(subject.updated?).to be false
- end
- end
-
- context "after the node has been edited" do
- context "and changes were made" do
- let(:updated_data) do
- subject.view.merge(
- "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } },
- "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] },
- "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } },
- "policy_name" => "mypolicy",
- "policy_group" => "prod",
- "run_list" => %w{role[drama] recipe[mystery]}
- )
- end
-
- context "and changes affect only editable properties" do
- before(:each) do
- allow(ui).to receive(:edit_hash)
- .with(subject.view)
- .and_return(updated_data)
-
- subject.edit_node
- end
-
- it "returns an array of the changed property names" do
- expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list }
- end
- end
-
- context "and the changes include non-editable properties" do
- before(:each) do
- data = updated_data.merge("bad_property" => "bad_value")
-
- allow(ui).to receive(:edit_hash)
- .with(subject.view)
- .and_return(data)
-
- subject.edit_node
- end
-
- it "returns an array of property names that doesn't include " +
- "the non-editable properties" do
- expect(subject.updated?).to eql %w{ chef_environment normal policy_name policy_group run_list }
- end
- end
- end
-
- context "and changes were not made" do
- before(:each) do
- allow(ui).to receive(:edit_hash)
- .with(subject.view)
- .and_return(subject.view.dup)
-
- subject.edit_node
- end
-
- it { is_expected.not_to be_updated }
- end
- end
- end
-end
diff --git a/spec/unit/knife/core/object_loader_spec.rb b/spec/unit/knife/core/object_loader_spec.rb
deleted file mode 100644
index 0dcabff46d..0000000000
--- a/spec/unit/knife/core/object_loader_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Juanje Ojeda (<juanje.ojeda@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/core/object_loader"
-
-describe Chef::Knife::Core::ObjectLoader do
- before(:each) do
- @knife = Chef::Knife.new
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- Dir.chdir(File.join(CHEF_SPEC_DATA, "object_loader"))
- end
-
- shared_examples_for "Chef object" do |chef_class|
- it "should create a #{chef_class} object" do
- expect(@object).to be_a_kind_of(chef_class)
- end
-
- it "should has a attribute 'name'" do
- expect(@object.name).to eql("test")
- end
- end
-
- {
- "nodes" => Chef::Node,
- "roles" => Chef::Role,
- "environments" => Chef::Environment,
- }.each do |repo_location, chef_class|
-
- describe "when the file is a #{chef_class}" do
- before do
- @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui)
- end
-
- describe "when the file is a Ruby" do
- before do
- @object = @loader.load_from(repo_location, "test.rb")
- end
-
- it_behaves_like "Chef object", chef_class
- end
-
- # NOTE: This is check for the bug described at CHEF-2352
- describe "when the file is a JSON" do
- describe "and it has defined 'json_class'" do
- before do
- @object = @loader.load_from(repo_location, "test_json_class.json")
- end
-
- it_behaves_like "Chef object", chef_class
- end
-
- describe "and it has not defined 'json_class'" do
- before do
- @object = @loader.load_from(repo_location, "test.json")
- end
-
- it_behaves_like "Chef object", chef_class
- end
- end
- end
- end
-
-end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
deleted file mode 100644
index e8bc045946..0000000000
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::SubcommandLoader do
- let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) }
- let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") }
- let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") }
-
- before do
- allow(ChefUtils).to receive(:windows?) { false }
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
- end
-
- after do
- Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
- end
-
- let(:config_dir) { File.join(CHEF_SPEC_DATA, "knife-site-subcommands") }
-
- describe "#for_config" do
- context "when ~/.chef/plugin_manifest.json exists" do
- before do
- allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(true)
- end
-
- it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do
- allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"_autogenerated_command_paths\": {}}")
- expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader
- end
- end
-
- context "when ~/.chef/plugin_manifest.json does not exist" do
- before do
- allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(false)
- end
-
- it "creates a GemGlobLoader" do
- expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
- end
- end
- end
-
- describe "#gem_glob_loader" do
- it "always creates a GemGlobLoader" do
- expect(Chef::Knife::SubcommandLoader.gem_glob_loader(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
- end
- end
-end
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
deleted file mode 100644
index 3bbe799267..0000000000
--- a/spec/unit/knife/core/ui_spec.rb
+++ /dev/null
@@ -1,656 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: John Keiser (<jkeiser@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UI do
- before do
- @out, @err, @in = StringIO.new, StringIO.new, StringIO.new
- @config = {
- verbosity: 0,
- yes: nil,
- format: "summary",
- field_separator: ".",
- }
- @ui = Chef::Knife::UI.new(@out, @err, @in, @config)
- end
-
- class TestObject < OpenStruct
- def self.from_hash(hsh)
- new(hsh)
- end
- end
-
- describe "edit" do
- ruby_for_json = { "foo" => "bar" }
- ruby_from_json = TestObject.from_hash(ruby_for_json)
- json_from_ruby = "{\n \"foo\": \"bar\"\n}"
- json_from_editor = "{\n \"bar\": \"foo\"\n}"
- ruby_from_editor = TestObject.from_hash({ "bar" => "foo" })
- my_editor = "veeeye"
- temp_path = "/tmp/bar/baz"
-
- let(:subject) { @ui.edit_data(ruby_for_json, parse_output, object_class: klass) }
- let(:parse_output) { false }
- let(:klass) { nil }
-
- context "when editing is disabled" do
- before do
- @ui.config[:disable_editing] = true
- stub_const("Tempfile", double) # Tempfiles should never be invoked
- end
- context "when parse_output is false" do
- it "returns pretty json string" do
- expect(subject).to eql(json_from_ruby)
- end
- end
- context "when parse_output is true" do
- let(:parse_output) { true }
- let(:klass) { TestObject }
- it "returns a ruby object" do
- expect(subject).to eql(ruby_from_json)
- end
- context "but no object class is provided" do
- let(:klass) { nil }
- it "raises an error" do
- expect { subject }.to raise_error ArgumentError,
- /Please pass in the object class to hydrate or use #edit_hash/
- end
- end
- end
- end
-
- context "when editing is enabled" do
- before do
- @ui.config[:disable_editing] = false
- @ui.config[:editor] = my_editor
- @mock = double("Tempfile")
- expect(@mock).to receive(:sync=).with(true)
- expect(@mock).to receive(:puts).with(json_from_ruby)
- expect(@mock).to receive(:close)
- expect(@mock).to receive(:path).at_least(:once).and_return(temp_path)
- expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@mock)
- end
- context "and the editor works" do
- before do
- expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(true)
- expect(IO).to receive(:read).with(temp_path).and_return(json_from_editor)
- end
-
- context "when parse_output is false" do
- it "returns an edited pretty json string" do
- expect(subject).to eql(json_from_editor)
- end
- end
- context "when parse_output is true" do
- let(:parse_output) { true }
- let(:klass) { TestObject }
- it "returns an edited ruby object" do
- expect(subject).to eql(ruby_from_editor)
- end
- end
- end
- context "when running the editor fails with nil" do
- before do
- expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(nil)
- expect(IO).not_to receive(:read)
- end
- it "throws an exception" do
- expect { subject }.to raise_error(RuntimeError)
- end
- end
- context "when running the editor fails with false" do
- before do
- expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(false)
- expect(IO).not_to receive(:read)
- end
- it "throws an exception" do
- expect { subject }.to raise_error(RuntimeError)
- end
- end
- end
- context "when editing and not stubbing Tempfile (semi-functional test)" do
- before do
- @ui.config[:disable_editing] = false
- @ui.config[:editor] = my_editor
- @tempfile = Tempfile.new([ "knife-edit-", ".json" ])
- expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@tempfile)
- end
-
- context "and the editor works" do
- before do
- expect(@ui).to receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true)
- expect(IO).to receive(:read).with(@tempfile.path).and_return(json_from_editor)
- end
-
- context "when parse_output is false" do
- it "returns an edited pretty json string" do
- expect(subject).to eql(json_from_editor)
- end
- it "the tempfile should have mode 0600", :unix_only do
- # XXX: this looks odd because we're really testing Tempfile.new here
- expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
- expect(subject).to eql(json_from_editor)
- end
- end
-
- context "when parse_output is true" do
- let(:parse_output) { true }
- let(:klass) { TestObject }
- it "returns an edited ruby object" do
- expect(subject).to eql(ruby_from_editor)
- end
- it "the tempfile should have mode 0600", :unix_only do
- # XXX: this looks odd because we're really testing Tempfile.new here
- expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
- expect(subject).to eql(ruby_from_editor)
- end
- end
- end
- end
- end
-
- describe "format_list_for_display" do
- it "should print the full hash if --with-uri is true" do
- @ui.config[:with_uri] = true
- expect(@ui.format_list_for_display({ marcy: :playground })).to eq({ marcy: :playground })
- end
-
- it "should print only the keys if --with-uri is false" do
- @ui.config[:with_uri] = false
- expect(@ui.format_list_for_display({ marcy: :playground })).to eq([ :marcy ])
- end
- end
-
- shared_examples "an output mehthod handling IO exceptions" do |method|
- it "should throw Errno::EIO exceptions" do
- allow(@out).to receive(:puts).and_raise(Errno::EIO)
- allow(@err).to receive(:puts).and_raise(Errno::EIO)
- expect { @ui.send(method, "hi") }.to raise_error(Errno::EIO)
- end
-
- it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do
- allow(@out).to receive(:puts).and_raise(Errno::EPIPE)
- allow(@err).to receive(:puts).and_raise(Errno::EPIPE)
- expect { @ui.send(method, "hi") }.to raise_error(SystemExit)
- end
-
- it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do
- @config[:verbosity] = 2
- allow(@out).to receive(:puts).and_raise(Errno::EPIPE)
- allow(@err).to receive(:puts).and_raise(Errno::EPIPE)
- expect { @ui.send(method, "hi") }.to raise_error(Errno::EPIPE)
- end
- end
-
- describe "output" do
- it_behaves_like "an output mehthod handling IO exceptions", :output
-
- it "formats strings appropriately" do
- @ui.output("hi")
- expect(@out.string).to eq("hi\n")
- end
-
- it "formats hashes appropriately" do
- @ui.output({ "hi" => "a", "lo" => "b" })
- expect(@out.string).to eq <<~EOM
- hi: a
- lo: b
- EOM
- end
-
- it "formats empty hashes appropriately" do
- @ui.output({})
- expect(@out.string).to eq("\n")
- end
-
- it "formats arrays appropriately" do
- @ui.output(%w{a b})
- expect(@out.string).to eq <<~EOM
- a
- b
- EOM
- end
-
- it "formats empty arrays appropriately" do
- @ui.output([ ])
- expect(@out.string).to eq("\n")
- end
-
- it "formats single-member arrays appropriately" do
- @ui.output([ "a" ])
- expect(@out.string).to eq("a\n")
- end
-
- it "formats nested single-member arrays appropriately" do
- @ui.output([ [ "a" ] ])
- expect(@out.string).to eq("a\n")
- end
-
- it "formats nested arrays appropriately" do
- @ui.output([ %w{a b}, %w{c d}])
- expect(@out.string).to eq <<~EOM
- a
- b
-
- c
- d
- EOM
- end
-
- it "formats nested arrays with single- and empty subarrays appropriately" do
- @ui.output([ %w{a b}, [ "c" ], [], %w{d e}])
- expect(@out.string).to eq <<~EOM
- a
- b
-
- c
-
-
- d
- e
- EOM
- end
-
- it "formats arrays of hashes with extra lines in between for readability" do
- @ui.output([ { "a" => "b", "c" => "d" }, { "x" => "y" }, { "m" => "n", "o" => "p" }])
- expect(@out.string).to eq <<~EOM
- a: b
- c: d
-
- x: y
-
- m: n
- o: p
- EOM
- end
-
- it "formats hashes with empty array members appropriately" do
- @ui.output({ "a" => [], "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a:
- b: c
- EOM
- end
-
- it "formats hashes with single-member array values appropriately" do
- @ui.output({ "a" => [ "foo" ], "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a: foo
- b: c
- EOM
- end
-
- it "formats hashes with array members appropriately" do
- @ui.output({ "a" => %w{foo bar}, "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a:
- foo
- bar
- b: c
- EOM
- end
-
- it "formats hashes with single-member nested array values appropriately" do
- @ui.output({ "a" => [ [ "foo" ] ], "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a:
- foo
- b: c
- EOM
- end
-
- it "formats hashes with nested array values appropriately" do
- @ui.output({ "a" => [ %w{foo bar}, %w{baz bjork} ], "b" => "c" })
- # XXX: using a HEREDOC at this point results in a line with required spaces which auto-whitespace removal settings
- # on editors will remove and will break this test.
- expect(@out.string).to eq("a:\n foo\n bar\n \n baz\n bjork\nb: c\n")
- end
-
- it "formats hashes with hash values appropriately" do
- @ui.output({ "a" => { "aa" => "bb", "cc" => "dd" }, "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a:
- aa: bb
- cc: dd
- b: c
- EOM
- end
-
- it "formats hashes with empty hash values appropriately" do
- @ui.output({ "a" => {}, "b" => "c" })
- expect(@out.string).to eq <<~EOM
- a:
- b: c
- EOM
- end
- end
-
- describe "warn" do
- it_behaves_like "an output mehthod handling IO exceptions", :warn
- end
-
- describe "error" do
- it_behaves_like "an output mehthod handling IO exceptions", :warn
- end
-
- describe "fatal" do
- it_behaves_like "an output mehthod handling IO exceptions", :warn
- end
-
- describe "format_for_display" do
- it "should return the raw data" do
- input = { gi: :go }
- expect(@ui.format_for_display(input)).to eq(input)
- end
-
- describe "with --attribute passed" do
- it "should return the deeply nested attribute" do
- input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" }
- @ui.config[:attribute] = "gi.go"
- expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi.go" => "ge" } })
- end
-
- it "should return multiple attributes" do
- input = { "gi" => "go", "hi" => "ho", "id" => "sample-data-bag-item" }
- @ui.config[:attribute] = %w{gi hi}
- expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi" => "go", "hi" => "ho" } })
- end
-
- it "should handle attributes named the same as methods" do
- input = { "keys" => "values", "hi" => "ho", "id" => "sample-data-bag-item" }
- @ui.config[:attribute] = "keys"
- expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys" => "values" } })
- end
-
- it "should handle nested attributes named the same as methods" do
- input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" }
- @ui.config[:attribute] = "keys.keys"
- expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } })
- end
-
- it "should return the name attribute" do
- input = Chef::Node.new
- input.name("chef.localdomain")
- @ui.config[:attribute] = "name"
- expect(@ui.format_for_display(input)).to eq( { "chef.localdomain" => { "name" => "chef.localdomain" } })
- end
-
- it "should return a 'class' attribute and not the node.class" do
- input = Chef::Node.new
- input.default["class"] = "classy!"
- @ui.config[:attribute] = "class"
- expect(@ui.format_for_display(input)).to eq( { nil => { "class" => "classy!" } } )
- end
-
- it "should return the chef_environment attribute" do
- input = Chef::Node.new
- input.chef_environment = "production-partner-load-integration-preview-testing"
- @ui.config[:attribute] = "chef_environment"
- expect(@ui.format_for_display(input)).to eq( { nil => { "chef_environment" => "production-partner-load-integration-preview-testing" } } )
- end
-
- it "works with arrays" do
- input = Chef::Node.new
- input.default["array"] = %w{zero one two}
- @ui.config[:attribute] = "array.1"
- expect(@ui.format_for_display(input)).to eq( { nil => { "array.1" => "one" } } )
- end
-
- it "returns nil when given an attribute path that isn't a name or attribute" do
- input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" }
- non_existing_path = "nope.nada.nothingtoseehere"
- @ui.config[:attribute] = non_existing_path
- expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } })
- end
-
- describe "when --field-separator is passed" do
- it "honors that separator" do
- input = { "keys" => { "with spaces" => { "open" => { "doors" => { "with many.dots" => "when asked" } } } } }
- @ui.config[:field_separator] = ";"
- @ui.config[:attribute] = "keys;with spaces;open;doors;with many.dots"
- expect(@ui.format_for_display(input)).to eq({ nil => { "keys;with spaces;open;doors;with many.dots" => "when asked" } })
- end
- end
- end
-
- describe "with --run-list passed" do
- it "should return the run list" do
- input = Chef::Node.new
- input.name("sample-node")
- input.run_list("role[monkey]", "role[churchmouse]")
- @ui.config[:run_list] = true
- response = @ui.format_for_display(input)
- expect(response["sample-node"]["run_list"][0]).to eq("role[monkey]")
- expect(response["sample-node"]["run_list"][1]).to eq("role[churchmouse]")
- end
- end
- end
-
- describe "format_cookbook_list_for_display" do
- before(:each) do
- @item = {
- "cookbook_name" => {
- "url" => "http://url/cookbooks/cookbook",
- "versions" => [
- { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" },
- { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" },
- { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" },
- ],
- },
- }
- end
-
- it "should return an array of the cookbooks with versions" do
- expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ]
- response = @ui.format_cookbook_list_for_display(@item)
- expect(response).to eq(expected_response)
- end
-
- describe "with --with-uri" do
- it "should return the URIs" do
- response = {
- "cookbook_name" => {
- "1.0.0" => "http://url/cookbooks/1.0.0",
- "2.0.0" => "http://url/cookbooks/2.0.0",
- "3.0.0" => "http://url/cookbooks/3.0.0" },
- }
- @ui.config[:with_uri] = true
- expect(@ui.format_cookbook_list_for_display(@item)).to eq(response)
- end
- end
-
- context "when running on Windows" do
- before(:each) do
- stdout = double("StringIO", tty?: true)
- allow(@ui).to receive(:stdout).and_return(stdout)
- allow(ChefUtils).to receive(:windows?) { true }
- Chef::Config.reset
- end
-
- after(:each) do
- Chef::Config.reset
- end
-
- it "should have color set to true if knife config has color explicitly set to true" do
- Chef::Config[:color] = true
- @ui.config[:color] = true
- expect(@ui.color?).to eql(true)
- end
-
- it "should have color set to false if knife config has color explicitly set to false" do
- Chef::Config[:color] = false
- expect(@ui.color?).to eql(false)
- end
-
- it "should not have color set to false by default" do
- expect(@ui.color?).to eql(false)
- end
- end
- end
-
- describe "color" do
- context "when ui.color? => true" do
- it "returns colored output" do
- skip "doesn't work on systems that don't correctly have terminals setup for color"
- expect(@ui).to receive(:color?).and_return(true)
- expect(@ui.color("a_bus_is", :yellow)).to eql("\e[33ma_bus_is\e[0m")
- end
- end
-
- context "when ui.color? => false" do
- it "returns plain output" do
- expect(@ui).to receive(:color?).and_return(false)
- expect(@ui.color("a_bus_is", :yellow)).to eql("a_bus_is")
- end
- end
- end
-
- describe "confirm" do
- let(:stdout) { StringIO.new }
- let(:output) { stdout.string }
-
- let(:question) { "monkeys rule" }
- let(:answer) { "y" }
-
- let(:default_choice) { nil }
- let(:append_instructions) { true }
-
- def run_confirm
- allow(@ui).to receive(:stdout).and_return(stdout)
- allow(@ui.stdin).to receive(:readline).and_return(answer)
- @ui.confirm(question, append_instructions, default_choice)
- end
-
- def run_confirm_without_exit
- allow(@ui).to receive(:stdout).and_return(stdout)
- allow(@ui.stdin).to receive(:readline).and_return(answer)
- @ui.confirm_without_exit(question, append_instructions, default_choice)
- end
-
- shared_examples_for "confirm with positive answer" do
- it "confirm should return true" do
- expect(run_confirm).to be_truthy
- end
-
- it "confirm_without_exit should return true" do
- expect(run_confirm_without_exit).to be_truthy
- end
- end
-
- shared_examples_for "confirm with negative answer" do
- it "confirm should exit 3" do
- expect do
- run_confirm
- end.to raise_error(SystemExit) { |e| expect(e.status).to eq(3) }
- end
-
- it "confirm_without_exit should return false" do
- expect(run_confirm_without_exit).to be_falsey
- end
- end
-
- describe "with default choice set to true" do
- let(:default_choice) { true }
-
- it "should show 'Y/n' in the instructions" do
- run_confirm
- expect(output).to include("Y/n")
- end
-
- describe "with empty answer" do
- let(:answer) { "" }
-
- it_behaves_like "confirm with positive answer"
- end
-
- describe "with answer N " do
- let(:answer) { "N" }
-
- it_behaves_like "confirm with negative answer"
- end
- end
-
- describe "with default choice set to false" do
- let(:default_choice) { false }
-
- it "should show 'y/N' in the instructions" do
- run_confirm
- expect(output).to include("y/N")
- end
-
- describe "with empty answer" do
- let(:answer) { "" }
-
- it_behaves_like "confirm with negative answer"
- end
-
- describe "with answer N " do
- let(:answer) { "Y" }
-
- it_behaves_like "confirm with positive answer"
- end
- end
-
- %w{Y y}.each do |answer|
- describe "with answer #{answer}" do
- let(:answer) { answer }
-
- it_behaves_like "confirm with positive answer"
- end
- end
-
- %w{N n}.each do |answer|
- describe "with answer #{answer}" do
- let(:answer) { answer }
-
- it_behaves_like "confirm with negative answer"
- end
- end
-
- describe "with --y or --yes passed" do
- it "should return true" do
- @ui.config[:yes] = true
- expect(run_confirm).to be_truthy
- expect(output).to eq("")
- end
- end
- end
-
- describe "when asking for free-form user input" do
- it "asks a question and returns the answer provided by the user" do
- out = StringIO.new
- allow(@ui).to receive(:stdout).and_return(out)
- allow(@ui).to receive(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
- expect(@ui.ask_question("your chef server URL?")).to eq("http://mychefserver.example.com")
- expect(out.string).to eq("your chef server URL?")
- end
-
- it "suggests a default setting and returns the default when the user's response only contains whitespace" do
- out = StringIO.new
- allow(@ui).to receive(:stdout).and_return(out)
- allow(@ui).to receive(:stdin).and_return(StringIO.new(" \n"))
- expect(@ui.ask_question("your chef server URL? ", default: "http://localhost:4000")).to eq("http://localhost:4000")
- expect(out.string).to eq("your chef server URL? [http://localhost:4000] ")
- end
- end
-
-end
diff --git a/spec/unit/knife/core/windows_bootstrap_context_spec.rb b/spec/unit/knife/core/windows_bootstrap_context_spec.rb
deleted file mode 100644
index 76b90c955e..0000000000
--- a/spec/unit/knife/core/windows_bootstrap_context_spec.rb
+++ /dev/null
@@ -1,238 +0,0 @@
-#
-# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/core/windows_bootstrap_context"
-describe Chef::Knife::Core::WindowsBootstrapContext do
- let(:config) { {} }
- let(:chef_config) { Chef::Config.save } # "dup" to a hash
- let(:bootstrap_context) { Chef::Knife::Core::WindowsBootstrapContext.new(config, nil, chef_config, nil) }
-
- describe "fips" do
- context "when fips is set" do
- before do
- chef_config[:fips] = true
- end
-
- it "sets fips mode in the client.rb" do
- expect(bootstrap_context.config_content).to match(/fips true/)
- end
- end
-
- context "when fips is not set" do
- before do
- chef_config[:fips] = false
- end
-
- it "sets fips mode in the client.rb" do
- expect(bootstrap_context.config_content).not_to match(/fips true/)
- end
- end
- end
-
- describe "trusted_certs_script" do
- let(:mock_cert_dir) { ::File.absolute_path(::File.join("spec", "assets", "fake_trusted_certs")) }
- let(:script_output) { bootstrap_context.trusted_certs_script }
- let(:crt_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.crt")) }
- let(:pem_files) { ::Dir.glob(::File.join(mock_cert_dir, "*.pem")) }
- let(:other_files) { ::Dir.glob(::File.join(mock_cert_dir, "*")) - crt_files - pem_files }
-
- before do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(trusted_certs_dir: mock_cert_dir))
- end
-
- it "should echo every .crt file in the trusted_certs directory" do
- crt_files.each do |f|
- echo_file = ::File.read(f).gsub(/^/, "echo.")
- expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f)))
- expect(script_output).to include(echo_file)
- end
- end
-
- it "should echo every .pem file in the trusted_certs directory" do
- pem_files.each do |f|
- echo_file = ::File.read(f).gsub(/^/, "echo.")
- expect(script_output).to include(::File.join("trusted_certs", ::File.basename(f)))
- expect(script_output).to include(echo_file)
- end
- end
-
- it "should not echo files which aren't .crt or .pem files" do
- other_files.each do |f|
- echo_file = ::File.read(f).gsub(/^/, "echo.")
- expect(script_output).to_not include(::File.join("trusted_certs", ::File.basename(f)))
- expect(script_output).to_not include(echo_file)
- end
- end
- end
-
- describe "validation_key" do
- before do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(validation_key: "C:\\chef\\key.pem"))
- end
-
- it "should return false if validation_key does not exist" do
- allow(::File).to receive(:expand_path).with("C:\\chef\\key.pem").and_call_original
- allow(::File).to receive(:exist?).and_return(false)
- expect(bootstrap_context.validation_key).to eq(false)
- end
- end
-
- describe "#get_log_location" do
-
- context "when config_log_location value is nil" do
- it "sets STDOUT in client.rb as default" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: nil))
- expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
- end
- end
-
- context "when config_log_location value is empty" do
- it "sets STDOUT in client.rb as default" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: ""))
- expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
- end
- end
-
- context "when config_log_location value is STDOUT" do
- it "sets STDOUT in client.rb" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDOUT))
- expect(bootstrap_context.get_log_location).to eq("STDOUT\n")
- end
- end
-
- context "when config_log_location value is STDERR" do
- it "sets STDERR in client.rb" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: STDERR))
- expect(bootstrap_context.get_log_location).to eq("STDERR\n")
- end
- end
-
- context "when config_log_location value is path to a file" do
- it "sets file path in client.rb" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: "C:\\chef\\chef.log"))
- expect(bootstrap_context.get_log_location).to eq("\"C:\\chef\\chef.log\"\n")
- end
- end
-
- context "when config_log_location value is :win_evt" do
- it "sets :win_evt in client.rb" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :win_evt))
- expect(bootstrap_context.get_log_location).to eq(":win_evt\n")
- end
- end
-
- context "when config_log_location value is :syslog" do
- it "raise error with message and exit" do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(config_log_location: :syslog))
- expect { bootstrap_context.get_log_location }.to raise_error("syslog is not supported for log_location on Windows OS\n")
- end
- end
-
- end
-
- describe "#config_content" do
- before do
- bootstrap_context.instance_variable_set(
- :@chef_config, Mash.new(
- config_log_level: :info,
- config_log_location: STDOUT,
- chef_server_url: "http://chef.example.com:4444",
- validation_client_name: "chef-validator-testing",
- file_cache_path: "c:/chef/cache",
- file_backup_path: "c:/chef/backup",
- cache_options: ({ path: "c:/chef/cache/checksums", skip_expires: true })
- )
- )
- end
-
- it "generates the config file data" do
- expected = <<~EXPECTED
- echo.chef_server_url "http://chef.example.com:4444"
- echo.validation_client_name "chef-validator-testing"
- echo.file_cache_path "C:\\\\chef\\\\cache"
- echo.file_backup_path "C:\\\\chef\\\\backup"
- echo.cache_options ^({:path =^> "C:\\\\chef\\\\cache\\\\checksums", :skip_expires =^> true}^)
- echo.# Using default node name ^(fqdn^)
- echo.log_level :auto
- echo.log_location STDOUT
- EXPECTED
- expect(bootstrap_context.config_content).to eq expected
- end
-
- describe "when chef_license is set" do
- before do
- bootstrap_context.instance_variable_set(:@chef_config, Mash.new(chef_license: "accept-no-persist"))
- end
- it "sets chef_license in the generated config file" do
- expect(bootstrap_context.config_content).to include("chef_license \"accept-no-persist\"")
- end
- end
- end
-
- describe "#start_chef" do
- it "returns the expected string" do
- expect(bootstrap_context.start_chef).to eq(
- <<~EOH
- SET "PATH=%SYSTEM32%;%SystemRoot%;%SYSTEM32%\\Wbem;%SYSTEM32%\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin;%PATH%"
- chef-client -c C:\\chef\\client.rb -j C:\\chef\\first-boot.json
- EOH
- )
- end
- end
-
- describe "msi_url" do
- context "when msi_url config option is not set" do
- let(:config) { { channel: "stable" } }
- before do
- expect(bootstrap_context).to receive(:version_to_install).and_return("something")
- end
-
- it "returns a chef.io msi url with minimal url parameters" do
- reference_url = "https://www.chef.io/chef/download?p=windows&channel=stable&v=something"
- expect(bootstrap_context.msi_url).to eq(reference_url)
- end
-
- it "returns a chef.io msi url with provided url parameters substituted" do
- reference_url = "https://www.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&channel=stable&v=something"
- expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(reference_url)
- end
-
- context "when a channel is provided in config" do
- let(:config) { { channel: "current" } }
- it "returns a chef.io msi url with the requested channel" do
- reference_url = "https://www.chef.io/chef/download?p=windows&channel=current&v=something"
- expect(bootstrap_context.msi_url).to eq(reference_url)
- end
- end
- end
-
- context "when msi_url config option is set" do
- let(:custom_url) { "file://something" }
- let(:config) { { msi_url: custom_url, install: true } }
-
- it "returns the overridden url" do
- expect(bootstrap_context.msi_url).to eq(custom_url)
- end
-
- it "doesn't introduce any unnecessary query parameters if provided by the template" do
- expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(custom_url)
- end
- end
- end
-end
diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb
deleted file mode 100644
index 93082c190e..0000000000
--- a/spec/unit/knife/data_bag_create_spec.rb
+++ /dev/null
@@ -1,175 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tempfile"
-
-describe Chef::Knife::DataBagCreate do
- let(:knife) do
- k = Chef::Knife::DataBagCreate.new
- allow(k).to receive(:rest).and_return(rest)
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- let(:rest) { double("Chef::ServerAPI") }
- let(:stdout) { StringIO.new }
-
- let(:bag_name) { "sudoing_admins" }
- let(:item_name) { "ME" }
-
- let(:secret) { "abc123SECRET" }
-
- let(:raw_hash) { { "login_name" => "alphaomega", "id" => item_name } }
-
- let(:config) { {} }
-
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- knife.name_args = [bag_name, item_name]
- allow(knife).to receive(:config).and_return(config)
- end
-
- context "when data_bag already exists" do
- it "doesn't create a data bag" do
- expect(knife).to receive(:create_object).and_yield(raw_hash)
- expect(rest).to receive(:get).with("data/#{bag_name}")
- expect(rest).to_not receive(:post).with("data", { "name" => bag_name })
- expect(knife.ui).to receive(:info).with("Data bag #{bag_name} already exists")
-
- knife.run
- end
- end
-
- context "when data_bag doesn't exist" do
- before do
- # Data bag doesn't exist by default so we mock the GET request to return 404
- exception = double("404 error", code: "404")
- allow(rest).to receive(:get)
- .with("data/#{bag_name}")
- .and_raise(Net::HTTPClientException.new("404", exception))
- end
-
- it "tries to create a data bag with an invalid name when given one argument" do
- knife.name_args = ["invalid&char"]
- expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
- expect { knife.run }.to exit_with_code(1)
- end
-
- it "won't create a data bag with a reserved name for search" do
- %w{node role client environment}.each do |name|
- knife.name_args = [name]
- expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
- expect { knife.run }.to exit_with_code(1)
- end
- end
-
- context "when part of the name is a reserved name" do
- before do
- exception = double("404 error", code: "404")
- %w{node role client environment}.each do |name|
- allow(rest).to receive(:get)
- .with("data/sudoing_#{name}_admins")
- .and_raise(Net::HTTPClientException.new("404", exception))
- end
- end
-
- it "will create a data bag containing a reserved word" do
- %w{node role client environment}.each do |name|
- knife.name_args = ["sudoing_#{name}_admins"]
- expect(rest).to receive(:post).with("data", { "name" => knife.name_args[0] })
- expect(knife.ui).to receive(:info).with("Created data_bag[#{knife.name_args[0]}]")
-
- knife.run
- end
- end
- end
-
- context "when given one argument" do
- before do
- knife.name_args = [bag_name]
- end
-
- it "creates a data bag" do
- expect(rest).to receive(:post).with("data", { "name" => bag_name })
- expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
-
- knife.run
- end
- end
-
- context "when given a data bag name partially matching a reserved name for search" do
- %w{xnode rolex xenvironmentx xclientx}.each do |name|
- let(:bag_name) { name }
-
- before do
- knife.name_args = [bag_name]
- end
-
- it "creates a data bag named '#{name}'" do
- expect(rest).to receive(:post).with("data", { "name" => bag_name })
- expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
-
- knife.run
- end
- end
- end
-
- context "no secret is specified for encryption" do
- let(:item) do
- item = Chef::DataBagItem.from_hash(raw_hash)
- item.data_bag(bag_name)
- item
- end
-
- it "creates a data bag item" do
- expect(knife).to receive(:create_object).and_yield(raw_hash)
- expect(knife).to receive(:encryption_secret_provided?).and_return(false)
- expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered
- expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered
-
- knife.run
- end
- end
-
- context "a secret is specified for encryption" do
- let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
-
- let(:item) do
- item = Chef::DataBagItem.from_hash(encoded_data)
- item.data_bag(bag_name)
- item
- end
-
- it "creates an encrypted data bag item" do
- expect(knife).to receive(:create_object).and_yield(raw_hash)
- expect(knife).to receive(:encryption_secret_provided?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(Chef::EncryptedDataBagItem)
- .to receive(:encrypt_data_bag_item)
- .with(raw_hash, secret)
- .and_return(encoded_data)
- expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered
- expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered
-
- knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb
deleted file mode 100644
index 6ebcaf4945..0000000000
--- a/spec/unit/knife/data_bag_edit_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-#
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "tempfile"
-
-describe Chef::Knife::DataBagEdit do
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- knife.name_args = [bag_name, item_name]
- allow(knife).to receive(:config).and_return(config)
- end
-
- let(:knife) do
- k = Chef::Knife::DataBagEdit.new
- allow(k).to receive(:rest).and_return(rest)
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- let(:raw_hash) { { "login_name" => "alphaomega", "id" => "item_name" } }
- let(:db) { Chef::DataBagItem.from_hash(raw_hash) }
- let(:raw_edited_hash) { { "login_name" => "rho", "id" => "item_name", "new_key" => "new_value" } }
-
- let(:rest) { double("Chef::ServerAPI") }
- let(:stdout) { StringIO.new }
-
- let(:bag_name) { "sudoing_admins" }
- let(:item_name) { "ME" }
-
- let(:secret) { "abc123SECRET" }
-
- let(:config) { {} }
-
- let(:is_encrypted?) { false }
- let(:transmitted_hash) { raw_edited_hash }
- let(:data_to_edit) { db.raw_data }
- shared_examples_for "editing a data bag" do
- it "correctly edits then uploads the data bag" do
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
- expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?)
- expect(knife).to receive(:edit_hash).with(data_to_edit).and_return(raw_edited_hash)
- expect(rest).to receive(:put).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered
-
- knife.run
- end
- end
-
- it "requires data bag and item arguments" do
- knife.name_args = []
- expect(stdout).to receive(:puts).twice.with(anything)
- expect { knife.run }.to exit_with_code(1)
- expect(stdout.string).to eq("")
- end
-
- context "when no secret is provided" do
- include_examples "editing a data bag"
- end
-
- context "when config[:print_after] is set" do
- let(:config) { { print_after: true } }
- before do
- expect(knife.ui).to receive(:output).with(raw_edited_hash)
- end
-
- include_examples "editing a data bag"
- end
-
- context "when a secret is provided" do
- let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
- let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) }
- let(:transmitted_hash) { enc_edited_hash }
-
- before(:each) do
- expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret)
- expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash)
- end
-
- context "the data bag starts encrypted" do
- let(:is_encrypted?) { true }
- let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) }
- # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag
- let(:data_to_edit) { raw_hash }
-
- before(:each) do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- end
-
- include_examples "editing a data bag"
- end
-
- context "the data bag starts unencrypted" do
- before(:each) do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times
- expect(knife).to receive(:encryption_secret_provided?).and_return(true)
- end
-
- include_examples "editing a data bag"
- end
- end
-
- it "fails to edit an encrypted data bag if the secret is missing" do
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
- expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true)
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
-
- expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.")
- expect { knife.run }.to exit_with_code(1)
- end
-
-end
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
deleted file mode 100644
index 12211eede3..0000000000
--- a/spec/unit/knife/data_bag_from_file_spec.rb
+++ /dev/null
@@ -1,174 +0,0 @@
-#
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-require "chef/data_bag_item"
-require "chef/encrypted_data_bag_item"
-require "tempfile"
-
-Chef::Knife::DataBagFromFile.load_deps
-
-describe Chef::Knife::DataBagFromFile do
- before :each do
- allow(ChefUtils).to receive(:windows?) { false }
- Chef::Config[:node_name] = "webmonkey.example.com"
- FileUtils.mkdir_p([db_folder, db_folder2])
- db_file.write(Chef::JSONCompat.to_json(plain_data))
- db_file.flush
- allow(knife).to receive(:config).and_return(config)
- allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader)
- end
-
- # We have to explicitly clean up Tempfile on Windows because it said so.
- after :each do
- db_file.close
- db_file2.close
- db_file3.close
- FileUtils.rm_rf(db_folder)
- FileUtils.rm_rf(db_folder2)
- FileUtils.remove_entry_secure tmp_dir
- end
-
- let(:knife) do
- k = Chef::Knife::DataBagFromFile.new
- allow(k).to receive(:rest).and_return(rest)
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- let(:tmp_dir) { make_canonical_temp_directory }
- let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) }
- let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) }
- let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) }
- let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) }
- let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) }
-
- def new_bag_expects(b = bag_name, d = plain_data)
- data_bag = double
- expect(data_bag).to receive(:data_bag).with(b)
- expect(data_bag).to receive(:raw_data=).with(d)
- expect(data_bag).to receive(:save)
- expect(data_bag).to receive(:data_bag)
- expect(data_bag).to receive(:id)
- data_bag
- end
-
- let(:loader) { double("Knife::Core::ObjectLoader") }
-
- let(:data_bags_path) { "data_bags" }
- let(:plain_data) do
- {
- "id" => "item_name",
- "greeting" => "hello",
- "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true } },
- }
- end
- let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) }
-
- let(:rest) { double("Chef::ServerAPI") }
- let(:stdout) { StringIO.new }
-
- let(:bag_name) { "sudoing_admins" }
- let(:bag_name2) { "sudoing_admins2" }
- let(:item_name) { "ME" }
-
- let(:secret) { "abc123SECRET" }
-
- let(:config) { {} }
-
- it "loads from a file and saves" do
- knife.name_args = [bag_name, db_file.path]
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects)
-
- knife.run
- end
-
- it "loads all from multiple files and saves" do
- knife.name_args = [ bag_name, db_file.path, db_file2.path ]
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
-
- knife.run
- end
-
- it "loads all from a folder and saves" do
- knife.name_args = [ bag_name, db_folder ]
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
-
- knife.run
- end
-
- describe "loading all data bags" do
-
- it "loads all data bags when -a or --all options is provided" do
- knife.name_args = []
- config[:all] = true
- expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2])
- expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)])
- expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data)
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data)
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2))
-
- knife.run
- end
-
- it "loads all data bags items when -a or --all options is provided" do
- knife.name_args = [bag_name2]
- config[:all] = true
- expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2))
-
- knife.run
- end
-
- end
-
- describe "encrypted data bag items" do
- before(:each) do
- expect(knife).to receive(:encryption_secret_provided?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data)
- end
-
- it "encrypts values when given --secret" do
- knife.name_args = [bag_name, db_file.path]
- expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
- expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data))
-
- knife.run
- end
-
- end
-
- describe "command line parsing" do
- it "prints help if given no arguments" do
- knife.name_args = [bag_name]
- expect { knife.run }.to exit_with_code(1)
- expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)")
- end
- end
-
-end
diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb
deleted file mode 100644
index e8f99c3f79..0000000000
--- a/spec/unit/knife/data_bag_secret_options_spec.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-#
-# Author:: Tyler Ball (<tball@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife"
-require "chef/config"
-require "tempfile"
-
-class ExampleDataBagCommand < Chef::Knife
- include Chef::Knife::DataBagSecretOptions
-end
-
-describe Chef::Knife::DataBagSecretOptions do
- let(:example_db) do
- k = ExampleDataBagCommand.new
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- let(:stdout) { StringIO.new }
-
- let(:secret) { "abc123SECRET" }
- let(:secret_file) do
- sfile = Tempfile.new("encrypted_data_bag_secret")
- sfile.puts(secret)
- sfile.flush
- sfile
- end
-
- after do
- secret_file.close
- secret_file.unlink
- end
-
- describe "#validate_secrets" do
-
- it "throws an error when provided with both --secret and --secret-file on the CL" do
- example_db.config[:cl_secret_file] = secret_file.path
- example_db.config[:cl_secret] = secret
- expect(example_db).to receive(:exit).with(1)
- expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file")
-
- example_db.validate_secrets
- end
-
- it "throws an error when provided with `secret` and `secret_file` in knife.rb" do
- Chef::Config[:knife][:secret_file] = secret_file.path
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db).to receive(:exit).with(1)
- expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file")
-
- example_db.validate_secrets
- end
-
- end
-
- describe "#read_secret" do
-
- it "returns the secret first" do
- example_db.config[:cl_secret] = secret
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db.read_secret).to eq(secret)
- end
-
- it "returns the secret_file only if secret does not exist" do
- example_db.config[:cl_secret_file] = secret_file.path
- Chef::Config[:knife][:secret_file] = secret_file.path
- example_db.merge_configs
- expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
- expect(example_db.read_secret).to eq("secret file contents")
- end
-
- it "returns the secret from the knife.rb config" do
- Chef::Config[:knife][:secret_file] = secret_file.path
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db.read_secret).to eq(secret)
- end
-
- it "returns the secret_file from the knife.rb config only if the secret does not exist" do
- Chef::Config[:knife][:secret_file] = secret_file.path
- example_db.merge_configs
- expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
- expect(example_db.read_secret).to eq("secret file contents")
- end
-
- end
-
- describe "#encryption_secret_provided?" do
-
- it "returns true if the secret is passed on the CL" do
- example_db.config[:cl_secret] = secret
- expect(example_db.encryption_secret_provided?).to eq(true)
- end
-
- it "returns true if the secret_file is passed on the CL" do
- example_db.config[:cl_secret_file] = secret_file.path
- expect(example_db.encryption_secret_provided?).to eq(true)
- end
-
- it "returns true if --encrypt is passed on the CL and :secret is in config" do
- example_db.config[:encrypt] = true
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db.encryption_secret_provided?).to eq(true)
- end
-
- it "returns true if --encrypt is passed on the CL and :secret_file is in config" do
- example_db.config[:encrypt] = true
- Chef::Config[:knife][:secret_file] = secret_file.path
- example_db.merge_configs
- expect(example_db.encryption_secret_provided?).to eq(true)
- end
-
- it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do
- example_db.config[:encrypt] = true
- expect(example_db).to receive(:exit).with(1)
- expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.")
- example_db.encryption_secret_provided?
- end
-
- it "returns false if no secret is passed" do
- expect(example_db.encryption_secret_provided?).to eq(false)
- end
-
- it "returns false if --encrypt is not provided and :secret is in the config" do
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db.encryption_secret_provided?).to eq(false)
- end
-
- it "returns false if --encrypt is not provided and :secret_file is in the config" do
- Chef::Config[:knife][:secret_file] = secret_file.path
- example_db.merge_configs
- expect(example_db.encryption_secret_provided?).to eq(false)
- end
-
- it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do
- Chef::Config[:knife][:secret] = secret
- example_db.merge_configs
- expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
- end
-
- it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do
- Chef::Config[:knife][:secret_file] = secret_file.path
- example_db.merge_configs
- expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
- end
-
- it "returns false if --encrypt is not provided and need_encrypt_flag is false" do
- expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false)
- end
-
- end
-
-end
diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb
deleted file mode 100644
index 2b806b8a65..0000000000
--- a/spec/unit/knife/data_bag_show_spec.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Seth Falcon (<seth@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-require "chef/data_bag_item"
-require "chef/encrypted_data_bag_item"
-require "chef/json_compat"
-require "tempfile"
-
-describe Chef::Knife::DataBagShow do
-
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- knife.name_args = [bag_name, item_name]
- allow(knife).to receive(:config).and_return(config)
- end
-
- let(:knife) do
- k = Chef::Knife::DataBagShow.new
- allow(k).to receive(:rest).and_return(rest)
- allow(k.ui).to receive(:stdout).and_return(stdout)
- k
- end
-
- let(:rest) { double("Chef::ServerAPI") }
- let(:stdout) { StringIO.new }
-
- let(:bag_name) { "sudoing_admins" }
- let(:item_name) { "ME" }
-
- let(:data_bag_contents) do
- { "id" => "id", "baz" => "http://localhost:4000/data/bag_o_data/baz",
- "qux" => "http://localhost:4000/data/bag_o_data/qux" }
- end
- let(:enc_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret) }
- let(:data_bag) { Chef::DataBagItem.from_hash(data_bag_contents) }
- let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) }
- let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) }
-
- let(:secret) { "abc123SECRET" }
- #
- # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
- #
- let(:config) { { format: "json" } }
-
- context "Data bag to show is encrypted" do
- before do
- allow(knife).to receive(:encrypted?).and_return(true)
- end
-
- it "decrypts and displays the encrypted data bag when the secret is provided" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
- expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.")
- expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag)
-
- expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
-id: id
-qux: http://localhost:4000/data/bag_o_data/qux}
- knife.run
- expect(stdout.string.strip).to eq(expected)
- end
-
- it "displays the encrypted data bag when the secret is not provided" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
- expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
-
- knife.run
- expect(stdout.string.strip).to include("baz", "qux", "cipher")
- end
- end
-
- context "Data bag to show is not encrypted" do
- before do
- allow(knife).to receive(:encrypted?).and_return(false)
- end
-
- it "displays the data bag" do
- expect(knife).to receive(:read_secret).exactly(0).times
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
-
- expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
-id: id
-qux: http://localhost:4000/data/bag_o_data/qux}
- knife.run
- expect(stdout.string.strip).to eq(expected)
- end
-
- context "when a secret is given" do
- it "displays the data bag" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
- expect(knife.ui).to receive(:warn).with("Unencrypted data bag detected, ignoring any provided secret options.")
-
- expected = %q{baz: http://localhost:4000/data/bag_o_data/baz
-id: id
-qux: http://localhost:4000/data/bag_o_data/qux}
- knife.run
- expect(stdout.string.strip).to eq(expected)
- end
- end
- end
-
- it "displays the list of items in the data bag when only one @name_arg is provided" do
- knife.name_args = [bag_name]
- expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({})
-
- knife.run
- expect(stdout.string.strip).to eq("")
- end
-
- it "raises an error when no @name_args are provided" do
- knife.name_args = []
-
- expect { knife.run }.to exit_with_code(1)
- expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)")
- end
-
-end
diff --git a/spec/unit/knife/environment_compare_spec.rb b/spec/unit/knife/environment_compare_spec.rb
deleted file mode 100644
index bfaeed0c82..0000000000
--- a/spec/unit/knife/environment_compare_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# Author:: Sander Botman (<sbotman@schubergphilis.com>)
-# Copyright:: Copyright 2013-2016, Sander Botman.
-# 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentCompare do
- before(:each) do
- @knife = Chef::Knife::EnvironmentCompare.new
-
- @environments = {
- "cita" => "http://localhost:4000/environments/cita",
- "citm" => "http://localhost:4000/environments/citm",
- }
-
- allow(@knife).to receive(:environment_list).and_return(@environments)
-
- @constraints = {
- "cita" => { "foo" => "= 1.0.1", "bar" => "= 0.0.4" },
- "citm" => { "foo" => "= 1.0.1", "bar" => "= 0.0.2" },
- }
-
- allow(@knife).to receive(:constraint_list).and_return(@constraints)
-
- @cookbooks = { "foo" => "= 1.0.1", "bar" => "= 0.0.1" }
-
- allow(@knife).to receive(:cookbook_list).and_return(@cookbooks)
-
- @rest_double = double("rest")
- allow(@knife).to receive(:rest).and_return(@rest_double)
- @cookbook_names = %w{apache2 mysql foo bar dummy chef_handler}
- @base_url = "https://server.example.com/cookbooks"
- @cookbook_data = {}
- @cookbook_names.each do |item|
- @cookbook_data[item] = { "url" => "#{@base_url}/#{item}",
- "versions" => [{ "version" => "1.0.1",
- "url" => "#{@base_url}/#{item}/1.0.1" }] }
- end
-
- allow(@rest_double).to receive(:get).with("/cookbooks?num_versions=1").and_return(@cookbook_data)
-
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should display only cookbooks with version constraints" do
- @knife.config[:format] = "summary"
- @knife.run
- @environments.each_key do |item|
- expect(@stdout.string).to(match(/#{item}/)) && expect(@stdout.string.lines.count).to(be 4)
- end
- end
-
- it "should display 4 number of lines" do
- @knife.config[:format] = "summary"
- @knife.run
- expect(@stdout.string.lines.count).to be 4
- end
- end
-
- describe "with -m or --mismatch" do
- it "should display only cookbooks that have mismatching version constraints" do
- @knife.config[:format] = "summary"
- @knife.config[:mismatch] = true
- @knife.run
- @constraints.each_value do |ver|
- expect(@stdout.string).to match(/#{ver[1]}/)
- end
- end
-
- it "should display 3 number of lines" do
- @knife.config[:format] = "summary"
- @knife.config[:mismatch] = true
- @knife.run
- expect(@stdout.string.lines.count).to be 3
- end
- end
-
- describe "with -a or --all" do
- it "should display all cookbooks" do
- @knife.config[:format] = "summary"
- @knife.config[:all] = true
- @knife.run
- @constraints.each_value do |ver|
- expect(@stdout.string).to match(/#{ver[1]}/)
- end
- end
-
- it "should display 8 number of lines" do
- @knife.config[:format] = "summary"
- @knife.config[:all] = true
- @knife.run
- expect(@stdout.string.lines.count).to be 8
- end
- end
-
-end
diff --git a/spec/unit/knife/environment_create_spec.rb b/spec/unit/knife/environment_create_spec.rb
deleted file mode 100644
index d54cab8dc9..0000000000
--- a/spec/unit/knife/environment_create_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentCreate do
- before(:each) do
- @knife = Chef::Knife::EnvironmentCreate.new
- allow(@knife).to receive(:msg).and_return true
- allow(@knife).to receive(:output).and_return true
- allow(@knife).to receive(:show_usage).and_return true
- @knife.name_args = [ "production" ]
-
- @environment = Chef::Environment.new
- allow(@environment).to receive(:save)
-
- allow(Chef::Environment).to receive(:new).and_return @environment
- allow(@knife).to receive(:edit_data).and_return @environment
- end
-
- describe "run" do
- it "should create a new environment" do
- expect(Chef::Environment).to receive(:new)
- @knife.run
- end
-
- it "should set the environment name" do
- expect(@environment).to receive(:name).with("production")
- @knife.run
- end
-
- it "should not print the environment" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- it "should prompt you to edit the data" do
- expect(@knife).to receive(:edit_data).with(@environment, object_class: Chef::Environment)
- @knife.run
- end
-
- it "should save the environment" do
- expect(@environment).to receive(:save)
- @knife.run
- end
-
- it "should show usage and exit when no environment name is provided" do
- @knife.name_args = [ ]
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "with --description" do
- before(:each) do
- @knife.config[:description] = "This is production"
- end
-
- it "should set the description" do
- expect(@environment).to receive(:description).with("This is production")
- @knife.run
- end
- end
-
- describe "with --print-after" do
- before(:each) do
- @knife.config[:print_after] = true
- end
-
- it "should pretty print the environment, formatted for display" do
- expect(@knife).to receive(:output).with(@environment)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/environment_delete_spec.rb b/spec/unit/knife/environment_delete_spec.rb
deleted file mode 100644
index 643bf1cc13..0000000000
--- a/spec/unit/knife/environment_delete_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentDelete do
- before(:each) do
- @knife = Chef::Knife::EnvironmentDelete.new
- allow(@knife).to receive(:msg).and_return true
- allow(@knife).to receive(:output).and_return true
- allow(@knife).to receive(:show_usage).and_return true
- allow(@knife).to receive(:confirm).and_return true
- @knife.name_args = [ "production" ]
-
- @environment = Chef::Environment.new
- @environment.name("production")
- @environment.description("Please delete me")
- allow(@environment).to receive(:destroy).and_return true
- allow(Chef::Environment).to receive(:load).and_return @environment
- end
-
- it "should confirm that you want to delete" do
- expect(@knife).to receive(:confirm)
- @knife.run
- end
-
- it "should load the environment" do
- expect(Chef::Environment).to receive(:load).with("production")
- @knife.run
- end
-
- it "should delete the environment" do
- expect(@environment).to receive(:destroy)
- @knife.run
- end
-
- it "should not print the environment" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- it "should show usage and exit when no environment name is provided" do
- @knife.name_args = []
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "with --print-after" do
- it "should pretty print the environment, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output).with(@environment)
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/environment_edit_spec.rb b/spec/unit/knife/environment_edit_spec.rb
deleted file mode 100644
index 1feb1c05fd..0000000000
--- a/spec/unit/knife/environment_edit_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentEdit do
- before(:each) do
- @knife = Chef::Knife::EnvironmentEdit.new
- allow(@knife.ui).to receive(:msg).and_return true
- allow(@knife.ui).to receive(:output).and_return true
- allow(@knife.ui).to receive(:show_usage).and_return true
- @knife.name_args = [ "production" ]
-
- @environment = Chef::Environment.new
- @environment.name("production")
- @environment.description("Please edit me")
- allow(@environment).to receive(:save).and_return true
- allow(Chef::Environment).to receive(:load).and_return @environment
- allow(@knife.ui).to receive(:edit_data).and_return @environment
- end
-
- it "should load the environment" do
- expect(Chef::Environment).to receive(:load).with("production")
- @knife.run
- end
-
- it "should let you edit the environment" do
- expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment)
- @knife.run
- end
-
- it "should save the edited environment data" do
- pansy = Chef::Environment.new
-
- @environment.name("new_environment_name")
- expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment).and_return(pansy)
- expect(pansy).to receive(:save)
- @knife.run
- end
-
- it "should not save the unedited environment data" do
- expect(@environment).not_to receive(:save)
- @knife.run
- end
-
- it "should not print the environment" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- it "should show usage and exit when no environment name is provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "with --print-after" do
- it "should pretty print the environment, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife.ui).to receive(:output).with(@environment)
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb
deleted file mode 100644
index 2090ec7bbd..0000000000
--- a/spec/unit/knife/environment_from_file_spec.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Author:: Seth Falcon (<seth@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::EnvironmentFromFile.load_deps
-
-describe Chef::Knife::EnvironmentFromFile do
- before(:each) do
- allow(ChefUtils).to receive(:windows?) { false }
- @knife = Chef::Knife::EnvironmentFromFile.new
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- @knife.name_args = [ "spec.rb" ]
-
- @environment = Chef::Environment.new
- @environment.name("spec")
- @environment.description("runs the unit tests")
- @environment.cookbook_versions({ "apt" => "= 1.2.3" })
- allow(@environment).to receive(:save).and_return true
- allow(@knife.loader).to receive(:load_from).and_return @environment
- end
-
- describe "run" do
- it "loads the environment data from a file and saves it" do
- expect(@knife.loader).to receive(:load_from).with("environments", "spec.rb").and_return(@environment)
- expect(@environment).to receive(:save)
- @knife.run
- end
-
- context "when handling multiple environments" do
- before(:each) do
- @env_apple = @environment.dup
- @env_apple.name("apple")
- allow(@knife.loader).to receive(:load_from).with("apple.rb").and_return @env_apple
- end
-
- it "loads multiple environments if given" do
- @knife.name_args = [ "spec.rb", "apple.rb" ]
- expect(@environment).to receive(:save).twice
- @knife.run
- end
-
- it "loads all environments with -a" do
- allow(File).to receive(:expand_path).with("./environments/").and_return("/tmp/environments")
- allow(Dir).to receive(:glob).with("/tmp/environments/*.{json,rb}").and_return(["spec.rb", "apple.rb"])
- @knife.name_args = []
- allow(@knife).to receive(:config).and_return({ all: true })
- expect(@environment).to receive(:save).twice
- @knife.run
- end
- end
-
- it "should not print the environment" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- it "should show usage and exit if not filename is provided" do
- @knife.name_args = []
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- describe "with --print-after" do
- it "should pretty print the environment, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/environment_list_spec.rb b/spec/unit/knife/environment_list_spec.rb
deleted file mode 100644
index 7bb0e723aa..0000000000
--- a/spec/unit/knife/environment_list_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentList do
- before(:each) do
- @knife = Chef::Knife::EnvironmentList.new
- allow(@knife).to receive(:msg).and_return true
- allow(@knife).to receive(:output).and_return true
- allow(@knife).to receive(:show_usage).and_return true
-
- @environments = {
- "production" => "http://localhost:4000/environments/production",
- "development" => "http://localhost:4000/environments/development",
- "testing" => "http://localhost:4000/environments/testing",
- }
- allow(Chef::Environment).to receive(:list).and_return @environments
- end
-
- it "should make an api call to list the environments" do
- expect(Chef::Environment).to receive(:list)
- @knife.run
- end
-
- it "should print the environment names in a sorted list" do
- names = @environments.keys.sort { |a, b| a <=> b }
- expect(@knife).to receive(:output).with(names)
- @knife.run
- end
-
- describe "with --with-uri" do
- it "should print and unsorted list of the environments and their URIs" do
- @knife.config[:with_uri] = true
- expect(@knife).to receive(:output).with(@environments)
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/environment_show_spec.rb b/spec/unit/knife/environment_show_spec.rb
deleted file mode 100644
index 8f67e593bc..0000000000
--- a/spec/unit/knife/environment_show_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@ospcode.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::EnvironmentShow do
- before(:each) do
- @knife = Chef::Knife::EnvironmentShow.new
- allow(@knife).to receive(:msg).and_return true
- allow(@knife).to receive(:output).and_return true
- allow(@knife).to receive(:show_usage).and_return true
- @knife.name_args = [ "production" ]
-
- @environment = Chef::Environment.new
- @environment.name("production")
- @environment.description("Look at me!")
- allow(Chef::Environment).to receive(:load).and_return @environment
- end
-
- it "should load the environment" do
- expect(Chef::Environment).to receive(:load).with("production")
- @knife.run
- end
-
- it "should pretty print the environment, formatted for display" do
- expect(@knife).to receive(:format_for_display).with(@environment)
- expect(@knife).to receive(:output)
- @knife.run
- end
-
- it "should show usage and exit when no environment name is provided" do
- @knife.name_args = []
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-end
diff --git a/spec/unit/knife/key_create_spec.rb b/spec/unit/knife/key_create_spec.rb
deleted file mode 100644
index 12826ae7e2..0000000000
--- a/spec/unit/knife/key_create_spec.rb
+++ /dev/null
@@ -1,223 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/user_key_create"
-require "chef/knife/client_key_create"
-require "chef/knife/key_create"
-require "chef/key"
-
-describe "key create commands that inherit knife" do
- shared_examples_for "a key create command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "after apply_params! is called with valid args" do
- let(:params) { ["charmander"] }
- before do
- command.apply_params!(params)
- end
-
- context "when the service object is called" do
- it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do
- expect(Chef::Knife::KeyCreate).to receive(:new)
- .with("charmander", command.actor_field_name, command.ui, command.config)
- .and_return(service_object)
- command.service_object
- end
- end # when the service object is called
- end # after apply_params! is called with valid args
- end # a key create command
-
- describe Chef::Knife::UserKeyCreate do
- it_should_behave_like "a key create command"
- # defined in key_helper.rb
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
- let(:params) { ["charmander"] }
- end
- end
-
- describe Chef::Knife::ClientKeyCreate do
- it_should_behave_like "a key create command"
- # defined in key_helper.rb
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
- let(:params) { ["charmander"] }
- end
- end
-end
-
-describe Chef::Knife::KeyCreate do
- let(:public_key) do
- "-----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
-KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
-WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
-E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
-IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
-Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
-0wIDAQAB
------END PUBLIC KEY-----"
- end
- let(:config) { {} }
- let(:actor) { "charmander" }
- let(:ui) { instance_double("Chef::Knife::UI") }
-
- shared_examples_for "key create run command" do
- let(:key_create_object) do
- described_class.new(actor, actor_field_name, ui, config)
- end
-
- context "when public_key and key_name weren't passed" do
- it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
- expect { key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg)
- end
- end
-
- context "when the command is run" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- }
- end
-
- before do
- allow(File).to receive(:read).and_return(public_key)
- allow(File).to receive(:expand_path)
-
- allow(key_create_object).to receive(:output_private_key_to_file)
- allow(key_create_object).to receive(:display_private_key)
- allow(key_create_object).to receive(:edit_hash).and_return(expected_hash)
- allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
- allow(key_create_object).to receive(:display_info)
- end
-
- context "when a valid hash is passed" do
- let(:key_name) { "charmander-key" }
- let(:valid_expiration_date) { "2020-12-24T21:00:00Z" }
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- "expiration_date" => valid_expiration_date,
- "key_name" => key_name,
- }
- end
- before do
- key_create_object.config[:public_key] = "public_key_path"
- key_create_object.config[:expiration_Date] = valid_expiration_date,
- key_create_object.config[:key_name] = key_name
- end
-
- it "creates the proper hash" do
- expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
- key_create_object.run
- end
- end
-
- context "when public_key is passed" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- }
- end
- before do
- key_create_object.config[:public_key] = "public_key_path"
- end
-
- it "calls File.expand_path with the public_key input" do
- expect(File).to receive(:expand_path).with("public_key_path")
- key_create_object.run
- end
- end # when public_key is passed
-
- context "when public_key isn't passed and key_name is" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "name" => "charmander-key",
- "create_key" => true,
- }
- end
- before do
- key_create_object.config[:key_name] = "charmander-key"
- end
-
- it "should set create_key to true" do
- expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
- key_create_object.run
- end
- end
-
- context "when the server returns a private key" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- "private_key" => "super_private",
- }
- end
-
- before do
- key_create_object.config[:public_key] = "public_key_path"
- end
-
- context "when file is not passed" do
- it "calls display_private_key with the private_key" do
- expect(key_create_object).to receive(:display_private_key).with("super_private")
- key_create_object.run
- end
- end
-
- context "when file is passed" do
- before do
- key_create_object.config[:file] = "/fake/file"
- end
-
- it "calls output_private_key_to_file with the private_key" do
- expect(key_create_object).to receive(:output_private_key_to_file).with("super_private")
- key_create_object.run
- end
- end
- end # when the server returns a private key
- end # when the command is run
- end # key create run command"
-
- context "when actor_field_name is 'user'" do
- it_should_behave_like "key create run command" do
- let(:actor_field_name) { "user" }
- end
- end
-
- context "when actor_field_name is 'client'" do
- it_should_behave_like "key create run command" do
- let(:actor_field_name) { "client" }
- end
- end
-end
diff --git a/spec/unit/knife/key_delete_spec.rb b/spec/unit/knife/key_delete_spec.rb
deleted file mode 100644
index fd39c7381a..0000000000
--- a/spec/unit/knife/key_delete_spec.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/user_key_delete"
-require "chef/knife/client_key_delete"
-require "chef/knife/key_delete"
-require "chef/key"
-
-describe "key delete commands that inherit knife" do
- shared_examples_for "a key delete command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "after apply_params! is called with valid args" do
- let(:params) { %w{charmander charmander-key} }
- before do
- command.apply_params!(params)
- end
-
- context "when the service object is called" do
- it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do
- expect(Chef::Knife::KeyDelete).to receive(:new)
- .with("charmander-key", "charmander", command.actor_field_name, command.ui)
- .and_return(service_object)
- command.service_object
- end
- end # when the service object is called
- end # after apply_params! is called with valid args
- end # a key delete command
-
- describe Chef::Knife::UserKeyDelete do
- it_should_behave_like "a key delete command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-
- describe Chef::Knife::ClientKeyDelete do
- it_should_behave_like "a key delete command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-end
-
-describe Chef::Knife::KeyDelete do
- let(:actor) { "charmander" }
- let(:keyname) { "charmander-key" }
- let(:ui) { instance_double("Chef::Knife::UI") }
-
- shared_examples_for "key delete run command" do
- let(:key_delete_object) do
- described_class.new(keyname, actor, actor_field_name, ui)
- end
-
- before do
- allow_any_instance_of(Chef::Key).to receive(:destroy)
- allow(key_delete_object).to receive(:print_destroyed)
- allow(key_delete_object).to receive(:confirm!)
- end
-
- context "when the command is run" do
- it "calls Chef::Key.new with the proper input" do
- expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original
- key_delete_object.run
- end
-
- it "calls name on the Chef::Key instance with the proper input" do
- expect_any_instance_of(Chef::Key).to receive(:name).with(keyname)
- key_delete_object.run
- end
-
- it "calls destroy on the Chef::Key instance" do
- expect_any_instance_of(Chef::Key).to receive(:destroy).once
- key_delete_object.run
- end
-
- it "calls confirm!" do
- expect(key_delete_object).to receive(:confirm!)
- key_delete_object.run
- end
-
- it "calls print_destroyed" do
- expect(key_delete_object).to receive(:print_destroyed)
- key_delete_object.run
- end
- end # when the command is run
-
- end # key delete run command
-
- context "when actor_field_name is 'user'" do
- it_should_behave_like "key delete run command" do
- let(:actor_field_name) { "user" }
- end
- end
-
- context "when actor_field_name is 'client'" do
- it_should_behave_like "key delete run command" do
- let(:actor_field_name) { "client" }
- end
- end
-end
diff --git a/spec/unit/knife/key_edit_spec.rb b/spec/unit/knife/key_edit_spec.rb
deleted file mode 100644
index b42503af59..0000000000
--- a/spec/unit/knife/key_edit_spec.rb
+++ /dev/null
@@ -1,264 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/user_key_edit"
-require "chef/knife/client_key_edit"
-require "chef/knife/key_edit"
-require "chef/key"
-
-describe "key edit commands that inherit knife" do
- shared_examples_for "a key edit command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "after apply_params! is called with valid args" do
- let(:params) { %w{charmander charmander-key} }
- before do
- command.apply_params!(params)
- end
-
- context "when the service object is called" do
- it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do
- expect(Chef::Knife::KeyEdit).to receive(:new)
- .with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config)
- .and_return(service_object)
- command.service_object
- end
- end # when the service object is called
- end # after apply_params! is called with valid args
- end # a key edit command
-
- describe Chef::Knife::UserKeyEdit do
- it_should_behave_like "a key edit command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-
- describe Chef::Knife::ClientKeyEdit do
- it_should_behave_like "a key edit command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-end
-
-describe Chef::Knife::KeyEdit do
- let(:public_key) do
- "-----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
-KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
-WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
-E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
-IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
-Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
-0wIDAQAB
------END PUBLIC KEY-----"
- end
- let(:config) { {} }
- let(:actor) { "charmander" }
- let(:keyname) { "charmander-key" }
- let(:ui) { instance_double("Chef::Knife::UI") }
-
- shared_examples_for "key edit run command" do
- let(:key_edit_object) do
- described_class.new(keyname, actor, actor_field_name, ui, config)
- end
-
- context "when the command is run" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- }
- end
- let(:new_keyname) { "charizard-key" }
-
- before do
- allow(File).to receive(:read).and_return(public_key)
- allow(File).to receive(:expand_path)
-
- allow(key_edit_object).to receive(:output_private_key_to_file)
- allow(key_edit_object).to receive(:display_private_key)
- allow(key_edit_object).to receive(:edit_hash).and_return(expected_hash)
- allow(key_edit_object).to receive(:display_info)
- end
-
- context "when public_key and create_key are passed" do
- before do
- key_edit_object.config[:public_key] = "public_key_path"
- key_edit_object.config[:create_key] = true
- end
-
- it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
- expect { key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg)
- end
- end
-
- context "when key_name is passed" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "name" => new_keyname,
- }
- end
- before do
- key_edit_object.config[:key_name] = new_keyname
- allow_any_instance_of(Chef::Key).to receive(:update)
- end
-
- it "update_key_from_hash gets passed a hash with new key name" do
- expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash))
- key_edit_object.run
- end
-
- it "Chef::Key.update is passed a string containing the original keyname" do
- expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash))
- key_edit_object.run
- end
-
- it "Chef::Key.update is not passed a string containing the new keyname" do
- expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/)
- allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash))
- key_edit_object.run
- end
- end
-
- context "when public_key, key_name, and expiration_date are passed" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- "name" => new_keyname,
- "expiration_date" => "infinity",
- }
- end
- before do
- key_edit_object.config[:public_key] = "this-public-key"
- key_edit_object.config[:key_name] = new_keyname
- key_edit_object.config[:expiration_date] = "infinity"
- allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
- end
-
- it "passes the right hash to update_key_from_hash" do
- expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
- key_edit_object.run
- end
- end
-
- context "when create_key is passed" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "create_key" => true,
- }
- end
-
- before do
- key_edit_object.config[:create_key] = true
- allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
- end
-
- it "passes the right hash to update_key_from_hash" do
- expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
- key_edit_object.run
- end
- end
-
- context "when public_key is passed" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- }
- end
- before do
- allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
- key_edit_object.config[:public_key] = "public_key_path"
- end
-
- it "calls File.expand_path with the public_key input" do
- expect(File).to receive(:expand_path).with("public_key_path")
- key_edit_object.run
- end
- end # when public_key is passed
-
- context "when the server returns a private key" do
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "public_key" => public_key,
- "private_key" => "super_private",
- }
- end
-
- before do
- allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
- key_edit_object.config[:public_key] = "public_key_path"
- end
-
- context "when file is not passed" do
- it "calls display_private_key with the private_key" do
- expect(key_edit_object).to receive(:display_private_key).with("super_private")
- key_edit_object.run
- end
- end
-
- context "when file is passed" do
- before do
- key_edit_object.config[:file] = "/fake/file"
- end
-
- it "calls output_private_key_to_file with the private_key" do
- expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private")
- key_edit_object.run
- end
- end
- end # when the server returns a private key
-
- end # when the command is run
-
- end # key edit run command
-
- context "when actor_field_name is 'user'" do
- it_should_behave_like "key edit run command" do
- let(:actor_field_name) { "user" }
- end
- end
-
- context "when actor_field_name is 'client'" do
- it_should_behave_like "key edit run command" do
- let(:actor_field_name) { "client" }
- end
- end
-end
diff --git a/spec/unit/knife/key_helper.rb b/spec/unit/knife/key_helper.rb
deleted file mode 100644
index 6dbfb567f4..0000000000
--- a/spec/unit/knife/key_helper.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-shared_examples_for "a knife key command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "before apply_params! is called" do
- context "when apply_params! is called with invalid args" do
- it "shows the usage" do
- expect(command).to receive(:show_usage)
- expect { command.apply_params!(params) }.to exit_with_code(1)
- end
-
- it "outputs the proper error" do
- expect { command.apply_params!(params) }.to exit_with_code(1)
- expect(stderr.string).to include(command.actor_missing_error)
- end
-
- it "exits 1" do
- expect { command.apply_params!(params) }.to exit_with_code(1)
- end
- end
- end # before apply_params! is called
-
- context "after apply_params! is called with valid args" do
- let(:params) { ["charmander"] }
- before do
- command.apply_params!(params)
- end
-
- it "properly defines the actor" do
- expect(command.actor).to eq("charmander")
- end
- end # after apply_params! is called with valid args
-
- context "when the command is run" do
- before do
- allow(command).to receive(:service_object).and_return(service_object)
- allow(command).to receive(:name_args).and_return(["charmander"])
- end
-
- context "when the command is successful" do
- before do
- expect(service_object).to receive(:run)
- end
- end
- end
-end # a knife key command
diff --git a/spec/unit/knife/key_list_spec.rb b/spec/unit/knife/key_list_spec.rb
deleted file mode 100644
index 51ed73b64f..0000000000
--- a/spec/unit/knife/key_list_spec.rb
+++ /dev/null
@@ -1,216 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/user_key_list"
-require "chef/knife/client_key_list"
-require "chef/knife/key_list"
-require "chef/key"
-
-describe "key list commands that inherit knife" do
- shared_examples_for "a key list command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:service_object) { instance_double(Chef::Knife::KeyList) }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "after apply_params! is called with valid args" do
- let(:params) { ["charmander"] }
- before do
- command.apply_params!(params)
- end
-
- context "when the service object is called" do
- it "creates a new instance of Chef::Knife::KeyList with the correct args" do
- expect(Chef::Knife::KeyList).to receive(:new)
- .with("charmander", command.list_method, command.ui, command.config)
- .and_return(service_object)
- command.service_object
- end
- end # when the service object is called
- end # after apply_params! is called with valid args
- end # a key list command
-
- describe Chef::Knife::UserKeyList do
- it_should_behave_like "a key list command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyList) }
- let(:params) { ["charmander"] }
- end
- end
-
- describe Chef::Knife::ClientKeyList do
- it_should_behave_like "a key list command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyList) }
- let(:params) { ["charmander"] }
- end
- end
-end
-
-describe Chef::Knife::KeyList do
- let(:config) { {} }
- let(:actor) { "charmander" }
- let(:ui) { instance_double("Chef::Knife::UI") }
-
- shared_examples_for "key list run command" do
- let(:key_list_object) do
- described_class.new(actor, list_method, ui, config)
- end
-
- before do
- allow(Chef::Key).to receive(list_method).and_return(http_response)
- allow(key_list_object).to receive(:display_info)
- # simply pass the string though that colorize takes in
- allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input|
- input
- end
- end
-
- context "when only_expired and only_non_expired were both passed" do
- before do
- key_list_object.config[:only_expired] = true
- key_list_object.config[:only_non_expired] = true
- end
-
- it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
- expect { key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg)
- end
- end
-
- context "when the command is run" do
- before do
- key_list_object.config[:only_expired] = false
- key_list_object.config[:only_non_expired] = false
- key_list_object.config[:with_details] = false
- end
-
- it "calls Chef::Key with the proper list command and input" do
- expect(Chef::Key).to receive(list_method).with(actor)
- key_list_object.run
- end
-
- it "displays all the keys" do
- expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
- expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
- key_list_object.run
- end
-
- context "when only_expired is called" do
- before do
- key_list_object.config[:only_expired] = true
- end
-
- it "excludes displaying non-expired keys" do
- expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times
- key_list_object.run
- end
-
- it "displays the expired keys" do
- expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
- key_list_object.run
- end
- end # when only_expired is called
-
- context "when only_non_expired is called" do
- before do
- key_list_object.config[:only_non_expired] = true
- end
-
- it "excludes displaying expired keys" do
- expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times
- key_list_object.run
- end
-
- it "displays the non-expired keys" do
- expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
- key_list_object.run
- end
- end # when only_expired is called
-
- context "when with_details is false" do
- before do
- key_list_object.config[:with_details] = false
- end
-
- it "does not display the uri" do
- expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times
- key_list_object.run
- end
-
- it "does not display the expired status" do
- expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times
- key_list_object.run
- end
- end # when with_details is false
-
- context "when with_details is true" do
- before do
- key_list_object.config[:with_details] = true
- end
-
- it "displays the uri" do
- expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times
- key_list_object.run
- end
-
- it "displays the expired status" do
- expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once
- key_list_object.run
- end
- end # when with_details is true
-
- end # when the command is run
-
- end # key list run command
-
- context "when list_method is :list_by_user" do
- it_should_behave_like "key list run command" do
- let(:list_method) { :list_by_user }
- let(:http_response) do
- [
- { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false },
- { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false },
- { "uri" => "https://api.opscode.piab/users/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true },
- ]
- end
- end
- end
-
- context "when list_method is :list_by_client" do
- it_should_behave_like "key list run command" do
- let(:list_method) { :list_by_client }
- let(:http_response) do
- [
- { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false },
- { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false },
- { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true },
- ]
- end
- end
- end
-end
diff --git a/spec/unit/knife/key_show_spec.rb b/spec/unit/knife/key_show_spec.rb
deleted file mode 100644
index 6d1ca2ccc7..0000000000
--- a/spec/unit/knife/key_show_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-#
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/user_key_show"
-require "chef/knife/client_key_show"
-require "chef/knife/key_show"
-require "chef/key"
-
-describe "key show commands that inherit knife" do
- shared_examples_for "a key show command" do
- let(:stderr) { StringIO.new }
- let(:params) { [] }
- let(:service_object) { instance_double(Chef::Knife::KeyShow) }
- let(:command) do
- c = described_class.new([])
- c.ui.config[:disable_editing] = true
- allow(c.ui).to receive(:stderr).and_return(stderr)
- allow(c.ui).to receive(:stdout).and_return(stderr)
- allow(c).to receive(:show_usage)
- c
- end
-
- context "after apply_params! is called with valid args" do
- let(:params) { %w{charmander charmander-key} }
- before do
- command.apply_params!(params)
- end
-
- context "when the service object is called" do
- it "creates a new instance of Chef::Knife::KeyShow with the correct args" do
- expect(Chef::Knife::KeyShow).to receive(:new)
- .with("charmander-key", "charmander", command.load_method, command.ui)
- .and_return(service_object)
- command.service_object
- end
- end # when the service object is called
- end # after apply_params! is called with valid args
- end # a key show command
-
- describe Chef::Knife::UserKeyShow do
- it_should_behave_like "a key show command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyShow) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-
- describe Chef::Knife::ClientKeyShow do
- it_should_behave_like "a key show command"
- # defined in key_helpers.rb
- it_should_behave_like "a knife key command with a keyname as the second arg"
- it_should_behave_like "a knife key command" do
- let(:service_object) { instance_double(Chef::Knife::KeyShow) }
- let(:params) { %w{charmander charmander-key} }
- end
- end
-end
-
-describe Chef::Knife::KeyShow do
- let(:actor) { "charmander" }
- let(:keyname) { "charmander" }
- let(:ui) { instance_double("Chef::Knife::UI") }
- let(:expected_hash) do
- {
- actor_field_name => "charmander",
- "name" => "charmander-key",
- "public_key" => "some-public-key",
- "expiration_date" => "infinity",
- }
- end
-
- shared_examples_for "key show run command" do
- let(:key_show_object) do
- described_class.new(keyname, actor, load_method, ui)
- end
-
- before do
- allow(key_show_object).to receive(:display_output)
- allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash))
- end
-
- context "when the command is run" do
- it "loads the key using the proper method and args" do
- expect(Chef::Key).to receive(load_method).with(actor, keyname)
- key_show_object.run
- end
-
- it "displays the key" do
- expect(key_show_object).to receive(:display_output)
- key_show_object.run
- end
- end
- end
-
- context "when load_method is :load_by_user" do
- it_should_behave_like "key show run command" do
- let(:load_method) { :load_by_user }
- let(:actor_field_name) { "user" }
- end
- end
-
- context "when load_method is :load_by_client" do
- it_should_behave_like "key show run command" do
- let(:load_method) { :load_by_client }
- let(:actor_field_name) { "user" }
- end
- end
-end
diff --git a/spec/unit/knife/node_bulk_delete_spec.rb b/spec/unit/knife/node_bulk_delete_spec.rb
deleted file mode 100644
index e23f286999..0000000000
--- a/spec/unit/knife/node_bulk_delete_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeBulkDelete do
- before(:each) do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeBulkDelete.new
- @knife.name_args = ["."]
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:confirm).and_return(true)
- @nodes = {}
- %w{adam brent jacob}.each do |node_name|
- @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}"
- end
- end
-
- describe "when creating the list of nodes" do
- it "fetches the node list" do
- expected = @nodes.inject({}) do |inflatedish, (name, uri)|
- inflatedish[name] = Chef::Node.new.tap { |n| n.name(name) }
- inflatedish
- end
- expect(Chef::Node).to receive(:list).and_return(@nodes)
- # I hate not having == defined for anything :(
- actual = @knife.all_nodes
- expect(actual.keys).to match_array(expected.keys)
- expect(actual.values.map(&:name)).to match_array(%w{adam brent jacob})
- end
- end
-
- describe "run" do
- before do
- @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name|
- node = Chef::Node.new
- node.name(name)
- allow(node).to receive(:destroy).and_return(true)
- nodes_by_name[name] = node
- nodes_by_name
- end
- allow(@knife).to receive(:all_nodes).and_return(@inflatedish_list)
- end
-
- it "should print the nodes you are about to delete" do
- @knife.run
- expect(@stdout.string).to match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/)
- end
-
- it "should confirm you really want to delete them" do
- expect(@knife.ui).to receive(:confirm)
- @knife.run
- end
-
- it "should delete each node" do
- @inflatedish_list.each_value do |n|
- expect(n).to receive(:destroy)
- end
- @knife.run
- end
-
- it "should only delete nodes that match the regex" do
- @knife.name_args = ["adam"]
- expect(@inflatedish_list["adam"]).to receive(:destroy)
- expect(@inflatedish_list["brent"]).not_to receive(:destroy)
- expect(@inflatedish_list["jacob"]).not_to receive(:destroy)
- @knife.run
- end
-
- it "should exit if the regex is not provided" do
- @knife.name_args = []
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- end
-end
diff --git a/spec/unit/knife/node_delete_spec.rb b/spec/unit/knife/node_delete_spec.rb
deleted file mode 100644
index e6c677c041..0000000000
--- a/spec/unit/knife/node_delete_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeDelete do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeDelete.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = %w{ adam ben }
- allow(@knife).to receive(:output).and_return(true)
- allow(@knife).to receive(:confirm).and_return(true)
-
- @adam_node = Chef::Node.new
- @ben_node = Chef::Node.new
- allow(@ben_node).to receive(:destroy).and_return(true)
- allow(@adam_node).to receive(:destroy).and_return(true)
- allow(Chef::Node).to receive(:load).with("adam").and_return(@adam_node)
- allow(Chef::Node).to receive(:load).with("ben").and_return(@ben_node)
-
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should confirm that you want to delete" do
- expect(@knife).to receive(:confirm)
- @knife.run
- end
-
- it "should load the nodes" do
- expect(Chef::Node).to receive(:load).with("adam").and_return(@adam_node)
- expect(Chef::Node).to receive(:load).with("ben").and_return(@ben_node)
- @knife.run
- end
-
- it "should delete the nodes" do
- expect(@adam_node).to receive(:destroy).and_return(@adam_node)
- expect(@ben_node).to receive(:destroy).and_return(@ben_node)
- @knife.run
- end
-
- it "should not print the node" do
- expect(@knife).not_to receive(:output).with("poop")
- @knife.run
- end
-
- describe "with -p or --print-after" do
- it "should pretty print the node, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:format_for_display).with(@adam_node).and_return("adam")
- expect(@knife).to receive(:format_for_display).with(@ben_node).and_return("ben")
- expect(@knife).to receive(:output).with("adam")
- expect(@knife).to receive(:output).with("ben")
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_edit_spec.rb b/spec/unit/knife/node_edit_spec.rb
deleted file mode 100644
index 7b2ebb5b2c..0000000000
--- a/spec/unit/knife/node_edit_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-Chef::Knife::NodeEdit.load_deps
-
-describe Chef::Knife::NodeEdit do
-
- # helper to convert the view from Chef objects into Ruby objects representing JSON
- def deserialized_json_view
- Chef::JSONCompat.from_json(Chef::JSONCompat.to_json_pretty(@knife.node_editor.send(:view)))
- end
-
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeEdit.new
- @knife.config = {
- editor: "cat",
- attribute: nil,
- print_after: nil,
- }
- @knife.name_args = [ "adam" ]
- @node = Chef::Node.new
- end
-
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam").and_return(@node)
- @knife.node
- end
-
- describe "after loading the node" do
- before do
- @knife.config[:all_attributes] = false
-
- allow(@knife).to receive(:node).and_return(@node)
- @node.automatic_attrs = { go: :away }
- @node.default_attrs = { hide: :me }
- @node.override_attrs = { dont: :show }
- @node.normal_attrs = { do_show: :these }
- @node.chef_environment("prod")
- @node.run_list("recipe[foo]")
- end
-
- it "creates a view of the node without attributes from roles or ohai" do
- actual = deserialized_json_view
- expect(actual).not_to have_key("automatic")
- expect(actual).not_to have_key("override")
- expect(actual).not_to have_key("default")
- expect(actual["normal"]).to eq({ "do_show" => "these" })
- expect(actual["run_list"]).to eq(["recipe[foo]"])
- expect(actual["chef_environment"]).to eq("prod")
- end
-
- it "shows the extra attributes when given the --all option" do
- @knife.config[:all_attributes] = true
-
- actual = deserialized_json_view
- expect(actual["automatic"]).to eq({ "go" => "away" })
- expect(actual["override"]).to eq({ "dont" => "show" })
- expect(actual["default"]).to eq({ "hide" => "me" })
- expect(actual["normal"]).to eq({ "do_show" => "these" })
- expect(actual["run_list"]).to eq(["recipe[foo]"])
- expect(actual["chef_environment"]).to eq("prod")
- end
-
- it "does not consider unedited data updated" do
- view = deserialized_json_view
- @knife.node_editor.send(:apply_updates, view)
- expect(@knife.node_editor).not_to be_updated
- end
-
- it "considers edited data updated" do
- view = deserialized_json_view
- view["run_list"] << "role[fuuu]"
- @knife.node_editor.send(:apply_updates, view)
- expect(@knife.node_editor).to be_updated
- end
-
- end
-
- describe "edit_node" do
-
- before do
- allow(@knife).to receive(:node).and_return(@node)
- end
-
- let(:subject) { @knife.node_editor.edit_node }
-
- it "raises an exception when editing is disabled" do
- @knife.config[:disable_editing] = true
- expect { subject }.to raise_error(SystemExit)
- end
-
- it "raises an exception when the editor is not set" do
- @knife.config[:editor] = nil
- expect { subject }.to raise_error(SystemExit)
- end
-
- end
-
-end
diff --git a/spec/unit/knife/node_environment_set_spec.rb b/spec/unit/knife/node_environment_set_spec.rb
deleted file mode 100644
index 6a6d48cc2f..0000000000
--- a/spec/unit/knife/node_environment_set_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Author:: Jimmy McCrory (<jimmy.mccrory@gmail.com>)
-# Copyright:: Copyright 2014-2016, Jimmy McCrory
-# 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 "spec_helper"
-
-describe Chef::Knife::NodeEnvironmentSet do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeEnvironmentSet.new
- @knife.name_args = %w{adam bar}
- allow(@knife).to receive(:output).and_return(true)
- @node = Chef::Node.new
- @node.name("knifetest-node")
- @node.chef_environment << "foo"
- allow(@node).to receive(:save).and_return(true)
- allow(Chef::Node).to receive(:load).and_return(@node)
- end
-
- describe "run" do
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam")
- @knife.run
- end
-
- it "should update the environment" do
- @knife.run
- expect(@node.chef_environment).to eq("bar")
- end
-
- it "should save the node" do
- expect(@node).to receive(:save)
- @knife.run
- end
-
- it "sets the environment to config for display" do
- @knife.run
- expect(@knife.config[:environment]).to eq("bar")
- end
-
- it "should print the environment" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.run
- end
-
- end
-end
diff --git a/spec/unit/knife/node_from_file_spec.rb b/spec/unit/knife/node_from_file_spec.rb
deleted file mode 100644
index 00d6dd5d1a..0000000000
--- a/spec/unit/knife/node_from_file_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::NodeFromFile.load_deps
-
-describe Chef::Knife::NodeFromFile do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeFromFile.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "adam.rb" ]
- allow(@knife).to receive(:output).and_return(true)
- allow(@knife).to receive(:confirm).and_return(true)
- @node = Chef::Node.new
- allow(@node).to receive(:save)
- allow(@knife.loader).to receive(:load_from).and_return(@node)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should load from a file" do
- expect(@knife.loader).to receive(:load_from).with("nodes", "adam.rb").and_return(@node)
- @knife.run
- end
-
- it "should not print the Node" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- describe "with -p or --print-after" do
- it "should print the Node" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_list_spec.rb b/spec/unit/knife/node_list_spec.rb
deleted file mode 100644
index d594fffc14..0000000000
--- a/spec/unit/knife/node_list_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeList do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded
- @knife = Chef::Knife::NodeList.new
- allow(@knife).to receive(:output).and_return(true)
- @list = {
- "foo" => "http://example.com/foo",
- "bar" => "http://example.com/foo",
- }
- allow(Chef::Node).to receive(:list).and_return(@list)
- allow(Chef::Node).to receive(:list_by_environment).and_return(@list)
- end
-
- describe "run" do
- it "should list all of the nodes if -E is not specified" do
- expect(Chef::Node).to receive(:list).and_return(@list)
- @knife.run
- end
-
- it "should pretty print the list" do
- expect(Chef::Node).to receive(:list).and_return(@list)
- expect(@knife).to receive(:output).with(%w{bar foo})
- @knife.run
- end
-
- it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do
- Chef::Config[:environment] = "prod"
- expect(Chef::Node).to receive(:list_by_environment).with("prod").and_return(@list)
- @knife.run
- end
-
- describe "with -w or --with-uri" do
- it "should pretty print the hash" do
- @knife.config[:with_uri] = true
- expect(Chef::Node).to receive(:list).and_return(@list)
- expect(@knife).to receive(:output).with(@list)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_policy_set_spec.rb b/spec/unit/knife/node_policy_set_spec.rb
deleted file mode 100644
index 40b1d2617d..0000000000
--- a/spec/unit/knife/node_policy_set_spec.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-#
-# Author:: Piyush Awasthi (<piyush.awasthi@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodePolicySet do
- let(:node) do
- node = Chef::Node.new
- node.name("adam")
- node.run_list = ["role[base]"]
- node
- end
-
- let(:knife) do
- Chef::Log.logger = Logger.new(StringIO.new)
- Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
- knife_obj = Chef::Knife::NodePolicySet.new(bootstrap_cli_options)
- knife_obj.merge_configs
- allow(knife_obj.ui).to receive(:stderr).and_return(stderr)
- allow(knife_obj).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
- knife_obj
- end
-
- let(:stderr) { StringIO.new }
- let(:bootstrap_template) { nil }
- let(:bootstrap_cli_options) { [ ] }
-
- describe "#run" do
- context "when node_name is not given" do
- let(:bootstrap_cli_options) { %w{ } }
- it "returns an error that you must specify a node name" do
- expect { knife.send(:validate_node!) }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: You must specify a node name")
- end
- end
-
- context "when node is given" do
- let(:bootstrap_cli_options) { %w{ adam staging my-app } }
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
- allow(node).to receive(:save).and_return(true)
- knife.run
- end
- end
-
- context "when node not saved" do
- let(:bootstrap_cli_options) { %w{ adam staging my-app } }
- it "returns an error node not updated successfully" do
- allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
- allow(node).to receive(:save).and_return(false)
- knife.run
- expect(stderr.string.strip).to eq("Error in updating node #{node.name}")
- end
- end
-
- context "when the policy is set successfully on the node" do
- let(:bootstrap_cli_options) { %w{ adam staging my-app } }
- it "returns node updated successfully" do
- allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
- allow(node).to receive(:save).and_return(true)
- knife.run
- expect(stderr.string.strip).to eq("Successfully set the policy on node #{node.name}")
- end
- end
- end
-
- describe "handling policy options" do
- context "when policy_group and policy_name is not given" do
- let(:bootstrap_cli_options) { %w{ } }
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
- end
- end
-
- context "when only policy_name is given" do
- let(:bootstrap_cli_options) { %w{ adam staging } }
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
- end
- end
-
- context "when only policy_group is given" do
- let(:bootstrap_cli_options) { %w{ adam my-app } }
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.send(:validate_options!) }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: Policy group and name must be specified together")
- end
- end
-
- context "when policy_name and policy_group are given with no conflicting options" do
- let(:bootstrap_cli_options) { %w{ adam staging my-app } }
- it "passes options validation" do
- expect { knife.send(:validate_options!) }.to_not raise_error
- end
-
- it "returns value set in config" do
- allow(Chef::Node).to receive(:load).with(bootstrap_cli_options[0]).and_return(node)
- allow(node).to receive(:save).and_return(false)
- knife.run
- expect(node.policy_name).to eq("my-app")
- expect(node.policy_group).to eq("staging")
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_run_list_add_spec.rb b/spec/unit/knife/node_run_list_add_spec.rb
deleted file mode 100644
index 0148711fac..0000000000
--- a/spec/unit/knife/node_run_list_add_spec.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeRunListAdd do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeRunListAdd.new
- @knife.config = {
- after: nil,
- }
- @knife.name_args = [ "adam", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
- @node = Chef::Node.new
- allow(@node).to receive(:save).and_return(true)
- allow(Chef::Node).to receive(:load).and_return(@node)
- end
-
- describe "run" do
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam")
- @knife.run
- end
-
- it "should add to the run list" do
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- end
-
- it "should save the node" do
- expect(@node).to receive(:save)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.run
- end
-
- describe "with -a or --after specified" do
- it "should add to the run list after the specified entry" do
- @node.run_list << "role[acorns]"
- @node.run_list << "role[barn]"
- @knife.config[:after] = "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- expect(@node.run_list[2]).to eq("role[barn]")
- end
- end
-
- describe "with -b or --before specified" do
- it "should add to the run list before the specified entry" do
- @node.run_list << "role[acorns]"
- @node.run_list << "role[barn]"
- @knife.config[:before] = "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[acorns]")
- expect(@node.run_list[2]).to eq("role[barn]")
- end
- end
-
- describe "with both --after and --before specified" do
- it "exits with an error" do
- @node.run_list << "role[acorns]"
- @node.run_list << "role[barn]"
- @knife.config[:before] = "role[acorns]"
- @knife.config[:after] = "role[acorns]"
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-
- describe "with more than one role or recipe" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
- @node.run_list << "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- expect(@node.run_list[2]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe with space between items" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
- @node.run_list << "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- expect(@node.run_list[2]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe as different arguments" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
- @node.run_list << "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- expect(@node.run_list[2]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe as different arguments and list separated by commas" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
- @node.run_list << "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- expect(@node.run_list[2]).to eq("role[duck]")
- end
- end
-
- describe "with one role or recipe but with an extraneous comma" do
- it "should add to the run list one item" do
- @knife.name_args = [ "adam", "role[monkey]," ]
- @node.run_list << "role[acorns]"
- @knife.run
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[monkey]")
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb
deleted file mode 100644
index 1974821728..0000000000
--- a/spec/unit/knife/node_run_list_remove_spec.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeRunListRemove do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeRunListRemove.new
- @knife.config[:print_after] = nil
- @knife.name_args = [ "adam", "role[monkey]" ]
- @node = Chef::Node.new
- @node.name("knifetest-node")
- @node.run_list << "role[monkey]"
- allow(@node).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:output).and_return(true)
- allow(@knife.ui).to receive(:confirm).and_return(true)
-
- allow(Chef::Node).to receive(:load).and_return(@node)
- end
-
- describe "run" do
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam").and_return(@node)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @knife.run
- expect(@node.run_list[0]).not_to eq("role[monkey]")
- end
-
- it "should save the node" do
- expect(@node).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- @knife.config[:print_after] = true
- expect(@knife.ui).to receive(:output).with({ "knifetest-node" => { "run_list" => [] } })
- @knife.run
- end
-
- describe "run with a list of roles and recipes" do
- it "should remove the items from the run list" do
- @node.run_list << "role[monkey]"
- @node.run_list << "recipe[duck::type]"
- @knife.name_args = [ "adam", "role[monkey],recipe[duck::type]" ]
- @knife.run
- expect(@node.run_list).not_to include("role[monkey]")
- expect(@node.run_list).not_to include("recipe[duck::type]")
- end
-
- it "should remove the items from the run list when name args contains whitespace" do
- @node.run_list << "role[monkey]"
- @node.run_list << "recipe[duck::type]"
- @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]" ]
- @knife.run
- expect(@node.run_list).not_to include("role[monkey]")
- expect(@node.run_list).not_to include("recipe[duck::type]")
- end
-
- it "should remove the items from the run list when name args contains multiple run lists" do
- @node.run_list << "role[blah]"
- @node.run_list << "recipe[duck::type]"
- @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]", "role[blah]" ]
- @knife.run
- expect(@node.run_list).not_to include("role[monkey]")
- expect(@node.run_list).not_to include("recipe[duck::type]")
- end
-
- it "should warn when the thing to remove is not in the runlist" do
- @node.run_list << "role[blah]"
- @node.run_list << "recipe[duck::type]"
- @knife.name_args = [ "adam", "role[blork]" ]
- expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list")
- @knife.run
- end
-
- it "should warn even more when the thing to remove is not in the runlist and unqualified" do
- @node.run_list << "role[blah]"
- @node.run_list << "recipe[duck::type]"
- @knife.name_args = %w{adam blork}
- expect(@knife.ui).to receive(:warn).with("blork is not in the run list")
- expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/node_run_list_set_spec.rb b/spec/unit/knife/node_run_list_set_spec.rb
deleted file mode 100644
index 6246dfce6a..0000000000
--- a/spec/unit/knife/node_run_list_set_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# Author:: Mike Fiedler (<miketheman@gmail.com>)
-# Copyright:: Copyright 2013-2016, Mike Fiedler
-# 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 "spec_helper"
-
-describe Chef::Knife::NodeRunListSet do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::NodeRunListSet.new
- @knife.config = {}
- @knife.name_args = [ "adam", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
- @node = Chef::Node.new
- allow(@node).to receive(:save).and_return(true)
- allow(Chef::Node).to receive(:load).and_return(@node)
- end
-
- describe "run" do
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam")
- @knife.run
- end
-
- it "should set the run list" do
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- end
-
- it "should save the node" do
- expect(@node).to receive(:save)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.run
- end
-
- describe "with more than one role or recipe" do
- it "should set the run list to all the entries" do
- @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe with space between items" do
- it "should set the run list to all the entries" do
- @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe as different arguments" do
- it "should set the run list to all the entries" do
- @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[duck]")
- end
- end
-
- describe "with more than one role or recipe as different arguments and list separated by comas" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[duck]")
- end
- end
-
- describe "with one role or recipe but with an extraneous comma" do
- it "should add to the run list one item" do
- @knife.name_args = [ "adam", "role[monkey]," ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- end
- end
-
- describe "with an existing run list" do
- it "should overwrite any existing run list items" do
- @node.run_list << "role[acorns]"
- @node.run_list << "role[zebras]"
- expect(@node.run_list[0]).to eq("role[acorns]")
- expect(@node.run_list[1]).to eq("role[zebras]")
- expect(@node.run_list.run_list_items.size).to eq(2)
-
- @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
- @knife.run
- expect(@node.run_list[0]).to eq("role[monkey]")
- expect(@node.run_list[1]).to eq("role[duck]")
- expect(@node.run_list.run_list_items.size).to eq(2)
- end
- end
-
- end
-end
diff --git a/spec/unit/knife/node_show_spec.rb b/spec/unit/knife/node_show_spec.rb
deleted file mode 100644
index 037672501e..0000000000
--- a/spec/unit/knife/node_show_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::NodeShow do
-
- let(:node) do
- node = Chef::Node.new
- node.name("adam")
- node.run_list = ["role[base]"]
- node
- end
-
- let(:knife) do
- knife = Chef::Knife::NodeShow.new
- knife.name_args = [ "adam" ]
- knife
- end
-
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- end
-
- describe "run" do
- it "should load the node" do
- expect(Chef::Node).to receive(:load).with("adam").and_return(node)
- allow(knife).to receive(:output).and_return(true)
- knife.run
- end
-
- it "should pretty print the node, formatted for display" do
- knife.config[:format] = nil
- stdout = StringIO.new
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(Chef::Node).to receive(:load).and_return(node)
- knife.run
- expect(stdout.string).to eql("Node Name: adam\nEnvironment: _default\nFQDN: \nIP: \nRun List: \nRoles: \nRecipes: \nPlatform: \nTags: \n")
- end
-
- it "should pretty print json" do
- knife.config[:format] = "json"
- stdout = StringIO.new
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- expect(Chef::Node).to receive(:load).with("adam").and_return(node)
- knife.run
- expect(stdout.string).to eql("{\n \"name\": \"adam\",\n \"chef_environment\": \"_default\",\n \"run_list\": [\n\n]\n,\n \"normal\": {\n\n }\n}\n")
- end
- end
-end
diff --git a/spec/unit/knife/raw_spec.rb b/spec/unit/knife/raw_spec.rb
deleted file mode 100644
index 1f88195e65..0000000000
--- a/spec/unit/knife/raw_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::Raw do
- let(:rest) do
- r = double("Chef::Knife::Raw::RawInputServerAPI")
- allow(Chef::Knife::Raw::RawInputServerAPI).to receive(:new).and_return(r)
- r
- end
-
- let(:knife) do
- k = Chef::Knife::Raw.new
- k.config[:method] = "GET"
- k.name_args = [ "/nodes" ]
- k
- end
-
- describe "run" do
- it "should set the x-ops-request-source header when --proxy-auth is set" do
- knife.config[:proxy_auth] = true
- expect(rest).to receive(:request).with(:GET, "/nodes",
- { "Content-Type" => "application/json",
- "x-ops-request-source" => "web" }, false)
- knife.run
- end
- end
-end
diff --git a/spec/unit/knife/role_bulk_delete_spec.rb b/spec/unit/knife/role_bulk_delete_spec.rb
deleted file mode 100644
index 5af7c51584..0000000000
--- a/spec/unit/knife/role_bulk_delete_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleBulkDelete do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleBulkDelete.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = ["."]
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:confirm).and_return(true)
- @roles = {}
- %w{dev staging production}.each do |role_name|
- role = Chef::Role.new
- role.name(role_name)
- allow(role).to receive(:destroy).and_return(true)
- @roles[role_name] = role
- end
- allow(Chef::Role).to receive(:list).and_return(@roles)
- end
-
- describe "run" do
-
- it "should get the list of the roles" do
- expect(Chef::Role).to receive(:list).and_return(@roles)
- @knife.run
- end
-
- it "should print the roles you are about to delete" do
- @knife.run
- expect(@stdout.string).to match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/)
- end
-
- it "should confirm you really want to delete them" do
- expect(@knife.ui).to receive(:confirm)
- @knife.run
- end
-
- it "should delete each role" do
- @roles.each_value do |r|
- expect(r).to receive(:destroy)
- end
- @knife.run
- end
-
- it "should only delete roles that match the regex" do
- @knife.name_args = ["dev"]
- expect(@roles["dev"]).to receive(:destroy)
- expect(@roles["staging"]).not_to receive(:destroy)
- expect(@roles["production"]).not_to receive(:destroy)
- @knife.run
- end
-
- it "should exit if the regex is not provided" do
- @knife.name_args = []
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- end
-end
diff --git a/spec/unit/knife/role_create_spec.rb b/spec/unit/knife/role_create_spec.rb
deleted file mode 100644
index 0d563e40dd..0000000000
--- a/spec/unit/knife/role_create_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleCreate do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleCreate.new
- @knife.config = {
- description: nil,
- }
- @knife.name_args = [ "adam" ]
- allow(@knife).to receive(:output).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:save)
- allow(Chef::Role).to receive(:new).and_return(@role)
- allow(@knife).to receive(:edit_data).and_return(@role)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should create a new role" do
- expect(Chef::Role).to receive(:new).and_return(@role)
- @knife.run
- end
-
- it "should set the role name" do
- expect(@role).to receive(:name).with("adam")
- @knife.run
- end
-
- it "should not print the role" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- it "should allow you to edit the data" do
- expect(@knife).to receive(:edit_data).with(@role, object_class: Chef::Role)
- @knife.run
- end
-
- it "should save the role" do
- expect(@role).to receive(:save)
- @knife.run
- end
-
- describe "with -d or --description" do
- it "should set the description" do
- @knife.config[:description] = "All is bob"
- expect(@role).to receive(:description).with("All is bob")
- @knife.run
- end
- end
-
- describe "with -p or --print-after" do
- it "should pretty print the node, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output).with(@role)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_delete_spec.rb b/spec/unit/knife/role_delete_spec.rb
deleted file mode 100644
index d43f99689d..0000000000
--- a/spec/unit/knife/role_delete_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleDelete do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleDelete.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "adam" ]
- allow(@knife).to receive(:output).and_return(true)
- allow(@knife).to receive(:confirm).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:destroy).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should confirm that you want to delete" do
- expect(@knife).to receive(:confirm)
- @knife.run
- end
-
- it "should load the Role" do
- expect(Chef::Role).to receive(:load).with("adam").and_return(@role)
- @knife.run
- end
-
- it "should delete the Role" do
- expect(@role).to receive(:destroy).and_return(@role)
- @knife.run
- end
-
- it "should not print the Role" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- describe "with -p or --print-after" do
- it "should pretty print the Role, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_edit_spec.rb b/spec/unit/knife/role_edit_spec.rb
deleted file mode 100644
index faf9cf7d84..0000000000
--- a/spec/unit/knife/role_edit_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEdit do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleEdit.new
- @knife.config[:print_after] = nil
- @knife.name_args = [ "adam" ]
- allow(@knife.ui).to receive(:output).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:save)
- allow(Chef::Role).to receive(:load).and_return(@role)
- allow(@knife.ui).to receive(:edit_data).and_return(@role)
- allow(@knife.ui).to receive(:msg)
- end
-
- describe "run" do
- it "should load the role" do
- expect(Chef::Role).to receive(:load).with("adam").and_return(@role)
- @knife.run
- end
-
- it "should edit the role data" do
- expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role)
- @knife.run
- end
-
- it "should save the edited role data" do
- pansy = Chef::Role.new
-
- @role.name("new_role_name")
- expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy)
- expect(pansy).to receive(:save)
- @knife.run
- end
-
- it "should not save the unedited role data" do
- pansy = Chef::Role.new
-
- expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy)
- expect(pansy).not_to receive(:save)
- @knife.run
-
- end
-
- it "should not print the role" do
- expect(@knife.ui).not_to receive(:output)
- @knife.run
- end
-
- describe "with -p or --print-after" do
- it "should pretty print the role, formatted for display" do
- @knife.config[:print_after] = true
- expect(@knife.ui).to receive(:output).with(@role)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_env_run_list_add_spec.rb b/spec/unit/knife/role_env_run_list_add_spec.rb
deleted file mode 100644
index 13a05db33e..0000000000
--- a/spec/unit/knife/role_env_run_list_add_spec.rb
+++ /dev/null
@@ -1,217 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEnvRunListAdd do
- before(:each) do
- # Chef::Config[:role_name] = "websimian"
- # Chef::Config[:env_name] = "QA"
- @knife = Chef::Knife::RoleEnvRunListAdd.new
- @knife.config = {
- after: nil,
- }
- @knife.name_args = [ "will", "QA", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:save).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should have an empty default run list" do
- @knife.run
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should have a QA environment" do
- @knife.run
- expect(@role.active_run_list_for("QA")).to eq("QA")
- end
-
- it "should load the role named will" do
- expect(Chef::Role).to receive(:load).with("will")
- @knife.run
- end
-
- it "should be able to add an environment specific run list" do
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- end
-
- it "should save the role" do
- expect(@role).to receive(:save)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.run
- end
-
- describe "with -a or --after specified" do
- it "should not create a change if the specified 'after' never comes" do
- @role.run_list_for("_default") << "role[acorns]"
- @role.run_list_for("_default") << "role[barn]"
- @knife.config[:after] = "role[acorns]"
- @knife.name_args = [ "will", "QA", "role[pad]" ]
- @knife.run
- expect(@role.run_list_for("QA")[0]).to be_nil
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[barn]")
- expect(@role.run_list[2]).to be_nil
- end
-
- it "should add to the run list after the specified entries in the QA run list" do
- # Setup
- @role.run_list_for("_default") << "role[acorns]"
- @role.run_list_for("_default") << "role[barn]"
- @knife.run
- @role.run_list_for("QA") << "role[pencil]"
- @role.run_list_for("QA") << "role[pen]"
- # Configuration we are testing
- @knife.config[:after] = "role[pencil]"
- @knife.name_args = [ "will", "QA", "role[pad]", "role[whackadoo]" ]
- @knife.run
- # The actual tests
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[pencil]")
- expect(@role.run_list_for("QA")[2]).to eq("role[pad]")
- expect(@role.run_list_for("QA")[3]).to eq("role[whackadoo]")
- expect(@role.run_list_for("QA")[4]).to eq("role[pen]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[barn]")
- expect(@role.run_list[2]).to be_nil
- end
- end
-
- describe "with more than one role or recipe" do
- it "should add to the QA run list all the entries" do
- @knife.name_args = [ "will", "QA", "role[monkey],role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "with more than one role or recipe with space between items" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "QA", "role[monkey], role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "with more than one role or recipe as different arguments" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "with more than one role or recipe as different arguments and list separated by comas" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
- expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "Recipe with version number is allowed" do
- it "should add to the run list all the entries including the versioned recipe" do
- @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to eq("role[duck]")
- expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly@1.1.3]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "with one role or recipe but with an extraneous comma" do
- it "should add to the run list one item" do
- @role.run_list_for("_default") << "role[acorns]"
- @knife.name_args = [ "will", "QA", "role[monkey]," ]
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[monkey]")
- expect(@role.run_list_for("QA")[1]).to be_nil
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- describe "with more than one command" do
- it "should be able to the environment run list by running multiple knife commands" do
- @knife.name_args = [ "will", "QA", "role[blue]," ]
- @knife.run
- @knife.name_args = [ "will", "QA", "role[black]," ]
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[blue]")
- expect(@role.run_list_for("QA")[1]).to eq("role[black]")
- expect(@role.run_list[0]).to be_nil
- end
- end
-
- describe "with more than one environment" do
- it "should add to the run list a second environment in the specific run list" do
- @role.run_list_for("_default") << "role[acorns]"
- @knife.name_args = [ "will", "QA", "role[blue]," ]
- @knife.run
- @role.run_list_for("QA") << "role[walnuts]"
-
- @knife.name_args = [ "will", "PRD", "role[ball]," ]
- @knife.run
- @role.run_list_for("PRD") << "role[pen]"
-
- expect(@role.run_list_for("QA")[0]).to eq("role[blue]")
- expect(@role.run_list_for("PRD")[0]).to eq("role[ball]")
- expect(@role.run_list_for("QA")[1]).to eq("role[walnuts]")
- expect(@role.run_list_for("PRD")[1]).to eq("role[pen]")
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to be_nil
- end
- end
-
- end
-end
diff --git a/spec/unit/knife/role_env_run_list_clear_spec.rb b/spec/unit/knife/role_env_run_list_clear_spec.rb
deleted file mode 100644
index d4b9625550..0000000000
--- a/spec/unit/knife/role_env_run_list_clear_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEnvRunListClear do
- before(:each) do
- Chef::Config[:role_name] = "will"
- Chef::Config[:env_name] = "QA"
- @setup = Chef::Knife::RoleEnvRunListAdd.new
- @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ]
-
- @knife = Chef::Knife::RoleEnvRunListClear.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = %w{will QA}
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list_for("QA")[0]).to be_nil
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "should clear an environmental run list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = %w{will QA}
- @knife.run
- expect(@role.run_list_for("QA")[0]).to be_nil
- expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
- expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
- expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
- expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
- expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_env_run_list_remove_spec.rb b/spec/unit/knife/role_env_run_list_remove_spec.rb
deleted file mode 100644
index 7f9b41475c..0000000000
--- a/spec/unit/knife/role_env_run_list_remove_spec.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEnvRunListRemove do
- before(:each) do
- Chef::Config[:role_name] = "will"
- Chef::Config[:env_name] = "QA"
- @setup = Chef::Knife::RoleEnvRunListAdd.new
- @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ]
-
- @knife = Chef::Knife::RoleEnvRunListRemove.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "QA", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list_for("QA")[0]).not_to eq("role[monkey]")
- expect(@role.run_list_for("QA")[0]).to eq("role[person]")
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "run with a list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "QA", "role[monkey]" ]
- @knife.run
- @knife.name_args = [ "will", "QA", "recipe[duck::type]" ]
- @knife.run
- expect(@role.run_list_for("QA")).not_to include("role[monkey]")
- expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]")
- expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("QA")[1]).to eq("role[person]")
- expect(@role.run_list_for("QA")[2]).to eq("role[bird]")
- expect(@role.run_list_for("QA")[3]).to eq("role[town]")
- expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
- expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
- expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
- expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
- expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_env_run_list_replace_spec.rb b/spec/unit/knife/role_env_run_list_replace_spec.rb
deleted file mode 100644
index 93b233efdc..0000000000
--- a/spec/unit/knife/role_env_run_list_replace_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEnvRunListReplace do
- before(:each) do
- Chef::Config[:role_name] = "will"
- Chef::Config[:env_name] = "QA"
- @setup = Chef::Knife::RoleEnvRunListAdd.new
- @setup.name_args = [ "will", "QA", "role[monkey]", "role[dude]", "role[fixer]" ]
-
- @knife = Chef::Knife::RoleEnvRunListReplace.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "QA", "role[dude]", "role[person]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list_for("QA")[1]).not_to eq("role[dude]")
- expect(@role.run_list_for("QA")[1]).to eq("role[person]")
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "run with a list of roles and recipes" do
- it "should replace the items from the run list" do
- @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "QA", "role[monkey]", "role[gibbon]" ]
- @knife.run
- @knife.name_args = [ "will", "QA", "recipe[duck::type]", "recipe[duck::mallard]" ]
- @knife.run
- expect(@role.run_list_for("QA")).not_to include("role[monkey]")
- expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]")
- expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("QA")[1]).to eq("role[gibbon]")
- expect(@role.run_list_for("QA")[2]).to eq("recipe[duck::mallard]")
- expect(@role.run_list_for("QA")[3]).to eq("role[person]")
- expect(@role.run_list_for("QA")[4]).to eq("role[bird]")
- expect(@role.run_list_for("QA")[5]).to eq("role[town]")
- expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
- expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
- expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
- expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
- expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
- expect(@role.run_list[0]).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_env_run_list_set_spec.rb b/spec/unit/knife/role_env_run_list_set_spec.rb
deleted file mode 100644
index d35e4dbb17..0000000000
--- a/spec/unit/knife/role_env_run_list_set_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleEnvRunListSet do
- before(:each) do
- Chef::Config[:role_name] = "will"
- Chef::Config[:env_name] = "QA"
- @setup = Chef::Knife::RoleEnvRunListAdd.new
- @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]", "role[bucket]" ]
-
- @knife = Chef::Knife::RoleEnvRunListSet.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "QA", "role[owen]", "role[mauntel]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should replace all the items in the runlist with what is specified" do
- @setup.run
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[owen]")
- expect(@role.run_list_for("QA")[1]).to eq("role[mauntel]")
- expect(@role.run_list_for("QA")[2]).to be_nil
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "should clear an environmental run list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "QA", "role[coke]", "role[pepsi]" ]
- @knife.run
- expect(@role.run_list_for("QA")[0]).to eq("role[coke]")
- expect(@role.run_list_for("QA")[1]).to eq("role[pepsi]")
- expect(@role.run_list_for("QA")[2]).to be_nil
- expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]")
- expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]")
- expect(@role.run_list_for("PRD")[3]).to eq("role[person]")
- expect(@role.run_list_for("PRD")[4]).to eq("role[bird]")
- expect(@role.run_list_for("PRD")[5]).to eq("role[town]")
- expect(@role.run_list[0]).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_from_file_spec.rb b/spec/unit/knife/role_from_file_spec.rb
deleted file mode 100644
index 51e94d31e3..0000000000
--- a/spec/unit/knife/role_from_file_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::RoleFromFile.load_deps
-
-describe Chef::Knife::RoleFromFile do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleFromFile.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "adam.rb" ]
- allow(@knife).to receive(:output).and_return(true)
- allow(@knife).to receive(:confirm).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:save)
- allow(@knife.loader).to receive(:load_from).and_return(@role)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- it "should load from a file" do
- expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role)
- @knife.run
- end
-
- it "should not print the role" do
- expect(@knife).not_to receive(:output)
- @knife.run
- end
-
- describe "with -p or --print-after" do
- it "should print the role" do
- @knife.config[:print_after] = true
- expect(@knife).to receive(:output)
- @knife.run
- end
- end
- end
-
- describe "run with multiple arguments" do
- it "should load each file" do
- @knife.name_args = [ "adam.rb", "caleb.rb" ]
- expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role)
- expect(@knife.loader).to receive(:load_from).with("roles", "caleb.rb").and_return(@role)
- @knife.run
- end
- end
-
-end
diff --git a/spec/unit/knife/role_list_spec.rb b/spec/unit/knife/role_list_spec.rb
deleted file mode 100644
index dea2e874a4..0000000000
--- a/spec/unit/knife/role_list_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleList do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::RoleList.new
- allow(@knife).to receive(:output).and_return(true)
- @list = {
- "foo" => "http://example.com/foo",
- "bar" => "http://example.com/foo",
- }
- allow(Chef::Role).to receive(:list).and_return(@list)
- end
-
- describe "run" do
- it "should list the roles" do
- expect(Chef::Role).to receive(:list).and_return(@list)
- @knife.run
- end
-
- it "should pretty print the list" do
- expect(Chef::Role).to receive(:list).and_return(@list)
- expect(@knife).to receive(:output).with(%w{bar foo})
- @knife.run
- end
-
- describe "with -w or --with-uri" do
- it "should pretty print the hash" do
- @knife.config[:with_uri] = true
- expect(Chef::Role).to receive(:list).and_return(@list)
- expect(@knife).to receive(:output).with(@list)
- @knife.run
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_run_list_add_spec.rb b/spec/unit/knife/role_run_list_add_spec.rb
deleted file mode 100644
index 6f222ee80a..0000000000
--- a/spec/unit/knife/role_run_list_add_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleRunListAdd do
- before(:each) do
- # Chef::Config[:role_name] = "websimian"
- # Chef::Config[:env_name] = "QA"
- @knife = Chef::Knife::RoleRunListAdd.new
- @knife.config = {
- after: nil,
- }
- @knife.name_args = [ "will", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
- @role = Chef::Role.new
- allow(@role).to receive(:save).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should have a run list with the monkey role" do
- @knife.run
- expect(@role.run_list[0]).to eq("role[monkey]")
- end
-
- it "should load the role named will" do
- expect(Chef::Role).to receive(:load).with("will")
- @knife.run
- end
-
- it "should save the role" do
- expect(@role).to receive(:save)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.run
- end
-
- describe "with -a or --after specified" do
- it "should not create a change if the specified 'after' never comes" do
- @role.run_list_for("_default") << "role[acorns]"
- @role.run_list_for("_default") << "role[barn]"
- @knife.config[:after] = "role[tree]"
- @knife.name_args = [ "will", "role[pad]" ]
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[barn]")
- expect(@role.run_list[2]).to be_nil
- end
-
- it "should add to the run list after the specified entries in the default run list" do
- # Setup
- @role.run_list_for("_default") << "role[acorns]"
- @role.run_list_for("_default") << "role[barn]"
- # Configuration we are testing
- @knife.config[:after] = "role[acorns]"
- @knife.name_args = [ "will", "role[pad]", "role[whackadoo]" ]
- @knife.run
- # The actual tests
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[pad]")
- expect(@role.run_list[2]).to eq("role[whackadoo]")
- expect(@role.run_list[3]).to eq("role[barn]")
- expect(@role.run_list[4]).to be_nil
- end
- end
-
- describe "with more than one role or recipe" do
- it "should add to the QA run list all the entries" do
- @knife.name_args = [ "will", "role[monkey],role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to eq("role[duck]")
- expect(@role.run_list[3]).to be_nil
- end
- end
-
- describe "with more than one role or recipe with space between items" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "role[monkey], role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to eq("role[duck]")
- expect(@role.run_list[3]).to be_nil
- end
- end
-
- describe "with more than one role or recipe as different arguments" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "role[monkey]", "role[duck]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to eq("role[duck]")
- expect(@role.run_list[3]).to be_nil
- end
- end
-
- describe "with more than one role or recipe as different arguments and list separated by comas" do
- it "should add to the run list all the entries" do
- @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to eq("role[duck]")
- expect(@role.run_list[3]).to eq("recipe[bird::fly]")
- expect(@role.run_list[4]).to be_nil
- end
- end
-
- describe "Recipe with version number is allowed" do
- it "should add to the run list all the entries including the versioned recipe" do
- @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ]
- @role.run_list_for("_default") << "role[acorns]"
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to eq("role[duck]")
- expect(@role.run_list[3]).to eq("recipe[bird::fly@1.1.3]")
- expect(@role.run_list[4]).to be_nil
- end
- end
-
- describe "with one role or recipe but with an extraneous comma" do
- it "should add to the run list one item" do
- @role.run_list_for("_default") << "role[acorns]"
- @knife.name_args = [ "will", "role[monkey]," ]
- @knife.run
- expect(@role.run_list[0]).to eq("role[acorns]")
- expect(@role.run_list[1]).to eq("role[monkey]")
- expect(@role.run_list[2]).to be_nil
- end
- end
-
- describe "with more than one command" do
- it "should be able to the environment run list by running multiple knife commands" do
- @knife.name_args = [ "will", "role[blue]," ]
- @knife.run
- @knife.name_args = [ "will", "role[black]," ]
- @knife.run
- expect(@role.run_list[0]).to eq("role[blue]")
- expect(@role.run_list[1]).to eq("role[black]")
- expect(@role.run_list[2]).to be_nil
- end
- end
-
- end
-end
diff --git a/spec/unit/knife/role_run_list_clear_spec.rb b/spec/unit/knife/role_run_list_clear_spec.rb
deleted file mode 100644
index 327a9979b0..0000000000
--- a/spec/unit/knife/role_run_list_clear_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleRunListClear do
- before(:each) do
- Chef::Config[:role_name] = "will"
- @setup = Chef::Knife::RoleRunListAdd.new
- @setup.name_args = [ "will", "role[monkey]", "role[person]" ]
-
- @knife = Chef::Knife::RoleRunListClear.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list[0]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "should clear an environmental run list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will" ]
- @knife.run
- expect(@role.run_list[0]).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_run_list_remove_spec.rb b/spec/unit/knife/role_run_list_remove_spec.rb
deleted file mode 100644
index 200a559c08..0000000000
--- a/spec/unit/knife/role_run_list_remove_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleRunListRemove do
- before(:each) do
- Chef::Config[:role_name] = "will"
- @setup = Chef::Knife::RoleRunListAdd.new
- @setup.name_args = [ "will", "role[monkey]", "role[person]" ]
-
- @knife = Chef::Knife::RoleRunListRemove.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "role[monkey]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list[0]).to eq("role[person]")
- expect(@role.run_list[1]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "run with a list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "role[monkey]" ]
- @knife.run
- @knife.name_args = [ "will", "recipe[duck::type]" ]
- @knife.run
- expect(@role.run_list).not_to include("role[monkey]")
- expect(@role.run_list).not_to include("recipe[duck::type]")
- expect(@role.run_list[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list[1]).to eq("role[person]")
- expect(@role.run_list[2]).to eq("role[bird]")
- expect(@role.run_list[3]).to eq("role[town]")
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_run_list_replace_spec.rb b/spec/unit/knife/role_run_list_replace_spec.rb
deleted file mode 100644
index 1957403fb1..0000000000
--- a/spec/unit/knife/role_run_list_replace_spec.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleRunListReplace do
- before(:each) do
- Chef::Config[:role_name] = "will"
- @setup = Chef::Knife::RoleRunListAdd.new
- @setup.name_args = [ "will", "role[monkey]", "role[dude]", "role[fixer]" ]
-
- @knife = Chef::Knife::RoleRunListReplace.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "role[dude]", "role[person]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should remove the item from the run list" do
- @setup.run
- @knife.run
- expect(@role.run_list[0]).to eq("role[monkey]")
- expect(@role.run_list[1]).not_to eq("role[dude]")
- expect(@role.run_list[1]).to eq("role[person]")
- expect(@role.run_list[2]).to eq("role[fixer]")
- expect(@role.run_list[3]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "run with a list of roles and recipes" do
- it "should replace the items from the run list" do
- @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "role[monkey]", "role[gibbon]" ]
- @knife.run
- @knife.name_args = [ "will", "recipe[duck::type]", "recipe[duck::mallard]" ]
- @knife.run
- expect(@role.run_list).not_to include("role[monkey]")
- expect(@role.run_list).not_to include("recipe[duck::type]")
- expect(@role.run_list[0]).to eq("recipe[orange::chicken]")
- expect(@role.run_list[1]).to eq("role[gibbon]")
- expect(@role.run_list[2]).to eq("recipe[duck::mallard]")
- expect(@role.run_list[3]).to eq("role[person]")
- expect(@role.run_list[4]).to eq("role[bird]")
- expect(@role.run_list[5]).to eq("role[town]")
- expect(@role.run_list[6]).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_run_list_set_spec.rb b/spec/unit/knife/role_run_list_set_spec.rb
deleted file mode 100644
index 06098c585e..0000000000
--- a/spec/unit/knife/role_run_list_set_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Will Albenzi (<walbenzi@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::RoleRunListSet do
- before(:each) do
- Chef::Config[:role_name] = "will"
- @setup = Chef::Knife::RoleRunListAdd.new
- @setup.name_args = [ "will", "role[monkey]", "role[person]", "role[bucket]" ]
-
- @knife = Chef::Knife::RoleRunListSet.new
- @knife.config = {
- print_after: nil,
- }
- @knife.name_args = [ "will", "role[owen]", "role[mauntel]" ]
- allow(@knife).to receive(:output).and_return(true)
-
- @role = Chef::Role.new
- @role.name("will")
- allow(@role).to receive(:save).and_return(true)
-
- allow(@knife.ui).to receive(:confirm).and_return(true)
- allow(Chef::Role).to receive(:load).and_return(@role)
-
- end
-
- describe "run" do
-
- # it "should display all the things" do
- # @knife.run
- # @role.to_json.should == 'show all the things'
- # end
-
- it "should load the node" do
- expect(Chef::Role).to receive(:load).with("will").and_return(@role)
- @knife.run
- end
-
- it "should replace all the items in the runlist with what is specified" do
- @setup.run
- @knife.run
- expect(@role.run_list[0]).to eq("role[owen]")
- expect(@role.run_list[1]).to eq("role[mauntel]")
- expect(@role.run_list[2]).to be_nil
- end
-
- it "should save the node" do
- expect(@role).to receive(:save).and_return(true)
- @knife.run
- end
-
- it "should print the run list" do
- expect(@knife).to receive(:output).and_return(true)
- @knife.config[:print_after] = true
- @setup.run
- @knife.run
- end
-
- describe "should clear an environmental run list of roles and recipes" do
- it "should remove the items from the run list" do
- @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ]
- @setup.run
- @knife.name_args = [ "will", "role[coke]", "role[pepsi]" ]
- @knife.run
- expect(@role.run_list[0]).to eq("role[coke]")
- expect(@role.run_list[1]).to eq("role[pepsi]")
- expect(@role.run_list[2]).to be_nil
- expect(@role.run_list[3]).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/knife/role_show_spec.rb b/spec/unit/knife/role_show_spec.rb
deleted file mode 100644
index fe48e2f940..0000000000
--- a/spec/unit/knife/role_show_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright 2014-2016, Lamont Granquist
-# 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 "spec_helper"
-
-describe Chef::Knife::RoleShow do
- let(:role) { "base" }
-
- let(:knife) do
- knife = Chef::Knife::RoleShow.new
- knife.name_args = [ role ]
- knife
- end
-
- let(:role_mock) { double("role_mock") }
-
- describe "run" do
- it "should list the role" do
- expect(Chef::Role).to receive(:load).with("base").and_return(role_mock)
- expect(knife).to receive(:format_for_display).with(role_mock)
- knife.run
- end
-
- it "should pretty print json" do
- knife.config[:format] = "json"
- stdout = StringIO.new
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- fake_role_contents = { "foo" => "bar", "baz" => "qux" }
- expect(Chef::Role).to receive(:load).with("base").and_return(fake_role_contents)
- knife.run
- expect(stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n")
- end
-
- context "without a role name" do
- let(:role) {}
-
- it "should print usage and exit when a role name is not provided" do
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
- end
- end
-end
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
deleted file mode 100644
index 8606045e8c..0000000000
--- a/spec/unit/knife/ssh_spec.rb
+++ /dev/null
@@ -1,403 +0,0 @@
-#
-# Author:: Bryan McLellan <btm@chef.io>
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "net/ssh"
-require "net/ssh/multi"
-
-describe Chef::Knife::Ssh do
- let(:query_result) { double("chef search results") }
-
- before do
- Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
- @knife = Chef::Knife::Ssh.new
- @knife.merge_configs
- @node_foo = {}
- @node_foo["fqdn"] = "foo.example.org"
- @node_foo["ipaddress"] = "10.0.0.1"
- @node_foo["cloud"] = {}
-
- @node_bar = {}
- @node_bar["fqdn"] = "bar.example.org"
- @node_bar["ipaddress"] = "10.0.0.2"
- @node_bar["cloud"] = {}
-
- end
-
- describe "#configure_session" do
- context "manual is set to false (default)" do
- before do
- @knife.config[:manual] = false
- allow(query_result).to receive(:search).with(any_args).and_yield(@node_foo).and_yield(@node_bar)
- allow(Chef::Search::Query).to receive(:new).and_return(query_result)
- end
-
- def self.should_return_specified_attributes
- it "returns an array of the attributes specified on the command line OR config file, if only one is set" do
- @node_bar["target"] = "10.0.0.2"
- @node_foo["target"] = "10.0.0.1"
- @node_bar["prefix"] = "bar"
- @node_foo["prefix"] = "foo"
- @knife.config[:ssh_attribute] = "ipaddress"
- @knife.config[:prefix_attribute] = "name"
- Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file
- Chef::Config[:knife][:prefix_attribute] = "name" # this value will be in the config file
- expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]])
- @knife.configure_session
- end
-
- it "returns an array of the attributes specified on the command line even when a config value is set" do
- @node_bar["target"] = "10.0.0.2"
- @node_foo["target"] = "10.0.0.1"
- @node_bar["prefix"] = "bar"
- @node_foo["prefix"] = "foo"
- Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file
- Chef::Config[:knife][:prefix_attribute] = "config_file" # this value will be in the config file
- @knife.config[:ssh_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
- @knife.config[:prefix_attribute] = "name" # this is the value of the command line via #configure_attribute
- expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil, "foo"], ["10.0.0.2", nil, "bar"]])
- @knife.configure_session
- end
- end
-
- it "searches for and returns an array of fqdns" do
- expect(@knife).to receive(:session_from_list).with([
- ["foo.example.org", nil, nil],
- ["bar.example.org", nil, nil],
- ])
- @knife.configure_session
- end
-
- should_return_specified_attributes
-
- context "when cloud hostnames are available" do
- before do
- @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
- @node_bar["cloud"]["public_hostname"] = "ec2-10-0-0-2.compute-1.amazonaws.com"
- end
- it "returns an array of cloud public hostnames" do
- expect(@knife).to receive(:session_from_list).with([
- ["ec2-10-0-0-1.compute-1.amazonaws.com", nil, nil],
- ["ec2-10-0-0-2.compute-1.amazonaws.com", nil, nil],
- ])
- @knife.configure_session
- end
-
- should_return_specified_attributes
- end
-
- context "when cloud hostnames are available but empty" do
- before do
- @node_foo["cloud"]["public_hostname"] = ""
- @node_bar["cloud"]["public_hostname"] = ""
- end
-
- it "returns an array of fqdns" do
- expect(@knife).to receive(:session_from_list).with([
- ["foo.example.org", nil, nil],
- ["bar.example.org", nil, nil],
- ])
- @knife.configure_session
- end
-
- should_return_specified_attributes
- end
-
- it "should raise an error if no host are found" do
- allow(query_result).to receive(:search).with(any_args)
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:exit).with(10)
- @knife.configure_session
- end
-
- context "when there are some hosts found but they do not have an attribute to connect with" do
- before do
- @node_foo["fqdn"] = nil
- @node_bar["fqdn"] = nil
- end
-
- it "should raise a specific error (CHEF-3402)" do
- expect(@knife.ui).to receive(:fatal).with(/^2 nodes found/)
- expect(@knife).to receive(:exit).with(10)
- @knife.configure_session
- end
- end
-
- context "when there are some hosts found but IPs duplicated if duplicated_fqdns option sets :fatal" do
- before do
- @knife.config[:duplicated_fqdns] = :fatal
- @node_foo["fqdn"] = "foo.example.org"
- @node_bar["fqdn"] = "foo.example.org"
- end
-
- it "should raise a specific error" do
- expect(@knife.ui).to receive(:fatal).with(/^SSH node is duplicated: foo\.example\.org/)
- expect(@knife).to receive(:exit).with(10)
- expect(@knife).to receive(:session_from_list).with([
- ["foo.example.org", nil, nil],
- ["foo.example.org", nil, nil],
- ])
- @knife.configure_session
- end
- end
- end
-
- context "manual is set to true" do
- before do
- @knife.config[:manual] = true
- end
-
- it "returns an array of provided values" do
- @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"])
- expect(@knife).to receive(:session_from_list).with(["foo.example.org", "bar.example.org"])
- @knife.configure_session
- end
- end
- end
-
- describe "#get_prefix_attribute" do
- # Order of precedence for prefix
- # 1) config value (cli or knife config)
- # 2) nil
- before do
- Chef::Config[:knife][:prefix_attribute] = nil
- @knife.config[:prefix_attribute] = nil
- @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
- @node_bar["cloud"]["public_hostname"] = ""
- end
-
- it "should return nil by default" do
- expect(@knife.get_prefix_attribute({})).to eq(nil)
- end
-
- it "should favor config over nil" do
- @node_foo["prefix"] = "config"
- expect( @knife.get_prefix_attribute(@node_foo)).to eq("config")
- end
- end
-
- describe "#get_ssh_attribute" do
- # Order of precedence for ssh target
- # 1) config value (cli or knife config)
- # 2) cloud attribute
- # 3) fqdn
- before do
- Chef::Config[:knife][:ssh_attribute] = nil
- @knife.config[:ssh_attribute] = nil
- @node_foo["cloud"]["public_hostname"] = "ec2-10-0-0-1.compute-1.amazonaws.com"
- @node_bar["cloud"]["public_hostname"] = ""
- end
-
- it "should return fqdn by default" do
- expect(@knife.get_ssh_attribute({ "fqdn" => "fqdn" })).to eq("fqdn")
- end
-
- it "should return cloud.public_hostname attribute if available" do
- expect(@knife.get_ssh_attribute(@node_foo)).to eq("ec2-10-0-0-1.compute-1.amazonaws.com")
- end
-
- it "should favor config over cloud and default" do
- @node_foo["target"] = "config"
- expect( @knife.get_ssh_attribute(@node_foo)).to eq("config")
- end
-
- it "should return fqdn if cloud.hostname is empty" do
- expect( @knife.get_ssh_attribute(@node_bar)).to eq("bar.example.org")
- end
- end
-
- describe "#session_from_list" do
- before :each do
- @knife.instance_variable_set(:@longest, 0)
- ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 }
- allow(Net::SSH).to receive(:configuration_for).with("the.b.org", true).and_return(ssh_config)
- end
-
- it "uses the port from an ssh config file" do
- @knife.session_from_list([["the.b.org", nil, nil]])
- expect(@knife.session.servers[0].port).to eq(23)
- end
-
- it "uses the port from a cloud attr" do
- @knife.session_from_list([["the.b.org", 123, nil]])
- expect(@knife.session.servers[0].port).to eq(123)
- end
-
- it "uses the prefix from list" do
- @knife.session_from_list([["the.b.org", nil, "b-team"]])
- expect(@knife.session.servers[0][:prefix]).to eq("b-team")
- end
-
- it "defaults to a prefix of host" do
- @knife.session_from_list([["the.b.org", nil, nil]])
- expect(@knife.session.servers[0][:prefix]).to eq("the.b.org")
- end
-
- it "defaults to a timeout of 120 seconds" do
- @knife.session_from_list([["the.b.org", nil, nil]])
- expect(@knife.session.servers[0].options[:timeout]).to eq(120)
- end
-
- it "uses the timeout from the CLI" do
- @knife.config = {}
- Chef::Config[:knife][:ssh_timeout] = nil
- @knife.config[:ssh_timeout] = 5
- @knife.session_from_list([["the.b.org", nil, nil]])
- @knife.merge_configs
- expect(@knife.session.servers[0].options[:timeout]).to eq(5)
- end
-
- it "uses the timeout from knife config" do
- @knife.config = {}
- Chef::Config[:knife][:ssh_timeout] = 6
- @knife.merge_configs
- @knife.session_from_list([["the.b.org", nil, nil]])
- expect(@knife.session.servers[0].options[:timeout]).to eq(6)
- end
-
- it "uses the user from an ssh config file" do
- @knife.session_from_list([["the.b.org", 123, nil]])
- expect(@knife.session.servers[0].user).to eq("locutus")
- end
-
- it "uses keepalive settings from an ssh config file" do
- @knife.session_from_list([["the.b.org", 123, nil]])
- expect(@knife.session.servers[0].options[:keepalive]).to be true
- expect(@knife.session.servers[0].options[:keepalive_interval]).to eq 60
- end
- end
-
- describe "#ssh_command" do
- let(:execution_channel) { double(:execution_channel, on_data: nil, on_extended_data: nil) }
- let(:session_channel) { double(:session_channel, request_pty: nil) }
-
- let(:execution_channel2) { double(:execution_channel, on_data: nil, on_extended_data: nil) }
- let(:session_channel2) { double(:session_channel, request_pty: nil) }
-
- let(:session) { double(:session, loop: nil) }
-
- let(:command) { "false" }
-
- before do
- expect(execution_channel)
- .to receive(:on_request)
- .and_yield(nil, double(:data_stream, read_long: exit_status))
-
- expect(session_channel)
- .to receive(:exec)
- .with(command)
- .and_yield(execution_channel, true)
-
- expect(execution_channel2)
- .to receive(:on_request)
- .and_yield(nil, double(:data_stream, read_long: exit_status2))
-
- expect(session_channel2)
- .to receive(:exec)
- .with(command)
- .and_yield(execution_channel2, true)
-
- expect(session)
- .to receive(:open_channel)
- .and_yield(session_channel)
- .and_yield(session_channel2)
- end
-
- context "both connections return 0" do
- let(:exit_status) { 0 }
- let(:exit_status2) { 0 }
-
- it "returns a 0 exit code" do
- expect(@knife.ssh_command(command, session)).to eq(0)
- end
- end
-
- context "the first connection returns 1 and the second returns 0" do
- let(:exit_status) { 1 }
- let(:exit_status2) { 0 }
-
- it "returns a non-zero exit code" do
- expect(@knife.ssh_command(command, session)).to eq(1)
- end
- end
-
- context "the first connection returns 1 and the second returns 2" do
- let(:exit_status) { 1 }
- let(:exit_status2) { 2 }
-
- it "returns a non-zero exit code" do
- expect(@knife.ssh_command(command, session)).to eq(2)
- end
- end
- end
-
- describe "#tmux" do
- before do
- ssh_config = { timeout: 50, user: "locutus", port: 23, keepalive: true, keepalive_interval: 60 }
- allow(Net::SSH).to receive(:configuration_for).with("foo.example.org", true).and_return(ssh_config)
- @query = Chef::Search::Query.new
- expect(@query).to receive(:search).and_yield(@node_foo)
- allow(Chef::Search::Query).to receive(:new).and_return(@query)
- allow(@knife).to receive(:exec).and_return(0)
- end
-
- it "filters out invalid characters from tmux session name" do
- @knife.name_args = ["name:foo.example.org", "tmux"]
- expect(@knife).to receive(:shell_out!).with("tmux new-session -d -s 'knife ssh name=foo-example-org' -n 'foo.example.org' 'ssh locutus@foo.example.org' ")
- @knife.run
- end
- end
-
- describe "#run" do
-
- it "should print usage and exit when a SEARCH QUERY is not provided" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal).with(/You must specify the SEARCH QUERY./)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- context "exit" do
- before do
- @query = Chef::Search::Query.new
- expect(@query).to receive(:search).and_yield(@node_foo)
- allow(Chef::Search::Query).to receive(:new).and_return(@query)
- allow(@knife).to receive(:ssh_command).and_return(exit_code)
- @knife.name_args = ["*:*", "false"]
- end
-
- context "with an error" do
- let(:exit_code) { 1 }
-
- it "should exit with a non-zero exit code" do
- expect(@knife).to receive(:exit).with(exit_code)
- @knife.run
- end
- end
-
- context "with no error" do
- let(:exit_code) { 0 }
-
- it "should not exit" do
- expect(@knife).not_to receive(:exit)
- @knife.run
- end
- end
- end
- end
-end
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
deleted file mode 100644
index 1165da4539..0000000000
--- a/spec/unit/knife/ssl_check_spec.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "stringio"
-
-describe Chef::Knife::SslCheck do
-
- let(:name_args) { [] }
- let(:stdout_io) { StringIO.new }
- let(:stderr_io) { StringIO.new }
-
- def stderr
- stderr_io.string
- end
-
- def stdout
- stdout_io.string
- end
-
- subject(:ssl_check) do
- s = Chef::Knife::SslCheck.new
- allow(s.ui).to receive(:stdout).and_return(stdout_io)
- allow(s.ui).to receive(:stderr).and_return(stderr_io)
- s.name_args = name_args
- s
- end
-
- before do
- Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
- end
-
- context "when no arguments are given" do
- it "uses the chef_server_url as the host to check" do
- expect(ssl_check.host).to eq("example.com")
- expect(ssl_check.port).to eq(8443)
- end
- end
-
- context "when a specific URI is given" do
- let(:name_args) { %w{https://example.test:10443/foo} }
-
- it "checks the SSL configuration against the given host" do
- expect(ssl_check.host).to eq("example.test")
- expect(ssl_check.port).to eq(10443)
- end
- end
-
- context "when an invalid URI is given" do
-
- let(:name_args) { %w{foo.test} }
-
- it "prints an error and exits" do
- expect { ssl_check.run }.to raise_error(SystemExit)
- expected_stdout = <<~E
- USAGE: knife ssl check [URL] (options)
- E
- expected_stderr = <<~E
- ERROR: Given URI: `foo.test' is invalid
- E
- expect(stdout_io.string).to eq(expected_stdout)
- expect(stderr_io.string).to eq(expected_stderr)
- end
-
- context "and its malformed enough to make URI.parse barf" do
-
- let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
-
- it "prints an error and exits" do
- expect { ssl_check.run }.to raise_error(SystemExit)
- expected_stdout = <<~E
- USAGE: knife ssl check [URL] (options)
- E
- expected_stderr = <<~E
- ERROR: Given URI: `#{name_args[0]}' is invalid
- E
- expect(stdout_io.string).to eq(expected_stdout)
- expect(stderr_io.string).to eq(expected_stderr)
- end
- end
- end
-
- describe "verifying trusted certificate X509 properties" do
- let(:name_args) { %w{https://foo.example.com:8443} }
-
- let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") }
- let(:trusted_cert_file) { File.join(trusted_certs_dir, "example.crt") }
-
- let(:store) { OpenSSL::X509::Store.new }
- let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_cert_file)) }
-
- before do
- Chef::Config[:trusted_certs_dir] = trusted_certs_dir
- allow(ssl_check).to receive(:trusted_certificates).and_return([trusted_cert_file])
- allow(store).to receive(:add_cert).with(certificate)
- allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
- allow(OpenSSL::X509::Certificate).to receive(:new).with(IO.read(trusted_cert_file)).and_return(certificate)
- allow(ssl_check).to receive(:verify_cert).and_return(true)
- allow(ssl_check).to receive(:verify_cert_host).and_return(true)
- end
-
- context "when the trusted certificates directory is not glob escaped", :windows_only do
- let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA.tr("/", "\\"), "trusted_certs") }
-
- before do
- allow(ssl_check).to receive(:trusted_certificates).and_call_original
- allow(store).to receive(:verify).with(certificate).and_return(true)
- end
-
- it "escpaes the trusted certificates directory" do
- expect(Dir).to receive(:glob)
- .with("#{ChefConfig::PathHelper.escape_glob_dir(trusted_certs_dir)}/*.{crt,pem}")
- .and_return([trusted_cert_file])
- ssl_check.run
- end
- end
-
- context "when the trusted certificates have valid X509 properties" do
- before do
- allow(store).to receive(:verify).with(certificate).and_return(true)
- end
-
- it "does not generate any X509 warnings" do
- expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/)
- ssl_check.run
- end
- end
-
- context "when the trusted certificates have invalid X509 properties" do
- before do
- allow(store).to receive(:verify).with(certificate).and_return(false)
- allow(store).to receive(:error_string).and_return("unable to get local issuer certificate")
- end
-
- it "generates a warning message with invalid certificate file names" do
- expect(ssl_check.ui).to receive(:warn).with(/#{trusted_cert_file}: unable to get local issuer certificate/)
- ssl_check.run
- end
- end
- end
-
- describe "verifying the remote certificate" do
- let(:name_args) { %w{https://foo.example.com:8443} }
-
- let(:tcp_socket) { double(TCPSocket) }
- let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
-
- before do
- expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket)
- end
-
- def run
- ssl_check.run
- rescue Exception
- # puts "OUT: #{stdout_io.string}"
- # puts "ERR: #{stderr_io.string}"
- raise
- end
-
- context "when the remote host's certificate is valid" do
-
- before do
- expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn)
- expect(ssl_socket).to receive(:connect) # no error
- expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error
- expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
- end
-
- it "prints a success message" do
- ssl_check.run
- expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'")
- end
- end
-
- describe "and the certificate is not valid" do
-
- let(:tcp_socket_for_debug) { double(TCPSocket) }
- let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) }
-
- let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
- let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
-
- before do
- @old_signal = trap(:INT, "DEFAULT")
-
- expect(ssl_check).to receive(:proxified_socket)
- .with("foo.example.com", 8443)
- .and_return(tcp_socket_for_debug)
- expect(OpenSSL::SSL::SSLSocket).to receive(:new)
- .with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context)
- .and_return(ssl_socket_for_debug)
- end
-
- after do
- trap(:INT, @old_signal)
- end
-
- context "when the certificate's CN does not match the hostname" do
- before do
- expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
- expect(ssl_socket).to receive(:connect) # no error
- expect(ssl_socket).to receive(:post_connection_check)
- .with("foo.example.com")
- .and_raise(OpenSSL::SSL::SSLError)
- expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
- expect(ssl_socket_for_debug).to receive(:connect)
- expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
- end
-
- it "shows the CN used by the certificate and prints an error" do
- expect { run }.to raise_error(SystemExit)
- expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname")
- expect(stderr).to include("You are attempting to connect to: 'foo.example.com'")
- expect(stderr).to include("The server's certificate belongs to 'example.local'")
- end
-
- end
-
- context "when the cert is not signed by any trusted authority" do
- before do
- expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
- expect(ssl_socket).to receive(:connect)
- .and_raise(OpenSSL::SSL::SSLError)
- expect(ssl_socket).to receive(:hostname=)
- .with("foo.example.com") # no error
- expect(ssl_socket_for_debug).to receive(:connect)
- expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
- end
-
- it "shows the CN used by the certificate and prints an error" do
- expect { run }.to raise_error(SystemExit)
- expect(stderr).to include("The SSL certificate of foo.example.com could not be verified")
- end
-
- end
- end
-
- end
-
-end
diff --git a/spec/unit/knife/ssl_fetch_spec.rb b/spec/unit/knife/ssl_fetch_spec.rb
deleted file mode 100644
index 2184994dc0..0000000000
--- a/spec/unit/knife/ssl_fetch_spec.rb
+++ /dev/null
@@ -1,222 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/ssl_fetch"
-
-describe Chef::Knife::SslFetch do
-
- let(:name_args) { [] }
- let(:stdout_io) { StringIO.new }
- let(:stderr_io) { StringIO.new }
-
- def stderr
- stderr_io.string
- end
-
- def stdout
- stdout_io.string
- end
-
- subject(:ssl_fetch) do
- s = Chef::Knife::SslFetch.new
- s.name_args = name_args
- allow(s.ui).to receive(:stdout).and_return(stdout_io)
- allow(s.ui).to receive(:stderr).and_return(stderr_io)
- s
- end
-
- context "when no arguments are given" do
-
- before do
- Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
- end
-
- it "uses the chef_server_url as the host to fetch" do
- expect(ssl_fetch.host).to eq("example.com")
- expect(ssl_fetch.port).to eq(8443)
- end
- end
-
- context "when a specific URI is given" do
- let(:name_args) { %w{https://example.test:10443/foo} }
-
- it "fetches the SSL configuration against the given host" do
- expect(ssl_fetch.host).to eq("example.test")
- expect(ssl_fetch.port).to eq(10443)
- end
- end
-
- context "when an invalid URI is given" do
-
- let(:name_args) { %w{foo.test} }
-
- it "prints an error and exits" do
- expect { ssl_fetch.run }.to raise_error(SystemExit)
- expected_stdout = <<~E
- USAGE: knife ssl fetch [URL] (options)
- E
- expected_stderr = <<~E
- ERROR: Given URI: `foo.test' is invalid
- E
- expect(stdout_io.string).to eq(expected_stdout)
- expect(stderr_io.string).to eq(expected_stderr)
- end
-
- context "and its malformed enough to make URI.parse barf" do
-
- let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
-
- it "prints an error and exits" do
- expect { ssl_fetch.run }.to raise_error(SystemExit)
- expected_stdout = <<~E
- USAGE: knife ssl fetch [URL] (options)
- E
- expected_stderr = <<~E
- ERROR: Given URI: `#{name_args[0]}' is invalid
- E
- expect(stdout_io.string).to eq(expected_stdout)
- expect(stderr_io.string).to eq(expected_stderr)
- end
- end
- end
-
- describe "normalizing CNs for use as paths" do
-
- it "normalizes '*' to 'wildcard'" do
- expect(ssl_fetch.normalize_cn("*.example.com")).to eq("wildcard_example_com")
- end
-
- it "normalizes non-alnum and hyphen characters to underscores" do
- expect(ssl_fetch.normalize_cn("Billy-Bob's Super Awesome CA!")).to eq("Billy-Bob_s_Super_Awesome_CA_")
- end
-
- end
-
- describe "#cn_of" do
- let(:certificate) { double("Certificate", subject: subject) }
-
- describe "when the certificate has a common name" do
- let(:subject) { [["CN", "common name"]] }
- it "returns the common name" do
- expect(ssl_fetch.cn_of(certificate)).to eq("common name")
- end
- end
-
- describe "when the certificate does not have a common name" do
- let(:subject) { [] }
- it "returns nil" do
- expect(ssl_fetch.cn_of(certificate)).to eq(nil)
- end
- end
- end
-
- describe "fetching the remote cert chain" do
-
- let(:name_args) { %w{https://foo.example.com:8443} }
-
- let(:tcp_socket) { double(TCPSocket) }
- let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
-
- let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
- let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
-
- let(:trusted_certs_dir) { Dir.mktmpdir }
-
- def run
- ssl_fetch.run
- rescue Exception
- puts "OUT: #{stdout_io.string}"
- puts "ERR: #{stderr_io.string}"
- raise
- end
-
- before do
- Chef::Config.trusted_certs_dir = trusted_certs_dir
- end
-
- after do
- FileUtils.rm_rf(trusted_certs_dir)
- end
-
- context "when the TLS connection is successful" do
-
- before do
- expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
- expect(ssl_socket).to receive(:connect)
- expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt])
- end
-
- it "fetches the cert chain and writes the certs to the trusted_certs_dir" do
- run
- stored_cert_path = File.join(trusted_certs_dir, "example_local.crt")
- expect(File).to exist(stored_cert_path)
- expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path))
- end
-
- end
-
- context "when connecting to a non-SSL service (like HTTP)" do
-
- let(:name_args) { %w{http://foo.example.com} }
-
- let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") }
-
- before do
- expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket)
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
- expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error)
-
- expect(ssl_fetch).to receive(:exit).with(1)
- end
-
- it "tells the user their URL is for a non-ssl service" do
- expected_error_text = <<~ERROR_TEXT
- ERROR: The service at the given URI (http://foo.example.com) does not accept SSL connections
- ERROR: Perhaps you meant to connect to 'https://foo.example.com'?
- ERROR_TEXT
-
- run
- expect(stderr).to include(expected_error_text)
- end
-
- end
-
- describe "when the certificate does not have a CN" do
- let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example_no_cn.crt") }
- let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
-
- before do
- expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket)
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket)
- expect(ssl_socket).to receive(:connect)
- expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt])
- expect(Time).to receive(:new).and_return(1)
- end
-
- it "fetches the certificate and writes it to a file in the trusted_certs_dir" do
- run
- stored_cert_path = File.join(trusted_certs_dir, "foo.example.com_1.crt")
- expect(File).to exist(stored_cert_path)
- expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path))
- end
- end
-
- end
-end
diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb
deleted file mode 100644
index 838e4c9600..0000000000
--- a/spec/unit/knife/status_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# Author:: Sahil Muthoo (<sahil.muthoo@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::Status do
- before(:each) do
- node = Chef::Node.new.tap do |n|
- n.automatic_attrs["fqdn"] = "foobar"
- n.automatic_attrs["ohai_time"] = 1343845969
- n.automatic_attrs["platform"] = "mac_os_x"
- n.automatic_attrs["platform_version"] = "10.12.5"
- end
- allow(Time).to receive(:now).and_return(Time.at(1428573420))
- @query = double("Chef::Search::Query")
- allow(@query).to receive(:search).and_yield(node)
- allow(Chef::Search::Query).to receive(:new).and_return(@query)
- @knife = Chef::Knife::Status.new
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
- let(:opts) do
- { filter_result:
- { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
- cloud: ["cloud"], run_list: ["run_list"], platform: ["platform"],
- platform_version: ["platform_version"], chef_environment: ["chef_environment"] } }
- end
-
- it "should default to searching for everything" do
- expect(@query).to receive(:search).with(:node, "*:*", opts)
- @knife.run
- end
-
- it "should filter by nodes older than some mins" do
- @knife.config[:hide_by_mins] = 59
- expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts)
- @knife.run
- end
-
- it "should filter by environment" do
- @knife.config[:environment] = "production"
- expect(@query).to receive(:search).with(:node, "chef_environment:production", opts)
- @knife.run
- end
-
- it "should filter by environment and nodes older than some mins" do
- @knife.config[:environment] = "production"
- @knife.config[:hide_by_mins] = 59
- expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
- @knife.run
- end
-
- it "should not use partial search with long output" do
- @knife.config[:long_output] = true
- expect(@query).to receive(:search).with(:node, "*:*", {})
- @knife.run
- end
-
- context "with a custom query" do
- before :each do
- @knife.instance_variable_set(:@name_args, ["name:my_custom_name"])
- end
-
- it "should allow a custom query to be specified" do
- expect(@query).to receive(:search).with(:node, "name:my_custom_name", opts)
- @knife.run
- end
-
- it "should filter by nodes older than some mins with nodename specified" do
- @knife.config[:hide_by_mins] = 59
- expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts)
- @knife.run
- end
-
- it "should filter by environment with nodename specified" do
- @knife.config[:environment] = "production"
- expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts)
- @knife.run
- end
-
- it "should filter by environment and nodes older than some mins with nodename specified" do
- @knife.config[:environment] = "production"
- @knife.config[:hide_by_mins] = 59
- expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts)
- @knife.run
- end
- end
-
- it "should not colorize output unless it's writing to a tty" do
- @knife.run
- expect(@stdout.string.match(/foobar/)).not_to be_nil
- expect(@stdout.string.match(/\e.*ago/)).to be_nil
- end
- end
-end
diff --git a/spec/unit/knife/supermarket_download_spec.rb b/spec/unit/knife/supermarket_download_spec.rb
deleted file mode 100644
index 5d15e74966..0000000000
--- a/spec/unit/knife/supermarket_download_spec.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
-# Copyright:: Copyright 2012-2016, Thomas Bishop
-# Copyright:: Copyright (c) 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 "chef/knife/supermarket_download"
-require "spec_helper"
-
-describe Chef::Knife::SupermarketDownload do
-
- describe "run" do
- before do
- @knife = Chef::Knife::SupermarketDownload.new
- @knife.name_args = ["apache2"]
- @noauth_rest = double("no auth rest")
- @stderr = StringIO.new
- @cookbook_api_url = "https://supermarket.chef.io/api/v1/cookbooks"
- @version = "1.0.2"
- @version_us = @version.tr ".", "_"
- @current_data = { "deprecated" => false,
- "latest_version" => "#{@cookbook_api_url}/apache2/versions/#{@version_us}",
- "replacement" => "other_apache2" }
-
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest)
- expect(@noauth_rest).to receive(:get)
- .with("#{@cookbook_api_url}/apache2")
- .and_return(@current_data)
- @knife.configure_chef
- end
-
- context "when the cookbook is deprecated and not forced" do
- before do
- @current_data["deprecated"] = true
- end
-
- it "should warn with info about the replacement" do
- expect(@knife.ui).to receive(:warn)
- .with(/.+deprecated.+replaced by other_apache2.+/i)
- expect(@knife.ui).to receive(:warn)
- .with(/use --force.+download.+/i)
- @knife.run
- end
- end
-
- context "when" do
- before do
- @cookbook_data = { "version" => @version,
- "file" => "http://example.com/apache2_#{@version_us}.tgz" }
- @temp_file = double( path: "/tmp/apache2_#{@version_us}.tgz" )
- @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
- end
-
- context "downloading the latest version" do
- before do
- expect(@noauth_rest).to receive(:get)
- .with(@current_data["latest_version"])
- .and_return(@cookbook_data)
- expect(@noauth_rest).to receive(:streaming_request)
- .with(@cookbook_data["file"])
- .and_return(@temp_file)
- end
-
- context "and it is deprecated and with --force" do
- before do
- @current_data["deprecated"] = true
- @knife.config[:force] = true
- end
-
- it "should download the latest version" do
- expect(@knife.ui).to receive(:warn)
- .with(/.+deprecated.+replaced by other_apache2.+/i)
- expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
- @knife.run
- expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
- expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
- end
-
- end
-
- it "should download the latest version" do
- expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
- @knife.run
- expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
- expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
- end
-
- context "with -f or --file" do
- before do
- @file = "/opt/chef/cookbooks/apache2.tar.gz"
- @knife.config[:file] = @file
- expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
- end
-
- it "should download the cookbook to the desired file" do
- @knife.run
- expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
- expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
- end
- end
-
- it "should provide an accessor to the version" do
- allow(FileUtils).to receive(:cp).and_return(true)
- expect(@knife.version).to eq(@version)
- @knife.run
- end
- end
-
- context "downloading a cookbook of a specific version" do
- before do
- @version = "1.0.1"
- @version_us = @version.tr ".", "_"
- @cookbook_data = { "version" => @version,
- "file" => "http://example.com/apache2_#{@version_us}.tgz" }
- @temp_file = double(path: "/tmp/apache2_#{@version_us}.tgz")
- @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
- @knife.name_args << @version
- end
-
- it "should download the desired version" do
- expect(@noauth_rest).to receive(:get)
- .with("#{@cookbook_api_url}/apache2/versions/#{@version_us}")
- .and_return(@cookbook_data)
- expect(@noauth_rest).to receive(:streaming_request)
- .with(@cookbook_data["file"])
- .and_return(@temp_file)
- expect(FileUtils).to receive(:cp).with(@temp_file.path, @file)
- @knife.run
- expect(@stderr.string).to match(/downloading apache2.+version.+#{Regexp.escape(@version)}/i)
- expect(@stderr.string).to match(/cookbook save.+#{Regexp.escape(@file)}/i)
- end
- end
-
- end
-
- end
-
-end
diff --git a/spec/unit/knife/supermarket_install_spec.rb b/spec/unit/knife/supermarket_install_spec.rb
deleted file mode 100644
index 03cc5d1992..0000000000
--- a/spec/unit/knife/supermarket_install_spec.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/supermarket_install"
-
-describe Chef::Knife::SupermarketInstall do
- let(:knife) { Chef::Knife::SupermarketInstall.new }
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
- let(:downloader) { {} }
- let(:archive) { double(Mixlib::Archive, extract: true) }
- let(:repo) do
- double(sanity_check: true, reset_to_default_state: true,
- prepare_to_import: true, finalize_updates_to: true,
- merge_updates_from: true)
- end
- let(:install_path) do
- if ChefUtils.windows?
- "C:/tmp/chef"
- else
- "/var/tmp/chef"
- end
- end
-
- before(:each) do
- require "chef/knife/core/cookbook_scm_repo"
-
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- knife.config = {}
- knife.config[:cookbook_path] = [ install_path ]
-
- allow(knife).to receive(:stderr).and_return(stderr)
- allow(knife).to receive(:stdout).and_return(stdout)
-
- # Assume all external commands would have succeed. :(
- allow(File).to receive(:unlink)
- allow(File).to receive(:rmtree)
- allow(knife).to receive(:shell_out!).and_return(true)
- allow(Mixlib::Archive).to receive(:new).and_return(archive)
-
- # SupermarketDownload Setup
- allow(knife).to receive(:download_cookbook_to).and_return(downloader)
- allow(downloader).to receive(:version) do
- if knife.name_args.size == 2
- knife.name_args[1]
- else
- "0.3.0"
- end
- end
-
- # Stubs for CookbookSCMRepo
- allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo)
- end
-
- describe "run" do
- it "raises an error if a cookbook name is not provided" do
- knife.name_args = []
- expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.")
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "raises an error if more than two arguments are given" do
- knife.name_args = %w{foo bar baz}
- expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "raises an error if the second argument is not a version" do
- knife.name_args = %w{getting-started 1pass}
- expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "raises an error if the second argument is a four-digit version" do
- knife.name_args = ["getting-started", "0.0.0.1"]
- expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "raises an error if the second argument is a one-digit version" do
- knife.name_args = %w{getting-started 1}
- expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.")
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "installs the specified version if second argument is a three-digit version" do
- knife.name_args = ["getting-started", "0.1.0"]
- knife.config[:no_deps] = true
- upstream_file = File.join(install_path, "getting-started.tar.gz")
- expect(knife).to receive(:download_cookbook_to).with(upstream_file)
- expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0")
- expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
- expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0")
- knife.run
- end
-
- it "installs the specified version if second argument is a two-digit version" do
- knife.name_args = ["getting-started", "0.1"]
- knife.config[:no_deps] = true
- upstream_file = File.join(install_path, "getting-started.tar.gz")
- expect(knife).to receive(:download_cookbook_to).with(upstream_file)
- expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1")
- expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
- expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1")
- knife.run
- end
-
- it "installs the latest version if only a cookbook name is given" do
- knife.name_args = ["getting-started"]
- knife.config[:no_deps] = true
- upstream_file = File.join(install_path, "getting-started.tar.gz")
- expect(knife).to receive(:download_cookbook_to).with(upstream_file)
- expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
- expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
- expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
- knife.run
- end
-
- it "does not create/reset git branches if use_current_branch is set" do
- knife.name_args = ["getting-started"]
- knife.config[:use_current_branch] = true
- knife.config[:no_deps] = true
- upstream_file = File.join(install_path, "getting-started.tar.gz")
- expect(repo).not_to receive(:prepare_to_import)
- expect(repo).not_to receive(:reset_to_default_state)
- knife.run
- end
-
- it "does not raise an error if cookbook_path is a string" do
- knife.config[:cookbook_path] = install_path
- knife.config[:no_deps] = true
- knife.name_args = ["getting-started"]
- upstream_file = File.join(install_path, "getting-started.tar.gz")
- expect(knife).to receive(:download_cookbook_to).with(upstream_file)
- expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0")
- expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started"))
- expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0")
- expect { knife.run }.not_to raise_error
- end
- end # end of run
-
- let(:metadata) { Chef::Cookbook::Metadata.new }
- let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") }
- let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") }
-
- describe "preferred_metadata" do
- before do
- allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata)
- allow(File).to receive(:exist?).and_return(false)
- knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen")
- knife.instance_variable_set(:@install_path, install_path)
- end
-
- it "returns a populated Metadata object if metadata.rb exists" do
- allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
- expect(metadata).to receive(:from_file).with(rb_metadata_path)
- knife.preferred_metadata
- end
-
- it "returns a populated Metadata object if metadata.json exists" do
- allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
- # expect(IO).to receive(:read).with(json_metadata_path)
- allow(IO).to receive(:read)
- expect(metadata).to receive(:from_json)
- knife.preferred_metadata
- end
-
- it "prefers metadata.rb over metadata.json" do
- allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true)
- allow(File).to receive(:exist?).with(json_metadata_path).and_return(true)
- allow(IO).to receive(:read)
- expect(metadata).to receive(:from_file).with(rb_metadata_path)
- expect(metadata).not_to receive(:from_json)
- knife.preferred_metadata
- end
-
- it "rasies an error if it finds no metadata file" do
- expect { knife.preferred_metadata }.to raise_error { |error|
- expect(error).to be_a(Chef::Exceptions::MetadataNotFound)
- expect(error.cookbook_name).to eq("post-punk-kitchen")
- expect(error.install_path).to eq(install_path)
- }
- end
-
- end
-end
diff --git a/spec/unit/knife/supermarket_list_spec.rb b/spec/unit/knife/supermarket_list_spec.rb
deleted file mode 100644
index a1acccaaaa..0000000000
--- a/spec/unit/knife/supermarket_list_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>)
-# Copyright:: Copyright (c) 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 "chef/knife/supermarket_list"
-require "spec_helper"
-
-describe Chef::Knife::SupermarketList do
- let(:knife) { described_class.new }
- let(:noauth_rest) { double("no auth rest") }
- let(:stdout) { StringIO.new }
- let(:cookbooks_data) {
- [
- { "cookbook_name" => "1password", "cookbook_maintainer" => "jtimberman", "cookbook_description" => "Installs 1password", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/1password" },
- { "cookbook_name" => "301", "cookbook_maintainer" => "markhuge", "cookbook_description" => "Installs/Configures 301", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/301" },
- { "cookbook_name" => "3cx", "cookbook_maintainer" => "obay", "cookbook_description" => "Installs/Configures 3cx", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/3cx" },
- { "cookbook_name" => "7dtd", "cookbook_maintainer" => "gregf", "cookbook_description" => "Installs/Configures the 7 Days To Die server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7dtd" },
- { "cookbook_name" => "7-zip", "cookbook_maintainer" => "sneal", "cookbook_description" => "Installs/Configures the 7-zip file archiver", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/7-zip" },
- ]
- }
-
- let(:response_text) {
- {
- "start" => 0,
- "total" => 5,
- "items" => cookbooks_data,
- }
- }
-
- describe "run" do
- before do
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(knife).to receive(:noauth_rest).and_return(noauth_rest)
- expect(noauth_rest).to receive(:get).and_return(response_text)
- knife.configure_chef
- end
-
- it "should display all supermarket cookbooks" do
- knife.run
- cookbooks_data.each do |item|
- expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
- end
- end
-
- describe "with -w or --with-uri" do
- it "should display the cookbook uris" do
- knife.config[:with_uri] = true
- knife.run
- cookbooks_data.each do |item|
- expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
- expect(stdout.string).to match(/#{item["cookbook"]}\s/)
- end
- end
- end
- end
-end
diff --git a/spec/unit/knife/supermarket_search_spec.rb b/spec/unit/knife/supermarket_search_spec.rb
deleted file mode 100644
index cba2f615aa..0000000000
--- a/spec/unit/knife/supermarket_search_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-#
-# Author:: Vivek Singh (<vivek.singh@msystechnologies.com>)
-# Copyright:: Copyright (c) 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 "chef/knife/supermarket_search"
-require "spec_helper"
-
-describe Chef::Knife::SupermarketSearch do
- let(:knife) { described_class.new }
- let(:noauth_rest) { double("no auth rest") }
- let(:stdout) { StringIO.new }
- let(:cookbooks_data) {
- [
- { "cookbook_name" => "mysql", "cookbook_maintainer" => "sous-chefs", "cookbook_description" => "Provides mysql_service, mysql_config, and mysql_client resources", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql" },
- { "cookbook_name" => "mw_mysql", "cookbook_maintainer" => "car", "cookbook_description" => "Installs/Configures mw_mysql", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mw_mysql" },
- { "cookbook_name" => "L7-mysql", "cookbook_maintainer" => "szelcsanyi", "cookbook_description" => "Installs/Configures MySQL server", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/l7-mysql" },
- { "cookbook_name" => "mysql-sys", "cookbook_maintainer" => "ovaistariq", "cookbook_description" => "Installs the mysql-sys tool. Description of the tool is available here https://github.com/MarkLeith/mysql-sys", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/mysql-sys" },
- { "cookbook_name" => "cg_mysql", "cookbook_maintainer" => "phai", "cookbook_description" => "Installs/Configures mysql with master and slave", "cookbook" => "https://supermarket.chef.io/api/v1/cookbooks/cg_mysql" },
- ]
- }
-
- let(:response_text) {
- {
- "start" => 0,
- "total" => 5,
- "items" => cookbooks_data,
- }
- }
-
- let(:empty_response_text) {
- {
- "start" => 0,
- "total" => 0,
- "items" => [],
- }
- }
-
- describe "run" do
- before do
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(knife).to receive(:noauth_rest).and_return(noauth_rest)
- knife.configure_chef
- end
-
- context "when name_args is present" do
- before do
- expect(noauth_rest).to receive(:get).and_return(response_text)
- end
-
- it "should display cookbooks with given name value" do
- knife.name_args = ["mysql"]
- knife.run
- cookbooks_data.each do |item|
- expect(stdout.string).to match(/#{item["cookbook_name"]}\s/)
- end
- end
- end
-
- context "when name_args is empty string" do
- before do
- expect(noauth_rest).to receive(:get).and_return(empty_response_text)
- end
-
- it "display nothing with name arg empty string" do
- knife.name_args = [""]
- knife.run
- expect(stdout.string).to eq("\n")
- end
- end
- end
-end
diff --git a/spec/unit/knife/supermarket_share_spec.rb b/spec/unit/knife/supermarket_share_spec.rb
deleted file mode 100644
index f6c44f4cd8..0000000000
--- a/spec/unit/knife/supermarket_share_spec.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/supermarket_share"
-require "chef/cookbook_uploader"
-require "chef/cookbook_site_streaming_uploader"
-
-describe Chef::Knife::SupermarketShare do
-
- before(:each) do
- @knife = Chef::Knife::SupermarketShare.new
- # Merge default settings in.
- @knife.merge_configs
- @knife.name_args = %w{cookbook_name AwesomeSausage}
-
- @cookbook = Chef::CookbookVersion.new("cookbook_name")
-
- @cookbook_loader = double("Chef::CookbookLoader")
- allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(true)
- allow(@cookbook_loader).to receive(:[]).and_return(@cookbook)
- allow(Chef::CookbookLoader).to receive(:new).and_return(@cookbook_loader)
-
- @noauth_rest = double(Chef::ServerAPI)
- allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest)
-
- @cookbook_uploader = Chef::CookbookUploader.new("herpderp", rest: "norest")
- allow(Chef::CookbookUploader).to receive(:new).and_return(@cookbook_uploader)
- allow(@cookbook_uploader).to receive(:validate_cookbooks).and_return(true)
- allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return(Dir.mktmpdir)
-
- allow(@knife).to receive(:shell_out!).and_return(true)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
-
- before(:each) do
- allow(@knife).to receive(:do_upload).and_return(true)
- @category_response = {
- "name" => "cookbook_name",
- "category" => "Testing Category",
- }
- @bad_category_response = {
- "error_code" => "NOT_FOUND",
- "error_messages" => [
- "Resource does not exist.",
- ],
- }
- end
-
- it "should set true to config[:dry_run] as default" do
- expect(@knife.config[:dry_run]).to be_falsey
- end
-
- it "should should print usage and exit when given no arguments" do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should not fail when given only 1 argument and can determine category" do
- @knife.name_args = ["cookbook_name"]
- expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response)
- expect(@knife).to receive(:do_upload)
- @knife.run
- end
-
- it "should use a default category when given only 1 argument and cannot determine category" do
- @knife.name_args = ["cookbook_name"]
- expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Net::HTTPClientException.new("404 Not Found", OpenStruct.new(code: "404")) }
- expect(@knife).to receive(:do_upload)
- expect { @knife.run }.to_not raise_error
- end
-
- it "should print error and exit when given only 1 argument and Chef::ServerAPI throws an exception" do
- @knife.name_args = ["cookbook_name"]
- expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" }
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should check if the cookbook exists" do
- expect(@cookbook_loader).to receive(:cookbook_exists?)
- @knife.run
- end
-
- it "should exit and log to error if the cookbook doesn't exist" do
- allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(false)
- expect(@knife.ui).to receive(:error)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- if File.exist?("/usr/bin/gnutar") || File.exist?("/bin/gnutar")
- it "should use gnutar to make a tarball of the cookbook" do
- expect(@knife).to receive(:shell_out!) do |args|
- expect(args.to_s).to match(/gnutar -czf/)
- end
- @knife.run
- end
- else
- it "should make a tarball of the cookbook" do
- expect(@knife).to receive(:shell_out!) do |args|
- expect(args.to_s).to match(/tar -czf/)
- end
- @knife.run
- end
- end
-
- it "should exit and log to error when the tarball creation fails" do
- allow(@knife).to receive(:shell_out!).and_raise(Chef::Exceptions::Exec)
- expect(@knife.ui).to receive(:error)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should upload the cookbook and clean up the tarball" do
- expect(@knife).to receive(:do_upload)
- expect(FileUtils).to receive(:rm_rf)
- @knife.run
- end
-
- context "when the --dry-run flag is specified" do
- before do
- allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return("/var/tmp/dummy")
- @knife.config = { dry_run: true }
- allow(@knife).to receive_message_chain(:shell_out!, :stdout).and_return("file")
- end
-
- it "should list files in the tarball" do
- allow(@knife).to receive(:tar_cmd).and_return("footar")
- expect(@knife).to receive(:shell_out!).with("footar -czf #{@cookbook.name}.tgz #{@cookbook.name}", { cwd: "/var/tmp/dummy" })
- expect(@knife).to receive(:shell_out!).with("footar -tzf #{@cookbook.name}.tgz", { cwd: "/var/tmp/dummy" })
- @knife.run
- end
-
- it "does not upload the cookbook" do
- allow(@knife).to receive(:shell_out!).and_return(true)
- expect(@knife).not_to receive(:do_upload)
- @knife.run
- end
- end
- end
-
- describe "do_upload" do
-
- before(:each) do
- @upload_response = double("Net::HTTPResponse")
- allow(Chef::CookbookSiteStreamingUploader).to receive(:post).and_return(@upload_response)
-
- @stdout = StringIO.new
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- allow(File).to receive(:open).and_return(true)
- end
-
- it 'should post the cookbook to "https://supermarket.chef.io"' do
- response_text = Chef::JSONCompat.to_json({ uri: "https://supermarket.chef.io/cookbooks/cookbook_name" })
- allow(@upload_response).to receive(:body).and_return(response_text)
- allow(@upload_response).to receive(:code).and_return(201)
- expect(Chef::CookbookSiteStreamingUploader).to receive(:post).with(/supermarket\.chef\.io/, anything, anything, anything)
- @knife.run
- end
-
- it "should alert the user when a version already exists" do
- response_text = Chef::JSONCompat.to_json({ error_messages: ["Version already exists"] })
- allow(@upload_response).to receive(:body).and_return(response_text)
- allow(@upload_response).to receive(:code).and_return(409)
- expect { @knife.run }.to raise_error(SystemExit)
- expect(@stderr.string).to match(/ERROR(.+)cookbook already exists/)
- end
-
- it "should pass any errors on to the user" do
- response_text = Chef::JSONCompat.to_json({ error_messages: ["You're holding it wrong"] })
- allow(@upload_response).to receive(:body).and_return(response_text)
- allow(@upload_response).to receive(:code).and_return(403)
- expect { @knife.run }.to raise_error(SystemExit)
- expect(@stderr.string).to match("ERROR(.*)You're holding it wrong")
- end
-
- it "should print the body if no errors are exposed on failure" do
- response_text = Chef::JSONCompat.to_json({ system_error: "Your call was dropped", reason: "There's a map for that" })
- allow(@upload_response).to receive(:body).and_return(response_text)
- allow(@upload_response).to receive(:code).and_return(500)
- expect(@knife.ui).to receive(:error).with(/#{Regexp.escape(response_text)}/) # .ordered
- expect(@knife.ui).to receive(:error).with(/Unknown error/) # .ordered
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- end
-
-end
diff --git a/spec/unit/knife/supermarket_unshare_spec.rb b/spec/unit/knife/supermarket_unshare_spec.rb
deleted file mode 100644
index 8ae4d03cb5..0000000000
--- a/spec/unit/knife/supermarket_unshare_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-#
-# Author:: Stephen Delano (<stephen@chef.io>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "chef/knife/supermarket_unshare"
-
-describe Chef::Knife::SupermarketUnshare do
-
- before(:each) do
- @knife = Chef::Knife::SupermarketUnshare.new
- @knife.name_args = ["cookbook_name"]
- allow(@knife).to receive(:confirm).and_return(true)
-
- @rest = double("Chef::ServerAPI")
- allow(@rest).to receive(:delete).and_return(true)
- allow(@knife).to receive(:rest).and_return(@rest)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- end
-
- describe "run" do
-
- describe "with no cookbook argument" do
- it "should print the usage and exit" do
- @knife.name_args = []
- expect(@knife.ui).to receive(:fatal)
- expect(@knife).to receive(:show_usage)
- expect { @knife.run }.to raise_error(SystemExit)
- end
- end
-
- it "should confirm you want to unshare the cookbook" do
- expect(@knife).to receive(:confirm)
- @knife.run
- end
-
- it "should send a delete request to the cookbook site" do
- expect(@rest).to receive(:delete)
- @knife.run
- end
-
- it "should log an error and exit when forbidden" do
- exception = double('403 "Forbidden"', code: "403")
- allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('403 "Forbidden"', exception))
- expect(@knife.ui).to receive(:error)
- expect { @knife.run }.to raise_error(SystemExit)
- end
-
- it "should re-raise any non-forbidden errors on delete" do
- exception = double('500 "Application Error"', code: "500")
- allow(@rest).to receive(:delete).and_raise(Net::HTTPClientException.new('500 "Application Error"', exception))
- expect { @knife.run }.to raise_error(Net::HTTPClientException)
- end
-
- it "should log a success message" do
- expect(@knife.ui).to receive(:info)
- @knife.run
- end
-
- end
-
-end
diff --git a/spec/unit/knife/tag_create_spec.rb b/spec/unit/knife/tag_create_spec.rb
deleted file mode 100644
index a1a4923871..0000000000
--- a/spec/unit/knife/tag_create_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require "spec_helper"
-
-describe Chef::Knife::TagCreate do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::TagCreate.new
- @knife.name_args = [ Chef::Config[:node_name], "happytag" ]
-
- @node = Chef::Node.new
- allow(@node).to receive :save
- allow(Chef::Node).to receive(:load).and_return @node
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- end
-
- describe "run" do
- it "can create tags on a node" do
- @knife.run
- expect(@node.tags).to eq(["happytag"])
- expect(@stderr.string).to match(/created tags happytag.+node webmonkey.example.com/i)
- end
- end
-end
diff --git a/spec/unit/knife/tag_delete_spec.rb b/spec/unit/knife/tag_delete_spec.rb
deleted file mode 100644
index 4201196de0..0000000000
--- a/spec/unit/knife/tag_delete_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require "spec_helper"
-
-describe Chef::Knife::TagDelete do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::TagDelete.new
- @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
-
- @node = Chef::Node.new
- allow(@node).to receive :save
- @node.tags << "sadtag" << "happytag"
- allow(Chef::Node).to receive(:load).and_return @node
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- end
-
- describe "run" do
- it "can delete tags on a node" do
- expect(@node.tags).to eq(%w{sadtag happytag})
- @knife.run
- expect(@node.tags).to eq(["happytag"])
- expect(@stderr.string).to match(/deleted.+sadtag/i)
- end
- end
-end
diff --git a/spec/unit/knife/tag_list_spec.rb b/spec/unit/knife/tag_list_spec.rb
deleted file mode 100644
index dceec9a5ea..0000000000
--- a/spec/unit/knife/tag_list_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require "spec_helper"
-
-describe Chef::Knife::TagList do
- before(:each) do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::TagList.new
- @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
-
- @node = Chef::Node.new
- allow(@node).to receive :save
- @node.tags << "sadtag" << "happytag"
- allow(Chef::Node).to receive(:load).and_return @node
- end
-
- describe "run" do
- it "can list tags on a node" do
- expected = %w{sadtag happytag}
- expect(@node.tags).to eq(expected)
- expect(@knife).to receive(:output).with(expected)
- @knife.run
- end
- end
-end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
deleted file mode 100644
index be3d2fd99c..0000000000
--- a/spec/unit/knife/user_create_spec.rb
+++ /dev/null
@@ -1,184 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Author:: Tyler Cloke (<tyler@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-Chef::Knife::UserCreate.load_deps
-
-describe Chef::Knife::UserCreate do
- let(:knife) { Chef::Knife::UserCreate.new }
-
- let(:stderr) do
- StringIO.new
- end
-
- let(:stdout) do
- StringIO.new
- end
-
- before(:each) do
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(knife.ui).to receive(:stderr).and_return(stderr)
- allow(knife.ui).to receive(:warn)
- end
-
- context "when USERNAME isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { [] }
- let(:fieldname) { "username" }
- end
- end
-
- context "when FIRST_NAME isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { %w{some_user some_display_name} }
- let(:fieldname) { "first name" }
- end
- end
-
- context "when LAST_NAME isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { %w{some_user some_display_name some_first_name} }
- let(:fieldname) { "last name" }
- end
- end
-
- context "when EMAIL isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { %w{some_user some_display_name some_first_name some_last_name} }
- let(:fieldname) { "email" }
- end
- end
-
- context "when PASSWORD isn't specified" do
- # from spec/support/shared/unit/knife_shared.rb
- it_should_behave_like "mandatory field missing" do
- let(:name_args) { %w{some_user some_display_name some_first_name some_last_name some_email} }
- let(:fieldname) { "password" }
- end
- end
-
- context "when all mandatory fields are validly specified" do
- before do
- knife.name_args = %w{some_user some_display_name some_first_name some_last_name some_email some_password}
- allow(knife).to receive(:edit_hash).and_return(knife.user.to_hash)
- allow(knife).to receive(:create_user_from_hash).and_return(knife.user)
- end
-
- before(:each) do
- # reset the user field every run
- knife.user_field = nil
- end
-
- it "sets all the mandatory fields" do
- knife.run
- expect(knife.user.username).to eq("some_user")
- expect(knife.user.display_name).to eq("some_display_name")
- expect(knife.user.first_name).to eq("some_first_name")
- expect(knife.user.last_name).to eq("some_last_name")
- expect(knife.user.email).to eq("some_email")
- expect(knife.user.password).to eq("some_password")
- end
-
- context "when user_key and prevent_keygen are passed" do
- before do
- knife.config[:user_key] = "some_key"
- knife.config[:prevent_keygen] = true
- end
- it "prints the usage" do
- expect(knife).to receive(:show_usage)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "prints a relevant error message" do
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/You cannot pass --user-key and --prevent-keygen/)
- end
- end
-
- context "when --prevent-keygen is passed" do
- before do
- knife.config[:prevent_keygen] = true
- end
-
- it "does not set user.create_key" do
- knife.run
- expect(knife.user.create_key).to be_falsey
- end
- end
-
- context "when --prevent-keygen is not passed" do
- it "sets user.create_key to true" do
- knife.run
- expect(knife.user.create_key).to be_truthy
- end
- end
-
- context "when --user-key is passed" do
- before do
- knife.config[:user_key] = "some_key"
- allow(File).to receive(:read).and_return("some_key")
- allow(File).to receive(:expand_path)
- end
-
- it "sets user.public_key" do
- knife.run
- expect(knife.user.public_key).to eq("some_key")
- end
- end
-
- context "when --user-key is not passed" do
- it "does not set user.public_key" do
- knife.run
- expect(knife.user.public_key).to be_nil
- end
- end
-
- context "when a private_key is returned" do
- before do
- allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({ "private_key" => "some_private_key" })))
- end
-
- context "when --file is passed" do
- before do
- knife.config[:file] = "/some/path"
- end
-
- it "creates a new file of the path passed" do
- filehandle = double("filehandle")
- expect(filehandle).to receive(:print).with("some_private_key")
- expect(File).to receive(:open).with("/some/path", "w").and_yield(filehandle)
- knife.run
- end
- end
-
- context "when --file is not passed" do
- it "prints the private key to stdout" do
- expect(knife.ui).to receive(:msg).with("some_private_key")
- knife.run
- end
- end
- end
-
- end # when all mandatory fields are validly specified
-end
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
deleted file mode 100644
index 959d792b9e..0000000000
--- a/spec/unit/knife/user_delete_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UserDelete do
- let(:knife) { Chef::Knife::UserDelete.new }
- let(:user) { double("user_object") }
- let(:stdout) { StringIO.new }
-
- before(:each) do
- Chef::Knife::UserDelete.load_deps
- knife.name_args = [ "my_user" ]
- allow(Chef::UserV1).to receive(:load).and_return(user)
- allow(user).to receive(:username).and_return("my_user")
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- end
-
- it "deletes the user" do
- expect(knife).to receive(:delete_object).with(Chef::UserV1, "my_user")
- knife.run
- end
-
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
-end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
deleted file mode 100644
index 54a44890e0..0000000000
--- a/spec/unit/knife/user_edit_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UserEdit do
- let(:knife) { Chef::Knife::UserEdit.new }
-
- before(:each) do
- @stderr = StringIO.new
- @stdout = StringIO.new
-
- Chef::Knife::UserEdit.load_deps
- allow(knife.ui).to receive(:stderr).and_return(@stderr)
- allow(knife.ui).to receive(:stdout).and_return(@stdout)
- knife.name_args = [ "my_user" ]
- knife.config[:disable_editing] = true
- end
-
- it "loads and edits the user" do
- data = { "username" => "my_user" }
- allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data)
- expect(knife).to receive(:edit_hash).with(data).and_return(data)
- knife.run
- end
-
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
-end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
deleted file mode 100644
index 21c07f3fb1..0000000000
--- a/spec/unit/knife/user_list_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Author:: Steven Danna
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UserList do
- let(:knife) { Chef::Knife::UserList.new }
- let(:stdout) { StringIO.new }
-
- before(:each) do
- Chef::Knife::UserList.load_deps
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- end
-
- it "lists the users" do
- expect(Chef::UserV1).to receive(:list)
- expect(knife).to receive(:format_list_for_display)
- knife.run
- end
-end
diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb
deleted file mode 100644
index 481415e432..0000000000
--- a/spec/unit/knife/user_reregister_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UserReregister do
- let(:knife) { Chef::Knife::UserReregister.new }
- let(:user_mock) { double("user_mock", private_key: "private_key") }
- let(:stdout) { StringIO.new }
-
- before do
- Chef::Knife::UserReregister.load_deps
- knife.name_args = [ "a_user" ]
- allow(Chef::UserV1).to receive(:load).and_return(user_mock)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(user_mock).to receive(:username).and_return("a_user")
- end
-
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
-
- it "reregisters the user and prints the key" do
- expect(user_mock).to receive(:reregister).and_return(user_mock)
- knife.run
- expect(stdout.string).to match( /private_key/ )
- end
-
- it "writes the private key to a file when --file is specified" do
- expect(user_mock).to receive(:reregister).and_return(user_mock)
- knife.config[:file] = "/tmp/a_file"
- filehandle = StringIO.new
- expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
- knife.run
- expect(filehandle.string).to eq("private_key")
- end
-end
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
deleted file mode 100644
index 198b9352f3..0000000000
--- a/spec/unit/knife/user_show_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Author:: Steven Danna (<steve@chef.io>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-
-describe Chef::Knife::UserShow do
- let(:knife) { Chef::Knife::UserShow.new }
- let(:user_mock) { double("user_mock") }
- let(:stdout) { StringIO.new }
-
- before do
- Chef::Knife::UserShow.load_deps
- knife.name_args = [ "my_user" ]
- allow(user_mock).to receive(:username).and_return("my_user")
- allow(knife.ui).to receive(:stderr).and_return(stdout)
- allow(knife.ui).to receive(:stdout).and_return(stdout)
- end
-
- it "loads and displays the user" do
- expect(Chef::UserV1).to receive(:load).with("my_user").and_return(user_mock)
- expect(knife).to receive(:format_for_display).with(user_mock)
- knife.run
- end
-
- it "prints usage and exits when a user name is not provided" do
- knife.name_args = []
- expect(knife).to receive(:show_usage)
- expect(knife.ui).to receive(:fatal)
- expect { knife.run }.to raise_error(SystemExit)
- end
-end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
deleted file mode 100644
index 88f36a3973..0000000000
--- a/spec/unit/knife_spec.rb
+++ /dev/null
@@ -1,634 +0,0 @@
-#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Tim Hinderliter (<tim@chef.io>)
-# Copyright:: Copyright (c) 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.
-#
-
-# Fixtures for subcommand loading live in this namespace
-module KnifeSpecs
-end
-
-require "spec_helper"
-require "uri"
-require "chef/knife/core/gem_glob_loader"
-
-describe Chef::Knife do
-
- let(:stderr) { StringIO.new }
-
- let(:knife) { Chef::Knife.new }
-
- let(:config_location) { File.expand_path("~/.chef/config.rb") }
-
- let(:config_loader) do
- instance_double("WorkstationConfigLoader",
- load: nil, no_config_found?: false,
- config_location: config_location,
- chef_config_dir: "/etc/chef")
- end
-
- before(:each) do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- Chef::Config[:node_name] = "webmonkey.example.com"
-
- allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
- allow(config_loader).to receive(:explicit_config_file=)
- allow(config_loader).to receive(:profile=)
-
- # Prevent gratuitous code reloading:
- allow(Chef::Knife).to receive(:load_commands)
- allow(knife.ui).to receive(:puts)
- allow(knife.ui).to receive(:print)
- allow(Chef::Log).to receive(:init)
- allow(Chef::Log).to receive(:level)
- %i{debug info warn error crit}.each do |level_sym|
- allow(Chef::Log).to receive(level_sym)
- end
- allow(Chef::Knife).to receive(:puts)
- end
-
- after(:each) do
- Chef::Knife.reset_config_loader!
- end
-
- it "does not reset Chef::Config[:verbosity to nil if config[:verbosity] is nil" do
- Chef::Config[:verbosity] = 2
- Chef::Knife.new
- expect(Chef::Config[:verbosity]).to eq(2)
- end
-
- describe "after loading a subcommand" do
- before do
- Chef::Knife.reset_subcommands!
-
- if KnifeSpecs.const_defined?(:TestNameMapping)
- KnifeSpecs.send(:remove_const, :TestNameMapping)
- end
-
- if KnifeSpecs.const_defined?(:TestExplicitCategory)
- KnifeSpecs.send(:remove_const, :TestExplicitCategory)
- end
-
- Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_name_mapping.rb"))
- Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_explicit_category.rb"))
- end
-
- it "has a category based on its name" do
- expect(KnifeSpecs::TestNameMapping.subcommand_category).to eq("test")
- end
-
- it "has an explicitly defined category if set" do
- expect(KnifeSpecs::TestExplicitCategory.subcommand_category).to eq("cookbook site")
- end
-
- it "can reference the subcommand by its snake cased name" do
- expect(Chef::Knife.subcommands["test_name_mapping"]).to equal(KnifeSpecs::TestNameMapping)
- end
-
- it "lists subcommands by category" do
- expect(Chef::Knife.subcommands_by_category["test"]).to include("test_name_mapping")
- end
-
- it "lists subcommands by category when the subcommands have explicit categories" do
- expect(Chef::Knife.subcommands_by_category["cookbook site"]).to include("test_explicit_category")
- end
-
- it "has empty dependency_loader list by default" do
- expect(KnifeSpecs::TestNameMapping.dependency_loaders).to be_empty
- end
- end
-
- describe "after loading all subcommands" do
- before do
- Chef::Knife.reset_subcommands!
- Chef::Knife.load_commands
- end
-
- it "references a subcommand class by its snake cased name" do
- class SuperAwesomeCommand < Chef::Knife
- end
-
- Chef::Knife.load_commands
-
- expect(Chef::Knife.subcommands).to have_key("super_awesome_command")
- expect(Chef::Knife.subcommands["super_awesome_command"]).to eq(SuperAwesomeCommand)
- end
-
- it "records the location of ChefFS-based commands correctly" do
- class AwesomeCheffsCommand < Chef::ChefFS::Knife
- end
-
- Chef::Knife.load_commands
- expect(Chef::Knife.subcommand_files["awesome_cheffs_command"]).to eq([__FILE__])
- end
-
- it "guesses a category from a given ARGV" do
- Chef::Knife.subcommands_by_category["cookbook"] << :cookbook
- Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site
- expect(Chef::Knife.guess_category(%w{cookbook foo bar baz})).to eq("cookbook")
- expect(Chef::Knife.guess_category(%w{cookbook site foo bar baz})).to eq("cookbook site")
- expect(Chef::Knife.guess_category(%w{cookbook site --help})).to eq("cookbook site")
- end
-
- it "finds a subcommand class based on ARGV" do
- Chef::Knife.subcommands["cookbook_site_install"] = :CookbookSiteInstall
- Chef::Knife.subcommands["cookbook"] = :Cookbook
- expect(Chef::Knife.subcommand_class_from(%w{cookbook site install --help foo bar baz})).to eq(:CookbookSiteInstall)
- end
-
- it "special case sets the subcommand_loader to GemGlobLoader when running rehash" do
- Chef::Knife.subcommands["rehash"] = :Rehash
- expect(Chef::Knife.subcommand_class_from(%w{rehash })).to eq(:Rehash)
- expect(Chef::Knife.subcommand_loader).to be_a(Chef::Knife::SubcommandLoader::GemGlobLoader)
- end
-
- end
-
- describe "the headers include X-Remote-Request-Id" do
-
- let(:headers) do
- { "Accept" => "application/json",
- "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
- "X-Chef-Version" => Chef::VERSION,
- "Host" => "api.opscode.piab",
- "X-REMOTE-REQUEST-ID" => request_id,
- }
- end
-
- let(:request_id) { "1234" }
-
- let(:request_mock) { {} }
-
- let(:rest) do
- allow(Net::HTTP).to receive(:new).and_return(http_client)
- allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id)
- allow(Chef::Config).to receive(:chef_server_url).and_return("https://api.opscode.piab")
- command = Chef::Knife.run(%w{test yourself})
- rest = command.noauth_rest
- rest
- end
-
- let!(:http_client) do
- http_client = Net::HTTP.new(url.host, url.port)
- allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response)
- http_client
- end
-
- let(:url) { URI.parse("https://api.opscode.piab") }
-
- let(:http_response) do
- http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
- allow(http_response).to receive(:read_body)
- allow(http_response).to receive(:body).and_return(body)
- http_response["Content-Length"] = body.bytesize.to_s
- http_response
- end
-
- let(:body) { "ninja" }
-
- before(:each) do
- Chef::Config[:chef_server_url] = "https://api.opscode.piab"
- if KnifeSpecs.const_defined?(:TestYourself)
- KnifeSpecs.send :remove_const, :TestYourself
- end
- Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb"))
- Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) }
- end
-
- it "confirms that the headers include X-Remote-Request-Id" do
- expect(Net::HTTP::Get).to receive(:new).with("/monkey", headers).and_return(request_mock)
- rest.get("monkey")
- end
- end
-
- describe "when running a command" do
- before(:each) do
- if KnifeSpecs.const_defined?(:TestYourself)
- KnifeSpecs.send :remove_const, :TestYourself
- end
- Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb"))
- Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.is_a?(Class) }
- end
-
- it "merges the global knife CLI options" do
- extra_opts = {}
- extra_opts[:editor] = { long: "--editor EDITOR",
- description: "Set the editor to use for interactive commands",
- short: "-e EDITOR",
- default: "/usr/bin/vim" }
-
- # there is special hackery to return the subcommand instance going on here.
- command = Chef::Knife.run(%w{test yourself}, extra_opts)
- editor_opts = command.options[:editor]
- expect(editor_opts[:long]).to eq("--editor EDITOR")
- expect(editor_opts[:description]).to eq("Set the editor to use for interactive commands")
- expect(editor_opts[:short]).to eq("-e EDITOR")
- expect(editor_opts[:default]).to eq("/usr/bin/vim")
- end
-
- it "creates an instance of the subcommand and runs it" do
- command = Chef::Knife.run(%w{test yourself})
- expect(command).to be_an_instance_of(KnifeSpecs::TestYourself)
- expect(command.ran).to be_truthy
- end
-
- it "passes the command specific args to the subcommand" do
- command = Chef::Knife.run(%w{test yourself with some args})
- expect(command.name_args).to eq(%w{with some args})
- end
-
- it "excludes the command name from the name args when parts are joined with underscores" do
- command = Chef::Knife.run(%w{test_yourself with some args})
- expect(command.name_args).to eq(%w{with some args})
- end
-
- it "exits if no subcommand matches the CLI args" do
- stdout = StringIO.new
-
- allow(Chef::Knife.ui).to receive(:stderr).and_return(stderr)
- allow(Chef::Knife.ui).to receive(:stdout).and_return(stdout)
- expect(Chef::Knife.ui).to receive(:fatal)
- expect { Chef::Knife.run(%w{fuuu uuuu fuuuu}) }.to raise_error(SystemExit) { |e| expect(e.status).not_to eq(0) }
- end
-
- it "loads lazy dependencies" do
- Chef::Knife.run(%w{test yourself})
- expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy
- end
-
- it "loads lazy dependencies from multiple deps calls" do
- other_deps_loaded = false
- KnifeSpecs::TestYourself.class_eval do
- deps { other_deps_loaded = true }
- end
-
- Chef::Knife.run(%w{test yourself})
- expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy
- expect(other_deps_loaded).to be_truthy
- end
-
- describe "working with unmerged configuration in #config_source" do
- let(:command) { KnifeSpecs::TestYourself.new([]) }
-
- before do
- KnifeSpecs::TestYourself.option(:opt_with_default,
- short: "-D VALUE",
- default: "default-value")
- end
- # This supports a use case used by plugins, where the pattern
- # seems to follow:
- # cmd = KnifeCommand.new
- # cmd.config[:config_key] = value
- # cmd.run
- #
- # This bypasses Knife::run and the `merge_configs` call it
- # performs - config_source should break when that happens.
- context "when config is fed in directly without a merge" do
- it "retains the value but returns nil as a config source" do
- command.config[:test1] = "value"
- expect(command.config[:test1]).to eq "value"
- expect(command.config_source(:test1)).to eq nil
- end
- end
-
- end
- describe "merging configuration options" do
- before do
- KnifeSpecs::TestYourself.option(:opt_with_default,
- short: "-D VALUE",
- default: "default-value")
- end
-
- it "sets the default log_location to STDERR for Chef::Log warnings" do
- knife_command = KnifeSpecs::TestYourself.new([])
- knife_command.configure_chef
- expect(Chef::Config[:log_location]).to eq(STDERR)
- end
-
- it "sets the default log_level to warn so we can issue Chef::Log.warn" do
- knife_command = KnifeSpecs::TestYourself.new([])
- knife_command.configure_chef
- expect(Chef::Config[:log_level]).to eql(:warn)
- end
-
- it "prefers the default value from option definition if no config or command line value is present and reports the source as default" do
- knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
- knife_command.configure_chef
- expect(knife_command.config[:opt_with_default]).to eq("default-value")
- expect(knife_command.config_source(:opt_with_default)).to eq(:cli_default)
- end
-
- it "prefers a value in Chef::Config[:knife] to the default and reports the source as config" do
- Chef::Config[:knife][:opt_with_default] = "from-knife-config"
- knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
- knife_command.configure_chef
- expect(knife_command.config[:opt_with_default]).to eq("from-knife-config")
- expect(knife_command.config_source(:opt_with_default)).to eq(:config)
- end
-
- it "prefers a value from command line over Chef::Config and the default and reports the source as CLI" do
- knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"])
- knife_command.configure_chef
- expect(knife_command.config[:opt_with_default]).to eq("from-cli")
- expect(knife_command.config_source(:opt_with_default)).to eq(:cli)
- end
-
- it "merges `listen` config to Chef::Config" do
- knife_command = Chef::Knife.run(%w{test yourself --no-listen}, Chef::Application::Knife.options)
- expect(Chef::Config[:listen]).to be(false)
- expect(knife_command.config_source(:listen)).to eq(:cli)
- end
-
- it "merges Chef::Config[:knife] values into the config hash even if they have no cli keys" do
- Chef::Config[:knife][:opt_with_no_cli_key] = "from-knife-config"
- knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
- knife_command.configure_chef
- expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-config")
- expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config)
- end
-
- it "merges Chef::Config[:knife] default values into the config hash even if they have no cli keys" do
- Chef::Config.config_context :knife do
- default :opt_with_no_cli_key, "from-knife-default"
- end
- knife_command = KnifeSpecs::TestYourself.new([]) # empty argv
- knife_command.configure_chef
- expect(knife_command.config[:opt_with_no_cli_key]).to eq("from-knife-default")
- expect(knife_command.config_source(:opt_with_no_cli_key)).to eq(:config_default)
- end
-
- context "verbosity is one" do
- let(:fake_config) { "/does/not/exist/knife.rb" }
-
- before do
- knife.config[:verbosity] = 1
- knife.config[:config_file] = fake_config
- config_loader = double("Chef::WorkstationConfigLoader", load: true, no_config_found?: false, chef_config_dir: "/etc/chef", config_location: fake_config)
- allow(config_loader).to receive(:explicit_config_file=).with(fake_config).and_return(fake_config)
- allow(config_loader).to receive(:profile=)
- allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
- end
-
- it "prints the path to the configuration file used" do
- stdout, stderr, stdin = StringIO.new, StringIO.new, StringIO.new
- knife.ui = Chef::Knife::UI.new(stdout, stderr, stdin, {})
- expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}")
- knife.configure_chef
- end
- end
-
- # -VV (2) is debug, -VVV (3) is trace
- [ 2, 3 ].each do |verbosity|
- it "does not humanize the exception if Chef::Config[:verbosity] is #{verbosity}" do
- Chef::Config[:verbosity] = verbosity
- allow(knife).to receive(:run).and_raise(Exception)
- expect(knife).not_to receive(:humanize_exception)
- expect { knife.run_with_pretty_exceptions }.to raise_error(Exception)
- end
- end
- end
-
- describe "setting arbitrary configuration with --config-option" do
-
- let(:stdout) { StringIO.new }
-
- let(:stderr) { StringIO.new }
-
- let(:stdin) { StringIO.new }
-
- let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) }
-
- let(:subcommand) do
- KnifeSpecs::TestYourself.options = Chef::Application::Knife.options.merge(KnifeSpecs::TestYourself.options)
- KnifeSpecs::TestYourself.new(%w{--config-option badly_formatted_arg}).tap do |cmd|
- cmd.ui = ui
- end
- end
-
- it "sets arbitrary configuration via --config-option" do
- Chef::Knife.run(%w{test yourself --config-option arbitrary_config_thing=hello}, Chef::Application::Knife.options)
- expect(Chef::Config[:arbitrary_config_thing]).to eq("hello")
- end
-
- it "handles errors in arbitrary configuration" do
- expect(subcommand).to receive(:exit).with(1)
- subcommand.configure_chef
- expect(stderr.string).to include("ERROR: Unparsable config option \"badly_formatted_arg\"")
- expect(stdout.string).to include(subcommand.opt_parser.to_s)
- end
- end
-
- end
-
- describe "when first created" do
-
- let(:knife) {
- Kernel.load "spec/data/knife_subcommand/test_yourself.rb"
- KnifeSpecs::TestYourself.new(%w{with some args -s scrogramming})
- }
-
- it "it parses the options passed to it" do
- expect(knife.config[:scro]).to eq("scrogramming")
- end
-
- it "extracts its command specific args from the full arg list" do
- expect(knife.name_args).to eq(%w{with some args})
- end
-
- it "does not have lazy dependencies loaded" do
- skip "unstable with randomization... prolly needs more isolation"
-
- expect(knife.class.test_deps_loaded).not_to be_truthy
- end
- end
-
- describe "when formatting exceptions" do
-
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
- let(:stdin) { StringIO.new }
-
- let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
-
- before do
- knife.ui = ui
- expect(knife).to receive(:exit).with(100)
- end
-
- it "formats 401s nicely" do
- response = Net::HTTPUnauthorized.new("1.1", "401", "Unauthorized")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no syncronize your clock?"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("401 Unauthorized", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: Failed to authenticate to/)
- expect(stderr.string).to match(/Response: y u no syncronize your clock\?/)
- end
-
- it "formats 403s nicely" do
- response = Net::HTTPForbidden.new("1.1", "403", "Forbidden")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response))
- allow(knife).to receive(:username).and_return("sadpanda")
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/)
- expect(stderr.string).to match(/Response: y u no administrator/)
- end
-
- context "when proxy servers are set" do
- before do
- ENV["http_proxy"] = "xyz"
- end
-
- after do
- ENV.delete("http_proxy")
- end
-
- it "formats proxy errors nicely" do
- response = Net::HTTPForbidden.new("1.1", "403", "Forbidden")
- response.instance_variable_set(:@read, true)
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u no administrator"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("403 Forbidden", response))
- allow(knife).to receive(:username).and_return("sadpanda")
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action/)
- expect(stderr.string).to match(/ERROR: There are proxy servers configured, your server url may need to be added to NO_PROXY./)
- expect(stderr.string).to match(/Response: y u no administrator/)
- end
- end
-
- it "formats 400s nicely" do
- response = Net::HTTPBadRequest.new("1.1", "400", "Bad Request")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "y u search wrong"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("400 Bad Request", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: The data in your request was invalid/)
- expect(stderr.string).to match(/Response: y u search wrong/)
- end
-
- it "formats 404s nicely" do
- response = Net::HTTPNotFound.new("1.1", "404", "Not Found")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nothing to see here"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("404 Not Found", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: The object you are looking for could not be found/)
- expect(stderr.string).to match(/Response: nothing to see here/)
- end
-
- it "formats 406s (non-supported API version error) nicely" do
- response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
-
- # set the header
- response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(min_version: "0", max_version: "1", request_version: "10000000")
-
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("406 Not Acceptable", response))
-
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/The request that .* sent was using API version 10000000./)
- expect(stderr.string).to match(/The server you sent the request to supports a min API version of 0 and a max API version of 1./)
- expect(stderr.string).to match(/Please either update your .* or the server to be a compatible set./)
- end
-
- it "formats 500s nicely" do
- response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sad trombone"))
- allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("500 Internal Server Error", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: internal server error/)
- expect(stderr.string).to match(/Response: sad trombone/)
- end
-
- it "formats 502s nicely" do
- response = Net::HTTPBadGateway.new("1.1", "502", "Bad Gateway")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "sadder trombone"))
- allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("502 Bad Gateway", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: bad gateway/)
- expect(stderr.string).to match(/Response: sadder trombone/)
- end
-
- it "formats 503s nicely" do
- response = Net::HTTPServiceUnavailable.new("1.1", "503", "Service Unavailable")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "saddest trombone"))
- allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("503 Service Unavailable", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: Service temporarily unavailable/)
- expect(stderr.string).to match(/Response: saddest trombone/)
- end
-
- it "formats other HTTP errors nicely" do
- response = Net::HTTPPaymentRequired.new("1.1", "402", "Payment Required")
- response.instance_variable_set(:@read, true) # I hate you, net/http.
- allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(error: "nobugfixtillyoubuy"))
- allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("402 Payment Required", response))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: Payment Required/)
- expect(stderr.string).to match(/Response: nobugfixtillyoubuy/)
- end
-
- it "formats NameError and NoMethodError nicely" do
- allow(knife).to receive(:run).and_raise(NameError.new("Undefined constant FUUU"))
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(/ERROR: .* encountered an unexpected error/)
- expect(stderr.string).to match(/This may be a bug in the 'knife' .* command or plugin/)
- expect(stderr.string).to match(/Exception: NameError: Undefined constant FUUU/)
- end
-
- it "formats missing private key errors nicely" do
- allow(knife).to receive(:run).and_raise(Chef::Exceptions::PrivateKeyMissing.new("key not there"))
- allow(knife).to receive(:api_key).and_return("/home/root/.chef/no-key-here.pem")
- knife.run_with_pretty_exceptions
- expect(stderr.string).to match(%r{ERROR: Your private key could not be loaded from /home/root/.chef/no-key-here.pem})
- expect(stderr.string).to match(/Check your configuration file and ensure that your private key is readable/)
- end
-
- it "formats connection refused errors nicely" do
- allow(knife).to receive(:run).and_raise(Errno::ECONNREFUSED.new("y u no shut up"))
- knife.run_with_pretty_exceptions
- # Errno::ECONNREFUSED message differs by platform
- # *nix = Errno::ECONNREFUSED: Connection refused
- # win32: Errno::ECONNREFUSED: No connection could be made because the target machine actively refused it.
- expect(stderr.string).to match(/ERROR: Network Error: .* - y u no shut up/)
- expect(stderr.string).to match(/Check your .* configuration and network settings/)
- end
-
- it "formats SSL errors nicely and suggests to use `knife ssl check` and `knife ssl fetch`" do
- error = OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed")
- allow(knife).to receive(:run).and_raise(error)
-
- knife.run_with_pretty_exceptions
-
- expected_message = <<~MSG
- ERROR: Could not establish a secure connection to the server.
- Use `.* ssl check` to troubleshoot your SSL configuration.
- If your server uses a self-signed certificate, you can use
- `.* ssl fetch` to make .* trust the server's certificates.
- MSG
- expect(stderr.string).to match(expected_message)
- end
-
- end
-
-end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index ac2c95d7e7..5767e3987b 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -576,7 +576,7 @@ describe "LWRP" do
@tmpdir = File.join(@tmpparent, "lwrp")
Dir.mkdir(@tmpdir)
resource_path = File.join(@tmpdir, "once.rb")
- IO.write(resource_path, "default_action :create")
+ IO.write(resource_path, "unified_mode true\ndefault_action :create")
@test_lwrp_class = Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil)
end
diff --git a/spec/unit/mixin/openssl_helper_spec.rb b/spec/unit/mixin/openssl_helper_spec.rb
index 7c12037798..7766e8f9b2 100644
--- a/spec/unit/mixin/openssl_helper_spec.rb
+++ b/spec/unit/mixin/openssl_helper_spec.rb
@@ -21,13 +21,6 @@ describe Chef::Mixin::OpenSSLHelper do
Class.new { include Chef::Mixin::OpenSSLHelper }.new
end
- describe ".included" do
- it "requires openssl" do
- instance
- expect(defined?(OpenSSL)).to_not be(false)
- end
- end
-
# Path helpers
describe "#get_key_filename" do
context "When the input is not a string" do
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
index 459c2ce237..1b00d9ee09 100644
--- a/spec/unit/mixin/params_validate_spec.rb
+++ b/spec/unit/mixin/params_validate_spec.rb
@@ -359,10 +359,11 @@ describe Chef::Mixin::ParamsValidate do
expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id)
end
- it "should set and return a default value when the argument is nil, then return the same value" do
+ it "should set and return a default value when the argument is nil, then return a dup of the value" do
value = "meow"
- expect(@vo.set_or_return(:test, nil, { default: value }).object_id).to eq(value.object_id)
- expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, { default: value }).object_id).not_to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, {}).object_id).not_to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, {})).to eql(value)
end
it "should raise an ArgumentError when argument is nil and required is true" do
diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb
index 6e78725bd9..e73d449225 100644
--- a/spec/unit/node/attribute_spec.rb
+++ b/spec/unit/node/attribute_spec.rb
@@ -898,7 +898,7 @@ describe Chef::Node::Attribute do
end
end
- describe "index" do
+ describe "index", ruby: "< 3.0.0" do
# Hash#index is deprecated and triggers warnings.
def silence
old_verbose = $VERBOSE
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index ec7beb9a50..284a993f5c 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -1951,4 +1951,82 @@ describe Chef::Node do
expect(node["foo"]["bar"]["test"]).to eql("right")
end
end
+
+ describe "lazy values" do
+ it "supports lazy values in attributes" do
+ node.instance_eval do
+ default["foo"]["bar"] = lazy { node["fizz"]["buzz"] }
+ default["fizz"]["buzz"] = "works"
+ end
+ expect(node["foo"]["bar"]).to eql("works")
+ end
+
+ it "lazy values maintain laziness" do
+ node.instance_eval do
+ default["foo"]["bar"] = lazy { node["fizz"]["buzz"] }
+ default["fizz"]["buzz"] = "works"
+ end
+ expect(node["foo"]["bar"]).to eql("works")
+ node.default["fizz"]["buzz"] = "still works"
+ expect(node["foo"]["bar"]).to eql("still works")
+ end
+
+ it "supports recursive lazy values in attributes" do
+ node.instance_eval do
+ default["cool"]["beans"] = lazy { node["foo"]["bar"] }
+ default["foo"]["bar"] = lazy { node["fizz"]["buzz"] }
+ default["fizz"]["buzz"] = "works"
+ end
+ expect(node["cool"]["beans"]).to eql("works")
+ node.default["fizz"]["buzz"] = "still works"
+ expect(node["cool"]["beans"]).to eql("still works")
+ end
+
+ it "supports top level lazy values in attributes" do
+ # due to the top level deep merge cache these are special cases
+ node.instance_eval do
+ default["cool"] = lazy { node["foo"] }
+ default["foo"] = lazy { node["fizz"] }
+ default["fizz"] = "works"
+ end
+ expect(node["cool"]).to eql("works")
+ node.default["fizz"] = "still works"
+ expect(node["cool"]).to eql("still works")
+ end
+
+ it "supports deep merged values in attributes" do
+ node.instance_eval do
+ override["bar"]["cool"] = lazy { node["bar"]["foo"] }
+ override["bar"]["foo"] = lazy { node["bar"]["fizz"] }
+ override["bar"]["fizz"] = "works"
+ end
+ expect(node["bar"]["cool"]).to eql("works")
+ node.override["bar"]["fizz"] = "still works"
+ expect(node["bar"]["cool"]).to eql("still works")
+ end
+
+ it "supports overridden deep merged values in attributes (deep_merge)" do
+ node.instance_eval do
+ role_override["bar"] = { "cool" => "bad", "foo" => "bad", "fizz" => "bad" }
+ force_override["bar"]["cool"] = lazy { node["bar"]["foo"] }
+ force_override["bar"]["foo"] = lazy { node["bar"]["fizz"] }
+ force_override["bar"]["fizz"] = "works"
+ end
+ expect(node["bar"]["cool"]).to eql("works")
+ node.force_override["bar"]["fizz"] = "still works"
+ expect(node["bar"]["cool"]).to eql("still works")
+ end
+
+ it "supports overridden deep merged values in attributes (hash_only_merge)" do
+ node.instance_eval do
+ default["bar"] = { "cool" => "bad", "foo" => "bad", "fizz" => "bad" }
+ override["bar"]["cool"] = lazy { node["bar"]["foo"] }
+ override["bar"]["foo"] = lazy { node["bar"]["fizz"] }
+ override["bar"]["fizz"] = "works"
+ end
+ expect(node["bar"]["cool"]).to eql("works")
+ node.override["bar"]["fizz"] = "still works"
+ expect(node["bar"]["cool"]).to eql("still works")
+ end
+ end
end
diff --git a/spec/unit/org_group_spec.rb b/spec/unit/org_group_spec.rb
new file mode 100644
index 0000000000..47a2587a9b
--- /dev/null
+++ b/spec/unit/org_group_spec.rb
@@ -0,0 +1,45 @@
+
+# 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 "spec_helper"
+require "chef/org"
+
+describe Chef::Org do
+ let(:org) { Chef::Org.new("myorg") }
+
+ describe "API Interactions" do
+ before(:each) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ @rest = double("rest")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@rest)
+ end
+
+ describe "group" do
+ it "should load group data when it's not loaded." do
+ expect(@rest).to receive(:get_rest).with("organizations/myorg/groups/admins").and_return({})
+ org.group("admins")
+ end
+
+ it "should not load group data a second time when it's already loaded." do
+ expect(@rest).to receive(:get_rest)
+ .with("organizations/myorg/groups/admins")
+ .and_return({ anything: "goes" })
+ .exactly(:once)
+ admin1 = org.group("admins")
+ admin2 = org.group("admins")
+ expect(admin1).to eq admin2
+ end
+ end
+ end
+end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
index 6c64a961b4..aa75710ffb 100644
--- a/spec/unit/property_spec.rb
+++ b/spec/unit/property_spec.rb
@@ -541,13 +541,16 @@ describe "Chef::Resource.property" do
it "when x is not set, it returns ''" do
expect(resource.x).to eq ""
end
- it "x is immutable" do
- expect { resource.x << "foo" }.to raise_error(FrozenError, /can't modify frozen String/)
+ it "setting x does not mutate the default" do
+ expect(resource.x).to eq ""
+ resource.x << "foo"
+ expect(resource.x).to eq "foo"
+ expect(resource_class.new("other").x).to eq ""
end
end
with_property ":x, default: lazy { '' }" do
- it "x is immutable" do
+ it "setting x does not mutate the default" do
expect(resource.x).to eq ""
resource.x << "foo"
expect(resource.x).to eq "foo"
@@ -561,16 +564,14 @@ describe "Chef::Resource.property" do
it "when x is not set, it returns {}" do
expect(resource.x).to eq({})
end
- it "x is immutable" do
- expect { resource.x["foo"] = "bar" }.to raise_error(FrozenError, /can't modify frozen Hash/)
- end
- it "The same exact value is returned multiple times in a row" do
- value = resource.x
- expect(value).to eq({})
- expect(resource.x.object_id).to eq(value.object_id)
+ it "setting x does not mutate the default" do
+ expect(resource.x).to eq({})
+ resource.x["plants"] = "zombies"
+ expect(resource.x).to eq({ "plants" => "zombies" })
+ expect(resource_class.new("other").x).to eq({})
end
- it "Multiple instances of x receive the exact same value" do
- expect(resource.x.object_id).to eq(resource_class.new("blah2").x.object_id)
+ it "Multiple instances of x receive different values" do
+ expect(resource.x.object_id).not_to eq(resource_class.new("blah2").x.object_id)
end
end
@@ -594,14 +595,14 @@ describe "Chef::Resource.property" do
it "when x is not set, it returns [{foo: 'bar'}]" do
expect(resource.x).to eq([{ foo: "bar" }])
end
- it "x is immutable" do
- expect { resource.x << :other }.to raise_error(FrozenError, /can't modify frozen Array/)
- end
- it "x.first is immutable" do
- expect { resource.x.first[:foo] = "other" }.to raise_error(FrozenError, /can't modify frozen Hash/)
+ it "setting x does not mutate the default" do
+ expect(resource.x).to eq([{ foo: "bar" }])
+ resource.x[0][:foo] << "baz"
+ expect(resource.x).to eq([{ foo: "barbaz" }])
+ expect(resource_class.new("other").x).to eq([{ foo: "bar" }])
end
- it "x.first[:foo] is immutable" do
- expect { resource.x.first[:foo] << "other" }.to raise_error(FrozenError, /can't modify frozen String/)
+ it "Multiple instances of x receive different values" do
+ expect(resource.x.object_id).not_to eq(resource_class.new("blah2").x.object_id)
end
end
end
@@ -708,10 +709,10 @@ describe "Chef::Resource.property" do
expect(resource.x).to eq "hi1"
expect(Namer.current_index).to eq 1
end
- it "when x is retrieved, coercion is run each time" do
+ it "when x is retrieved, coercion is run exactly once" do
expect(resource.x).to eq "101"
- expect(resource.x).to eq "102"
- expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq "101"
+ expect(Namer.current_index).to eq 1
end
end
diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb
index 76f170312e..9a276bfffd 100644
--- a/spec/unit/provider/cron_spec.rb
+++ b/spec/unit/provider/cron_spec.rb
@@ -719,7 +719,7 @@ describe Chef::Provider::Cron do
it "should log nothing changed" do
expect(logger).to receive(:trace).with("Found cron '#{@new_resource.name}'")
- expect(logger).to receive(:trace).with("Skipping existing cron entry '#{@new_resource.name}'")
+ expect(logger).to receive(:debug).with("#{@new_resource}: Skipping existing cron entry")
@provider.run_action(:create)
end
end
diff --git a/spec/unit/provider/group/gpasswd_spec.rb b/spec/unit/provider/group/gpasswd_spec.rb
index adc6efb5a6..98deb9dd69 100644
--- a/spec/unit/provider/group/gpasswd_spec.rb
+++ b/spec/unit/provider/group/gpasswd_spec.rb
@@ -68,7 +68,7 @@ describe Chef::Provider::Group::Gpasswd, "modify_group_members" do
end
it "logs a message and sets group's members to 'none'" do
- expect(logger).to receive(:trace).with("group[wheel] setting group members to: none")
+ expect(logger).to receive(:debug).with("group[wheel] setting group members to: none")
expect(@provider).to receive(:shell_out_compacted!).with("gpasswd", "-M", "", "wheel")
@provider.modify_group_members
end
@@ -88,7 +88,7 @@ describe Chef::Provider::Group::Gpasswd, "modify_group_members" do
describe "when the resource specifies group members" do
it "should log an appropriate debug message" do
- expect(logger).to receive(:trace).with("group[wheel] setting group members to: lobster, rage, fist")
+ expect(logger).to receive(:debug).with("group[wheel] setting group members to: lobster, rage, fist")
allow(@provider).to receive(:shell_out_compacted!)
@provider.modify_group_members
end
diff --git a/spec/unit/provider/group/groupmod_spec.rb b/spec/unit/provider/group/groupmod_spec.rb
index 5a7d1ada77..9961d32bc5 100644
--- a/spec/unit/provider/group/groupmod_spec.rb
+++ b/spec/unit/provider/group/groupmod_spec.rb
@@ -64,7 +64,7 @@ describe Chef::Provider::Group::Groupmod do
end
it "logs a message and sets group's members to 'none', then removes existing group members" do
- expect(logger).to receive(:trace).with("group[wheel] setting group members to: none")
+ expect(logger).to receive(:debug).with("group[wheel] setting group members to: none")
expect(@provider).to receive(:shell_out_compacted!).with("group", "mod", "-n", "wheel_bak", "wheel")
expect(@provider).to receive(:shell_out_compacted!).with("group", "add", "-g", "123", "-o", "wheel")
expect(@provider).to receive(:shell_out_compacted!).with("group", "del", "wheel_bak")
@@ -79,7 +79,7 @@ describe Chef::Provider::Group::Groupmod do
end
it "logs a message and does not modify group membership" do
- expect(logger).to receive(:trace).with("group[wheel] not changing group members, the group has no members to add")
+ expect(logger).to receive(:debug).with("group[wheel] not changing group members, the group has no members to add")
expect(@provider).not_to receive(:shell_out_compacted!)
@provider.manage_group
end
diff --git a/spec/unit/provider/group/pw_spec.rb b/spec/unit/provider/group/pw_spec.rb
index f46bb9cb48..b6d6942a83 100644
--- a/spec/unit/provider/group/pw_spec.rb
+++ b/spec/unit/provider/group/pw_spec.rb
@@ -96,7 +96,7 @@ describe Chef::Provider::Group::Pw do
end
it "should log an appropriate message" do
- expect(logger).to receive(:trace).with("group[wheel] removing group members: all,your,base")
+ expect(logger).to receive(:debug).with("group[wheel] removing group members: all,your,base")
@provider.set_members_options
end
@@ -112,7 +112,7 @@ describe Chef::Provider::Group::Pw do
end
it "should log an appropriate debug message" do
- expect(logger).to receive(:trace).with("group[wheel] adding group members: all,your,base")
+ expect(logger).to receive(:debug).with("group[wheel] adding group members: all,your,base")
@provider.set_members_options
end
diff --git a/spec/unit/provider/group_spec.rb b/spec/unit/provider/group_spec.rb
index a3701c1f45..b55d334f92 100644
--- a/spec/unit/provider/group_spec.rb
+++ b/spec/unit/provider/group_spec.rb
@@ -119,7 +119,7 @@ describe Chef::Provider::User do
end
it "should return true if the append is true and excluded_members include an existing user" do
- @new_resource.members.each { |m| @new_resource.excluded_members << m }
+ @new_resource.excluded_members += @new_resource.members
@new_resource.members.clear
@new_resource.append(true)
expect(@provider.compare_group).to be_truthy
diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb
index eebf0a9d07..4ab4c57094 100644
--- a/spec/unit/provider/link_spec.rb
+++ b/spec/unit/provider/link_spec.rb
@@ -41,7 +41,7 @@ describe Chef::Resource::Link do
end
def canonicalize(path)
- ChefUtils.windows? ? path.tr("/", '\\') : path
+ ChefUtils.windows? ? path.tr("/", "\\") : path
end
describe "when the target is a symlink" do
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
index 9a7d9198b5..13b2443b8f 100644
--- a/spec/unit/provider/mount/mount_spec.rb
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -66,6 +66,7 @@ describe Chef::Provider::Mount::Mount do
describe "when dealing with network mounts" do
{ "nfs" => "nfsserver:/vol/path",
+ "cephfs" => "cephserver:6789:/",
"cifs" => "//cifsserver/share" }.each do |type, fs_spec|
it "should detect network fs_spec (#{type})" do
@new_resource.device fs_spec
@@ -505,6 +506,57 @@ describe Chef::Provider::Mount::Mount do
end
end
+ context "network mount" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Mount.new("/tmp/bar")
+ @new_resource.device "cephserver:6789:/"
+ @new_resource.device_type :device
+ @new_resource.fstype "cephfs"
+
+ @new_resource.supports remount: false
+
+ @provider = Chef::Provider::Mount::Mount.new(@new_resource, @run_context)
+
+ allow(::File).to receive(:exists?).with("cephserver:6789:/").and_return true
+ allow(::File).to receive(:exists?).with("/tmp/bar").and_return true
+ allow(::File).to receive(:realpath).with("cephserver:6789:/").and_return "cephserver:6789:/"
+ allow(::File).to receive(:realpath).with("/tmp/bar").and_return "/tmp/foo"
+ end
+
+ before do
+ @current_resource = Chef::Resource::Mount.new("/tmp/foo")
+ @current_resource.device "cephserver:6789:/"
+ @current_resource.device_type :device
+ @current_resource.fstype "cephfs"
+
+ @provider.current_resource = @current_resource
+ end
+
+ it "should enable network mount if enabled isn't true" do
+ @current_resource.enabled(false)
+
+ @fstab = StringIO.new
+ allow(::File).to receive(:open).with("/etc/fstab", "a").and_yield(@fstab)
+ @provider.enable_fs
+ expect(@fstab.string).to match(%r{^cephserver:6789:/\s+/tmp/bar\s+cephfs\s+defaults\s+0\s+2\s*$})
+ end
+
+ it "should not enable network if enabled is true and resources match" do
+ @current_resource.enabled(true)
+ @current_resource.fstype("cephfs")
+ @current_resource.options(["defaults"])
+ @current_resource.dump(0)
+ @current_resource.pass(2)
+ expect(::File).not_to receive(:open).with("/etc/fstab", "a")
+
+ @provider.enable_fs
+ end
+ end
+
# the fstab might contain the mount with the device as a device but the resource has a label.
# we should not create two mount lines, but update the existing one
# not supported on solaris because it can't cope with a UUID device type
diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb
index 2ff1f0fddc..cf50c124a9 100644
--- a/spec/unit/provider/package/apt_spec.rb
+++ b/spec/unit/provider/package/apt_spec.rb
@@ -48,6 +48,21 @@ describe Chef::Provider::Package::Apt do
@timeout = 900
end
+ def ubuntu1804downgrade_stubs
+ so = instance_double(Mixlib::ShellOut, stdout: "apt 1.6~beta1 (amd64)\notherstuff\n")
+ so2 = instance_double(Mixlib::ShellOut, error?: false)
+ allow(@provider).to receive(:shell_out).with("apt-get --version").and_return(so)
+ allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.6~beta1", "gt", "1.1.0").and_return(so2)
+ end
+
+ def ubuntu1404downgrade_stubs
+ so = instance_double(Mixlib::ShellOut, stdout: "apt 1.0.1ubuntu2 for amd64 compiled on Dec 8 2016 16:23:38\notherstuff\n")
+ so2 = instance_double(Mixlib::ShellOut, error?: true)
+ allow(@provider).to receive(:shell_out).with("apt-get --version").and_return(so)
+ allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.0.1ubuntu2", "gt", "1.1.0").and_return(so2)
+ allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.0.1ubuntu2", "eq", "1.1.0").and_return(so2)
+ end
+
describe "when loading current resource" do
it "should create a current resource with the name of the new_resource" do
@@ -237,6 +252,72 @@ describe Chef::Provider::Package::Apt do
).and_return(@shell_out)
expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
end
+
+ it "downgrades when requested" do
+ ubuntu1804downgrade_stubs
+ so = instance_double(Mixlib::ShellOut, stdout: "apt 1.6~beta1 (amd64)\notherstuff\n")
+ so2 = instance_double(Mixlib::ShellOut, error?: false)
+ allow(@provider).to receive(:shell_out).with("apt-get --version").and_return(so)
+ allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.6~beta1", "gt", "1.1.0").and_return(so2)
+
+ @new_resource.package_name("libmysqlclient-dev")
+ @new_resource.version("5.1.41-3ubuntu12.7")
+ real_package_out = <<~RPKG_STDOUT
+ libmysqlclient-dev:
+ Installed: 5.1.41-3ubuntu12.10
+ Candidate: 5.1.41-3ubuntu12.10
+ Version table:
+ *** 5.1.41-3ubuntu12.10 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main packages
+ 100 /var/lib/dpkg/status
+ 5.1.41-3ubuntu12.7 0
+ 500 http://security.ubuntu.com/ubuntu/ lucid-security/main packages
+ 5.1.41-3ubuntu12 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main packages
+ RPKG_STDOUT
+ real_package = double(stdout: real_package_out, exitstatus: 0)
+ expect(@provider).to receive(:shell_out_compacted!).with(
+ "apt-cache", "policy", "libmysqlclient-dev",
+ env: { "DEBIAN_FRONTEND" => "noninteractive" },
+ timeout: @timeout
+ ).and_return(real_package)
+ expect(@provider).to receive(:shell_out_compacted!).with(
+ "apt-get", "-q", "-y", "--allow-downgrades", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", "install", "libmysqlclient-dev=5.1.41-3ubuntu12.7",
+ env: { "DEBIAN_FRONTEND" => "noninteractive" },
+ timeout: @timeout
+ )
+ @provider.run_action(:install)
+ end
+
+ it "raises an exception if bad version specified" do
+ @new_resource.package_name("libmysqlclient-dev")
+ @new_resource.version("non_existent")
+ real_package_out = <<~RPKG_STDOUT
+ libmysqlclient-dev:
+ Installed: 5.1.41-3ubuntu12.10
+ Candidate: 5.1.41-3ubuntu12.10
+ Version table:
+ *** 5.1.41-3ubuntu12.10 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main packages
+ 100 /var/lib/dpkg/status
+ 5.1.41-3ubuntu12.7 0
+ 500 http://security.ubuntu.com/ubuntu/ lucid-security/main packages
+ 5.1.41-3ubuntu12 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main packages
+ RPKG_STDOUT
+ real_package = double(stdout: real_package_out, exitstatus: 0)
+ expect(@provider).to receive(:shell_out_compacted!).with(
+ "apt-cache", "policy", @new_resource.package_name,
+ env: { "DEBIAN_FRONTEND" => "noninteractive" } ,
+ timeout: @timeout
+ ).and_return(real_package)
+ expect(@provider).to receive(:shell_out_compacted!).with(
+ "apt-cache", "showpkg", @new_resource.package_name,
+ env: { "DEBIAN_FRONTEND" => "noninteractive" } ,
+ timeout: @timeout
+ ).and_return(real_package)
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
+ end
end
context "after loading the current resource" do
@@ -257,21 +338,6 @@ describe Chef::Provider::Package::Apt do
})
end
- def ubuntu1804downgrade_stubs
- so = instance_double(Mixlib::ShellOut, stdout: "apt 1.6~beta1 (amd64)\notherstuff\n")
- so2 = instance_double(Mixlib::ShellOut, error?: false)
- allow(@provider).to receive(:shell_out).with("apt-get --version").and_return(so)
- allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.6~beta1", "gt", "1.1.0").and_return(so2)
- end
-
- def ubuntu1404downgrade_stubs
- so = instance_double(Mixlib::ShellOut, stdout: "apt 1.0.1ubuntu2 for amd64 compiled on Dec 8 2016 16:23:38\notherstuff\n")
- so2 = instance_double(Mixlib::ShellOut, error?: true)
- allow(@provider).to receive(:shell_out).with("apt-get --version").and_return(so)
- allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.0.1ubuntu2", "gt", "1.1.0").and_return(so2)
- allow(@provider).to receive(:shell_out).with("dpkg", "--compare-versions", "1.0.1ubuntu2", "eq", "1.1.0").and_return(so2)
- end
-
describe "install_package" do
before(:each) do
ubuntu1804downgrade_stubs
@@ -513,7 +579,7 @@ describe Chef::Provider::Package::Apt do
).and_return(instance_double(
Mixlib::ShellOut, stdout: "irssi"
))
- expect(logger).to receive(:trace).with("#{@provider.new_resource} is already locked")
+ expect(logger).to receive(:debug).with("#{@provider.new_resource} is already locked")
@provider.action_lock
end
@@ -534,7 +600,7 @@ describe Chef::Provider::Package::Apt do
).and_return(instance_double(
Mixlib::ShellOut, stdout: ""
))
- expect(logger).to receive(:trace).with("#{@provider.new_resource} is already unlocked")
+ expect(logger).to receive(:debug).with("#{@provider.new_resource} is already unlocked")
@provider.action_unlock
end
@@ -590,7 +656,7 @@ describe Chef::Provider::Package::Apt do
end
end
- describe "#action_install" do
+ describe "#action_upgrade" do
it "should run dpkg to compare versions if an existing version is installed" do
allow(@provider).to receive(:get_current_versions).and_return("1.4.0")
allow(@new_resource).to receive(:allow_downgrade).and_return(false)
diff --git a/spec/unit/provider/package/deb_spec.rb b/spec/unit/provider/package/deb_spec.rb
index da06b2d1b6..46867fc573 100644
--- a/spec/unit/provider/package/deb_spec.rb
+++ b/spec/unit/provider/package/deb_spec.rb
@@ -66,7 +66,7 @@ describe Chef::Provider::Package::Deb do
it "does not reconfigure the package if the package is not installed" do
allow(provider).to receive(:get_current_versions).and_return(nil)
allow(provider.load_current_resource).to receive(:version).and_return(nil)
- expect(logger).to receive(:trace).with("apt_package[emacs] is NOT installed - nothing to do")
+ expect(logger).to receive(:debug).with("apt_package[emacs] is NOT installed - nothing to do")
expect(provider).not_to receive(:reconfig_package)
provider.run_action(:reconfig)
expect(new_resource).not_to be_updated_by_last_action
@@ -75,7 +75,7 @@ describe Chef::Provider::Package::Deb do
it "does not reconfigure the package if no response_file is given" do
allow(provider).to receive(:get_current_versions).and_return("1.0")
allow(new_resource).to receive(:response_file).and_return(nil)
- expect(logger).to receive(:trace).with("apt_package[emacs] no response_file provided - nothing to do")
+ expect(logger).to receive(:debug).with("apt_package[emacs] no response_file provided - nothing to do")
expect(provider).not_to receive(:reconfig_package)
provider.run_action(:reconfig)
expect(new_resource).not_to be_updated_by_last_action
@@ -86,7 +86,7 @@ describe Chef::Provider::Package::Deb do
allow(new_resource).to receive(:response_file).and_return(true)
expect(provider).to receive(:get_preseed_file).and_return(false)
allow(provider).to receive(:preseed_package).and_return(false)
- expect(logger).to receive(:trace).with("apt_package[emacs] preseeding has not changed - nothing to do")
+ expect(logger).to receive(:debug).with("apt_package[emacs] preseeding has not changed - nothing to do")
expect(provider).not_to receive(:reconfig_package)
provider.run_action(:reconfig)
expect(new_resource).not_to be_updated_by_last_action
diff --git a/spec/unit/provider/package/dnf/python_helper_spec.rb b/spec/unit/provider/package/dnf/python_helper_spec.rb
index 1f94147273..fb84cdddf9 100644
--- a/spec/unit/provider/package/dnf/python_helper_spec.rb
+++ b/spec/unit/provider/package/dnf/python_helper_spec.rb
@@ -19,11 +19,18 @@ require "spec_helper"
# NOTE: most of the tests of this functionality are baked into the func tests for the dnf package provider
-describe Chef::Provider::Package::Dnf::PythonHelper do
+# run this test only for following platforms.
+exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf"))
+describe Chef::Provider::Package::Dnf::PythonHelper, :requires_root, external: exclude_test do
let(:helper) { Chef::Provider::Package::Dnf::PythonHelper.instance }
+ before(:each) { Singleton.__init__(Chef::Provider::Package::Dnf::PythonHelper) }
it "propagates stacktraces on stderr from the forked subprocess", :rhel do
allow(helper).to receive(:dnf_command).and_return("ruby -e 'raise \"your hands in the air\"'")
expect { helper.package_query(:whatprovides, "tcpdump") }.to raise_error(/your hands in the air/)
end
+
+ it "compares EVRAs with dots in the release correctly" do
+ expect(helper.compare_versions("0:1.8.29-6.el8.x86_64", "0:1.8.29-6.el8_3.1.x86_64")).to eql(-1)
+ end
end
diff --git a/spec/unit/provider/package/freebsd/pkgng_spec.rb b/spec/unit/provider/package/freebsd/pkgng_spec.rb
index bc78a178eb..ce5d58b7dc 100644
--- a/spec/unit/provider/package/freebsd/pkgng_spec.rb
+++ b/spec/unit/provider/package/freebsd/pkgng_spec.rb
@@ -66,7 +66,7 @@ describe Chef::Provider::Package::Freebsd::Port do
end
it "should query pkg database" do
- expect(@provider).to receive(:shell_out_compacted!).with("pkg", "info", "zsh", env: nil, returns: [0, 70], timeout: 900).and_return(@pkg_info)
+ expect(@provider).to receive(:shell_out_compacted!).with("pkg", "info", "zsh", env: nil, returns: [0, 1, 70], timeout: 900).and_return(@pkg_info)
expect(@provider.current_installed_version).to eq("3.1.7")
end
end
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index 6f31c231ce..5013b6505c 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -50,6 +50,8 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
before do
@gem_env = Chef::Provider::Package::Rubygems::CurrentGemEnvironment.new
allow(@gem_env).to receive(:logger).and_return(logger)
+
+ WebMock.disable_net_connect!
end
it "determines the gem paths from the in memory rubygems" do
@@ -61,10 +63,8 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.7")
expect(Gem::Specification).to receive(:dirs).and_return(["/path/to/gems/specifications", "/another/path/to/gems/specifications"])
expect(Gem::Specification).to receive(:installed_stubs).with(["/path/to/gems/specifications", "/another/path/to/gems/specifications"], "rspec-core-*.gemspec").and_return(gems)
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0")
+ else # >= Rubygems 1.8 behavior
expect(Gem::Specification).to receive(:find_all_by_name).with("rspec-core", Gem::Dependency.new("rspec-core").requirement).and_return(gems)
- else
- expect(Gem.source_index).to receive(:search).with(Gem::Dependency.new("rspec-core", nil)).and_return(gems)
end
expect(@gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil))).to eq(gems)
end
@@ -103,7 +103,7 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
expect(Gem.sources).to eq(normal_sources)
end
- context "new default rubygems behavior" do
+ context "new default rubygems behavior", ruby: ">= 3.0" do
before do
Chef::Config[:rubygems_cache_enabled] = false
@@ -113,28 +113,53 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
end
it "finds a matching gem candidate version on rubygems 2.0.0+" do
- dep = Gem::Dependency.new("rspec", ">= 0")
+ stub_request(:head, "https://index.rubygems.org/")
+ .to_return(status: 200, body: "", headers: {})
+ stub_request(:get, "https://index.rubygems.org/info/sexp_processor")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-info")))
+ stub_request(:get, "https://index.rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz")))
+
+ dep = Gem::Dependency.new("sexp_processor", ">= 0")
expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version)
end
it "gives the candidate version as nil if none is found" do
- dep = Gem::Dependency.new("lksdjflksdjflsdkfj", ">= 0")
+ stub_request(:head, "https://index.rubygems.org/")
+ .to_return(status: 200, body: "", headers: {})
+ stub_request(:get, "https://index.rubygems.org/info/nonexistent_gem")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "nonexistent_gem-info")))
+
+ dep = Gem::Dependency.new("nonexistent_gem", ">= 0")
expect(@gem_env.candidate_version_from_remote(dep)).to be_nil
end
it "finds a matching gem from a specific gemserver when explicit sources are given (to a server that doesn't respond to api requests)" do
- dep = Gem::Dependency.new("rspec", ">= 0")
- expect(@gem_env.candidate_version_from_remote(dep, "https://rubygems.org")).to be_kind_of(Gem::Version)
+ stub_request(:head, "https://rubygems2.org/")
+ .to_return(status: 200, body: "", headers: {})
+ stub_request(:get, "https://rubygems2.org/info/sexp_processor")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-info")))
+ stub_request(:get, "https://rubygems2.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz")))
+
+ dep = Gem::Dependency.new("sexp_processor", ">= 0")
+ expect(@gem_env.candidate_version_from_remote(dep, "https://rubygems2.org")).to be_kind_of(Gem::Version)
end
end
context "old rubygems caching behavior" do
before do
Chef::Config[:rubygems_cache_enabled] = true
+
+ stub_request(:get, "https://rubygems.org/latest_specs.4.8.gz")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "latest_specs.4.8.gz")))
end
it "finds a matching gem candidate version on rubygems 2.0.0+" do
- dep = Gem::Dependency.new("rspec", ">= 0")
+ stub_request(:get, "https://rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz")))
+
+ dep = Gem::Dependency.new("sexp_processor", ">= 0")
expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version)
end
@@ -144,8 +169,11 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
end
it "finds a matching gem from a specific gemserver when explicit sources are given" do
- dep = Gem::Dependency.new("rspec", ">= 0")
- expect(@gem_env.candidate_version_from_remote(dep, "http://production.cf.rubygems.org")).to be_kind_of(Gem::Version)
+ stub_request(:get, "https://rubygems.org/quick/Marshal.4.8/sexp_processor-4.15.1.gemspec.rz")
+ .to_return(status: 200, body: File.binread(File.join(CHEF_SPEC_DATA, "rubygems.org", "sexp_processor-4.15.1.gemspec.rz")))
+
+ dep = Gem::Dependency.new("sexp_processor", ">= 0")
+ expect(@gem_env.candidate_version_from_remote(dep, "http://rubygems2.org")).to be_kind_of(Gem::Version)
end
end
@@ -220,13 +248,8 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
it "builds the gems source index from the gem paths" do
allow(@gem_env).to receive(:gem_paths).and_return(["/path/to/gems", "/another/path/to/gems"])
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0")
- @gem_env.gem_specification
- expect(Gem::Specification.dirs).to eq([ "/path/to/gems/specifications", "/another/path/to/gems/specifications" ])
- else
- expect(Gem::SourceIndex).to receive(:from_gems_in).with("/path/to/gems/specifications", "/another/path/to/gems/specifications")
- @gem_env.gem_source_index
- end
+ @gem_env.gem_specification
+ expect(Gem::Specification.dirs).to eq([ "/path/to/gems/specifications", "/another/path/to/gems/specifications" ])
end
it "determines the installed versions of gems from the source index" do
@@ -236,12 +259,9 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
allow(@gem_env).to receive(:gem_specification).and_return(Gem::Specification)
expect(Gem::Specification).to receive(:dirs).and_return(["/path/to/gems/specifications", "/another/path/to/gems/specifications"])
expect(Gem::Specification).to receive(:installed_stubs).with(["/path/to/gems/specifications", "/another/path/to/gems/specifications"], "rspec-*.gemspec").and_return(gems)
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0")
+ else # >= rubygems 1.8 behavior
allow(@gem_env).to receive(:gem_specification).and_return(Gem::Specification)
expect(@gem_env.gem_specification).to receive(:find_all_by_name).with(rspec_dep.name, rspec_dep.requirement).and_return(gems)
- else
- allow(@gem_env).to receive(:gem_source_index).and_return(Gem.source_index)
- expect(@gem_env.gem_source_index).to receive(:search).with(rspec_dep).and_return(gems)
end
expect(@gem_env.installed_versions(Gem::Dependency.new("rspec", nil))).to eq(gems)
end
@@ -450,10 +470,10 @@ describe Chef::Provider::Package::Rubygems do
end
end
- context "when in omnibus chefdk" do
- let(:bindir) { "/opt/chefdk/embedded/bin" }
+ context "when in omnibus chef-workstation" do
+ let(:bindir) { "/opt/chef-workstation/embedded/bin" }
- it "recognizes chefdk as omnibus" do
+ it "recognizes chef-workstation as omnibus" do
expect(provider.is_omnibus?).to be true
end
end
diff --git a/spec/unit/provider/package/yum/python_helper_spec.rb b/spec/unit/provider/package/yum/python_helper_spec.rb
index 39c067d135..b8336a500b 100644
--- a/spec/unit/provider/package/yum/python_helper_spec.rb
+++ b/spec/unit/provider/package/yum/python_helper_spec.rb
@@ -21,6 +21,7 @@ require "spec_helper"
describe Chef::Provider::Package::Yum::PythonHelper do
let(:helper) { Chef::Provider::Package::Yum::PythonHelper.instance }
+ before(:each) { Singleton.__init__(Chef::Provider::Package::Yum::PythonHelper) }
it "propagates stacktraces on stderr from the forked subprocess", :rhel do
allow(helper).to receive(:yum_command).and_return("ruby -e 'raise \"your hands in the air\"'")
diff --git a/spec/unit/provider/service/arch_service_spec.rb b/spec/unit/provider/service/arch_service_spec.rb
index 026db3dc75..c92af83de5 100644
--- a/spec/unit/provider/service/arch_service_spec.rb
+++ b/spec/unit/provider/service/arch_service_spec.rb
@@ -44,6 +44,7 @@ describe Chef::Provider::Service::Arch, "load_current_resource" do
describe "when first created" do
it "should set the current resources service name to the new resources service name" do
+ allow(@provider).to receive(:determine_current_status!)
allow(@provider).to receive(:shell_out).and_return(OpenStruct.new(exitstatus: 0, stdout: ""))
@provider.load_current_resource
expect(@provider.current_resource.service_name).to eq("chef")
diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb
index d0cd048c4f..0ae1d28cd3 100644
--- a/spec/unit/provider/service/debian_service_spec.rb
+++ b/spec/unit/provider/service/debian_service_spec.rb
@@ -33,6 +33,7 @@ describe Chef::Provider::Service::Debian do
@pid, @stdin, @stdout, @stderr = nil, nil, nil, nil
allow(File).to receive(:exist?).with("/etc/init.d/chef").and_return true
+ allow(@provider).to receive(:determine_current_status!)
end
let(:init_lines) do
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
index 6cc0f88725..eafc857cf1 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -78,7 +78,7 @@ describe Chef::Provider::Service::Macosx do
@getpwuid = double("Etc::Passwd", { name: "mikedodge04" })
allow(Etc).to receive(:getpwuid).and_return(@getpwuid)
allow(node).to receive(:[]).with("platform_version").and_return("10.11.1")
- cmd = "launchctl list #{service_label}"
+ cmd = "/bin/launchctl list #{service_label}"
allow(provider).to receive(:shell_out)
.with(/(#{su_cmd} '#{cmd}'|#{cmd})/, default_env: false)
.and_return(double("Status",
@@ -259,13 +259,13 @@ describe Chef::Provider::Service::Macosx do
it "shows warning message if service is already running" do
allow(current_resource).to receive(:running).and_return(true)
- expect(logger).to receive(:trace).with("macosx_service[#{service_name}] already running, not starting")
+ expect(logger).to receive(:debug).with("macosx_service[#{service_name}] already running, not starting")
provider.start_service
end
it "starts service via launchctl if service found" do
- cmd = "launchctl load -w " + session + plist
+ cmd = "/bin/launchctl load -w " + session + plist
expect(provider).to receive(:shell_out)
.with(/(#{su_cmd} .#{cmd}.|#{cmd})/, default_env: false)
.and_return(0)
@@ -291,13 +291,13 @@ describe Chef::Provider::Service::Macosx do
it "shows warning message if service is not running" do
allow(current_resource).to receive(:running).and_return(false)
- expect(logger).to receive(:trace).with("macosx_service[#{service_name}] not running, not stopping")
+ expect(logger).to receive(:debug).with("macosx_service[#{service_name}] not running, not stopping")
provider.stop_service
end
it "stops the service via launchctl if service found" do
- cmd = "launchctl unload -w " + plist
+ cmd = "/bin/launchctl unload -w " + plist
expect(provider).to receive(:shell_out)
.with(/(#{su_cmd} .#{cmd}.|#{cmd})/, default_env: false)
.and_return(0)
diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb
index 10f59ed025..4f38ed5465 100644
--- a/spec/unit/provider/service/systemd_service_spec.rb
+++ b/spec/unit/provider/service/systemd_service_spec.rb
@@ -297,6 +297,55 @@ describe Chef::Provider::Service::Systemd do
end
end
+ enabled_and_active = <<-STDOUT
+ ActiveState=active
+ UnitFileState=enabled
+ STDOUT
+ disabled_and_inactive = <<-STDOUT
+ ActiveState=disabled
+ UnitFileState=inactive
+ STDOUT
+ # No unit known for this service, and inactive
+ nil_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=
+ STDOUT
+
+ def with_systemctl_show(systemctl_path, stdout)
+ systemctl_show = [systemctl_path, "--system", "show", "-p", "UnitFileState", "-p", "ActiveState", service_name]
+ expect(provider).to receive(:shell_out!).with(*systemctl_show).and_return(double(stdout: stdout, exitstatus: 0, error?: false))
+ end
+
+ describe "systemd_service_status" do
+ before(:each) do
+ provider.current_resource = current_resource
+ current_resource.service_name(service_name)
+ end
+
+ it "should return status if '#{systemctl_path} --system show -p UnitFileState -p ActiveState service_name' returns 0 and has nil" do
+ nil_and_inactive_h = {
+ "ActiveState" => "inactive",
+ "UnitFileState" => nil,
+ }
+ with_systemctl_show(systemctl_path, nil_and_inactive)
+ expect(provider.systemd_service_status).to eql(nil_and_inactive_h)
+ end
+
+ it "should error if '#{systemctl_path} --system show -p UnitFileState -p ActiveState service_name' misses fields" do
+ partial_systemctl_stdout = <<-STDOUT
+ ActiveState=inactive
+ STDOUT
+ with_systemctl_show(systemctl_path, partial_systemctl_stdout)
+ expect { provider.systemd_service_status }.to raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should error if '#{systemctl_path} --system show -p UnitFileState -p ActiveState service_name' returns non 0" do
+ systemctl_show = [systemctl_path, "--system", "show", "-p", "UnitFileState", "-p", "ActiveState", service_name]
+ allow(provider).to receive(:shell_out!).with(*systemctl_show).and_return(shell_out_failure)
+ expect { provider.systemd_service_status }.to raise_error(Chef::Exceptions::Service)
+ end
+ end
+
describe "is_active?" do
before(:each) do
provider.current_resource = current_resource
@@ -304,13 +353,22 @@ describe Chef::Provider::Service::Systemd do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
- it "should return true if '#{systemctl_path} --system is-active service_name' returns 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-active", service_name, "--quiet", timeout: 900).and_return(shell_out_success)
+ it "should return true if service is active" do
+ with_systemctl_show(systemctl_path, enabled_and_active)
expect(provider.is_active?).to be true
end
- it "should return false if '#{systemctl_path} --system is-active service_name' returns anything except 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-active", service_name, "--quiet", timeout: 900).and_return(shell_out_failure)
+ it "should return false if service is not active" do
+ with_systemctl_show(systemctl_path, disabled_and_inactive)
+ expect(provider.is_active?).to be false
+ end
+
+ it "should return false if service is activating" do
+ enabled_and_activating = <<-STDOUT
+ ActiveState=activating
+ UnitFileState=enabled
+ STDOUT
+ with_systemctl_show(systemctl_path, enabled_and_activating)
expect(provider.is_active?).to be false
end
end
@@ -322,15 +380,60 @@ describe Chef::Provider::Service::Systemd do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
- it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, "--quiet", timeout: 900).and_return(shell_out_success)
+ it "should return true if service is enabled" do
+ with_systemctl_show(systemctl_path, enabled_and_active)
expect(provider.is_enabled?).to be true
end
- it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, "--quiet", timeout: 900).and_return(shell_out_failure)
+ it "should return false if service is disabled" do
+ with_systemctl_show(systemctl_path, disabled_and_inactive)
expect(provider.is_enabled?).to be false
end
+
+ it "should return false if service has no unit file" do
+ with_systemctl_show(systemctl_path, nil_and_inactive)
+ expect(provider.is_enabled?).to be false
+ end
+
+ it "should return true if service is static" do
+ static_and_active = <<-STDOUT
+ ActiveState=active
+ UnitFileState=static
+ STDOUT
+ with_systemctl_show(systemctl_path, static_and_active)
+ expect(provider.is_enabled?).to be true
+ end
+
+ it "should return false if service is enabled-runtime" do
+ enabled_runtime_and_active = <<-STDOUT
+ ActiveState=active
+ UnitFileState=enabled-runtime
+ STDOUT
+ with_systemctl_show(systemctl_path, enabled_runtime_and_active)
+ expect(provider.is_enabled?).to be false
+ end
+
+ it "should shellout to 'is-enabled' and return false if unit file is bad and sysv compat isn't enabled" do
+ bad_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=bad
+ STDOUT
+ with_systemctl_show(systemctl_path, bad_and_inactive)
+ systemctl_isenabled = [systemctl_path, "--system", "is-enabled", service_name, "--quiet"]
+ expect(provider).to receive(:shell_out).with(*systemctl_isenabled).and_return(shell_out_failure)
+ expect(provider.is_enabled?).to be false
+ end
+
+ it "should shellout to 'is-enabled' and return true if unit file is bad and sysv compat is enabled" do
+ bad_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=bad
+ STDOUT
+ with_systemctl_show(systemctl_path, bad_and_inactive)
+ systemctl_isenabled = [systemctl_path, "--system", "is-enabled", service_name, "--quiet"]
+ expect(provider).to receive(:shell_out).with(*systemctl_isenabled).and_return(shell_out_success)
+ expect(provider.is_enabled?).to be true
+ end
end
describe "is_masked?" do
@@ -340,23 +443,31 @@ describe Chef::Provider::Service::Systemd do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
- it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 'masked' and returns anything except 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "masked", exitstatus: shell_out_failure))
+ it "should return true if service is masked" do
+ masked_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=masked
+ STDOUT
+ with_systemctl_show(systemctl_path, masked_and_inactive)
expect(provider.is_masked?).to be true
end
- it "should return true if '#{systemctl_path} --system is-enabled service_name' outputs 'masked-runtime' and returns anything except 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "masked-runtime", exitstatus: shell_out_failure))
- expect(provider.is_masked?).to be true
+ it "should return false if service is masked-runtime" do
+ masked_runtime_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=masked-runtime
+ STDOUT
+ with_systemctl_show(systemctl_path, masked_runtime_and_inactive)
+ expect(provider.is_masked?).to be false
end
- it "should return false if '#{systemctl_path} --system is-enabled service_name' returns 0" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "enabled", exitstatus: shell_out_success))
+ it "should return false if service is enabled" do
+ with_systemctl_show(systemctl_path, enabled_and_active)
expect(provider.is_masked?).to be false
end
- it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0 and outputs an error'" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "Failed to get unit file state for #{service_name}: No such file or directory", exitstatus: shell_out_failure))
+ it "should return false if service has no known unit file" do
+ with_systemctl_show(systemctl_path, nil_and_inactive)
expect(provider.is_masked?).to be false
end
end
@@ -368,18 +479,22 @@ describe Chef::Provider::Service::Systemd do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
- it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 'indirect'" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "indirect", exitstatus: shell_out_success))
+ it "should return true if service is indirect" do
+ indirect_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=indirect
+ STDOUT
+ with_systemctl_show(systemctl_path, indirect_and_inactive)
expect(provider.is_indirect?).to be true
end
- it "should return false if '#{systemctl_path} --system is-enabled service_name' returns 0 and outputs something other than 'indirect'" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "enabled", exitstatus: shell_out_success))
+ it "should return false if service not indirect" do
+ with_systemctl_show(systemctl_path, enabled_and_active)
expect(provider.is_indirect?).to be false
end
- it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0 and outputs somethign other than 'indirect''" do
- expect(provider).to receive(:shell_out_compacted).with(systemctl_path, "--system", "is-enabled", service_name, timeout: 900).and_return(double(stdout: "enabled", exitstatus: shell_out_failure))
+ it "should return false if service has no known unit file" do
+ with_systemctl_show(systemctl_path, nil_and_inactive)
expect(provider.is_indirect?).to be false
end
end
diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb
index 20cbef11ce..924ac58551 100644
--- a/spec/unit/provider/service/upstart_service_spec.rb
+++ b/spec/unit/provider/service/upstart_service_spec.rb
@@ -27,7 +27,6 @@ describe Chef::Provider::Service::Upstart do
@node = Chef::Node.new
@node.name("upstarter")
@node.automatic_attrs[:platform] = "ubuntu"
- @node.automatic_attrs[:platform_version] = "9.10"
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
@@ -36,34 +35,6 @@ describe Chef::Provider::Service::Upstart do
@provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
end
- describe "when first created" do
- before do
- @platform = nil
- end
-
- it "should return /etc/event.d as the upstart job directory when running on Ubuntu 9.04" do
- @node.automatic_attrs[:platform_version] = "9.04"
- # Chef::Platform.stub(:find_platform_and_version).and_return([ "ubuntu", "9.04" ])
- @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
- expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/event.d")
- expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq("")
- end
-
- it "should return /etc/init as the upstart job directory when running on Ubuntu 9.10" do
- @node.automatic_attrs[:platform_version] = "9.10"
- @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
- expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/init")
- expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq(".conf")
- end
-
- it "should return /etc/init as the upstart job directory by default" do
- @node.automatic_attrs[:platform_version] = "9000"
- @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
- expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/init")
- expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq(".conf")
- end
- end
-
describe "load_current_resource" do
before(:each) do
@node.automatic_attrs[:command] = { ps: "ps -ax" }
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
index b3a85715a8..08d4b2a620 100644
--- a/spec/unit/provider/service/windows_spec.rb
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -254,7 +254,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
end
it "logs debug message" do
- expect(logger).to receive(:trace).with("windows_service[#{chef_service_name}] already exists - nothing to do")
+ expect(logger).to receive(:debug).with("windows_service[#{chef_service_name}] already exists - nothing to do")
provider.action_create
end
@@ -334,7 +334,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
end
it "logs debug message" do
- expect(logger).to receive(:trace).with("windows_service[#{chef_service_name}] does not exist - nothing to do")
+ expect(logger).to receive(:debug).with("windows_service[#{chef_service_name}] does not exist - nothing to do")
provider.action_delete
end
diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb
index f249f564b1..0ce9a19ec2 100644
--- a/spec/unit/provider/subversion_spec.rb
+++ b/spec/unit/provider/subversion_spec.rb
@@ -319,11 +319,11 @@ describe Chef::Provider::Subversion do
let(:http_proxy_uri) { "http://somehost:1" }
let(:http_no_proxy) { "svn.example.org" }
- before (:all) do
+ before(:all) do
@original_env = ENV.to_hash
end
- after (:all) do
+ after(:all) do
ENV.clear
ENV.update(@original_env)
end
diff --git a/spec/unit/provider/systemd_unit_spec.rb b/spec/unit/provider/systemd_unit_spec.rb
index e1170b4fc5..85ab87e722 100644
--- a/spec/unit/provider/systemd_unit_spec.rb
+++ b/spec/unit/provider/systemd_unit_spec.rb
@@ -18,7 +18,7 @@
require "spec_helper"
-describe Chef::Provider::SystemdUnit do
+describe Chef::Provider::SystemdUnit, :linux_only do
let(:node) { Chef::Node.new }
let(:events) { Chef::EventDispatch::Dispatcher.new }
@@ -117,6 +117,7 @@ describe Chef::Provider::SystemdUnit do
allow(provider).to receive(:enabled?).and_return(false)
allow(provider).to receive(:masked?).and_return(false)
allow(provider).to receive(:static?).and_return(false)
+ allow(provider).to receive(:indirect?).and_return(false)
end
it "should create a current resource with the name of the new resource" do
@@ -811,6 +812,49 @@ describe Chef::Provider::SystemdUnit do
end
end
+ def with_systemctl_show(systemctl_path, instance, opts, stdout)
+ systemctl_show = [systemctl_path, instance, "show", "-p", "UnitFileState", "-p", "ActiveState", unit_name]
+ expect(provider).to receive(:shell_out).with(*systemctl_show, **opts).and_return(double(stdout: stdout, exitstatus: 0, error?: false))
+ end
+
+ describe "systemd_unit_status" do
+ before(:each) do
+ provider.current_resource = current_resource
+ current_resource.unit_name(unit_name)
+ end
+
+ it "should return status if '#{systemctl_path} --system show -p UnitFileState -p ActiveState unit_name' returns 0 and has nil" do
+ # No unit known for this service, and inactive
+ nil_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=
+ STDOUT
+ nil_and_inactive_h = {
+ "ActiveState" => "inactive",
+ "UnitFileState" => nil,
+ }
+ with_systemctl_show(systemctl_path, "--system", {}, nil_and_inactive)
+ expect(provider.systemd_unit_status).to eql(nil_and_inactive_h)
+ end
+
+ it "should not error if '#{systemctl_path} --system show' is run against a template unit" do
+ current_resource.unit_name("foo@.service")
+ template_error = "Failed to get properties: Unit name foo@.service is neither a valid invocation ID nor unit name."
+ systemctl_show = [systemctl_path, "--system", "show", "-p", "UnitFileState", "-p", "ActiveState", "foo@.service"]
+ expect(provider).to receive(:shell_out).with(*systemctl_show).and_return(double(stdout: "", stderr: template_error, exitstatus: 1, error?: true))
+ expect(provider.systemd_unit_status).to eql({})
+ end
+ end
+
+ enabled_and_active = <<-STDOUT
+ ActiveState=active
+ UnitFileState=enabled
+ STDOUT
+ disabled_and_inactive = <<-STDOUT
+ ActiveState=disabled
+ UnitFileState=inactive
+ STDOUT
+
describe "#active?" do
before(:each) do
provider.current_resource = current_resource
@@ -820,33 +864,25 @@ describe Chef::Provider::SystemdUnit do
context "when a user is specified" do
it "returns true when unit is active" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-active", unit_name, user_cmd_opts)
- .and_return(shell_out_success)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, enabled_and_active)
expect(provider.active?).to be true
end
it "returns false when unit is inactive" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-active", unit_name, user_cmd_opts)
- .and_return(shell_out_failure)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, disabled_and_inactive)
expect(provider.active?).to be false
end
end
context "when no user is specified" do
it "returns true when unit is active" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-active", unit_name)
- .and_return(shell_out_success)
+ with_systemctl_show(systemctl_path, "--system", {}, enabled_and_active)
expect(provider.active?).to be true
end
it "returns false when unit is not active" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-active", unit_name)
- .and_return(shell_out_failure)
+ with_systemctl_show(systemctl_path, "--system", {}, disabled_and_inactive)
expect(provider.active?).to be false
end
end
@@ -861,33 +897,25 @@ describe Chef::Provider::SystemdUnit do
context "when a user is specified" do
it "returns true when unit is enabled" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_success)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, enabled_and_active)
expect(provider.enabled?).to be true
end
it "returns false when unit is not enabled" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_disabled)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, disabled_and_inactive)
expect(provider.enabled?).to be false
end
end
context "when no user is specified" do
it "returns true when unit is enabled" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_success)
+ with_systemctl_show(systemctl_path, "--system", {}, enabled_and_active)
expect(provider.enabled?).to be true
end
it "returns false when unit is not enabled" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_disabled)
+ with_systemctl_show(systemctl_path, "--system", {}, disabled_and_inactive)
expect(provider.enabled?).to be false
end
end
@@ -899,36 +927,33 @@ describe Chef::Provider::SystemdUnit do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
+ masked_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=masked
+ STDOUT
+
context "when a user is specified" do
it "returns true when the unit is masked" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "status", unit_name, user_cmd_opts)
- .and_return(shell_out_masked)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, masked_and_inactive)
expect(provider.masked?).to be true
end
it "returns false when the unit is not masked" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "status", unit_name, user_cmd_opts)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, enabled_and_active)
expect(provider.masked?).to be false
end
end
context "when no user is specified" do
it "returns true when the unit is masked" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "status", unit_name)
- .and_return(shell_out_masked)
+ with_systemctl_show(systemctl_path, "--system", {}, masked_and_inactive)
expect(provider.masked?).to be true
end
it "returns false when the unit is not masked" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "status", unit_name)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--system", {}, enabled_and_active)
expect(provider.masked?).to be false
end
end
@@ -940,36 +965,33 @@ describe Chef::Provider::SystemdUnit do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
+ static_and_active = <<-STDOUT
+ ActiveState=active
+ UnitFileState=static
+ STDOUT
+
context "when a user is specified" do
it "returns true when the unit is static" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, static_and_active)
expect(provider.static?).to be true
end
it "returns false when the unit is not static" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_masked)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, enabled_and_active)
expect(provider.static?).to be false
end
end
context "when no user is specified" do
it "returns true when the unit is static" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--system", {}, static_and_active)
expect(provider.static?).to be true
end
it "returns false when the unit is not static" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_masked)
+ with_systemctl_show(systemctl_path, "--system", {}, enabled_and_active)
expect(provider.static?).to be false
end
end
@@ -981,36 +1003,33 @@ describe Chef::Provider::SystemdUnit do
allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path.to_s)
end
+ indirect_and_inactive = <<-STDOUT
+ ActiveState=inactive
+ UnitFileState=indirect
+ STDOUT
+
context "when a user is specified" do
it "returns true when the unit is indirect" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_indirect)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, indirect_and_inactive)
expect(provider.indirect?).to be true
end
it "returns false when the unit is not indirect" do
current_resource.user(user_name)
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--user", "is-enabled", unit_name, user_cmd_opts)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--user", user_cmd_opts, enabled_and_active)
expect(provider.indirect?).to be false
end
end
context "when no user is specified" do
it "returns true when the unit is indirect" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_indirect)
+ with_systemctl_show(systemctl_path, "--system", {}, indirect_and_inactive)
expect(provider.indirect?).to be true
end
it "returns false when the unit is not indirect" do
- expect(provider).to receive(:shell_out_compacted)
- .with(systemctl_path, "--system", "is-enabled", unit_name)
- .and_return(shell_out_static)
+ with_systemctl_show(systemctl_path, "--system", {}, enabled_and_active)
expect(provider.indirect?).to be false
end
end
diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
deleted file mode 100644
index 5652ae6868..0000000000
--- a/spec/unit/provider/user/dscl_spec.rb
+++ /dev/null
@@ -1,699 +0,0 @@
-#
-# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-require "ostruct"
-
-describe Chef::Provider::User::Dscl do
- before do
- allow(ChefUtils).to receive(:windows?) { false }
- end
-
- let(:shellcmdresult) { Struct.new(:stdout, :stderr, :exitstatus) }
-
- let(:password) { nil }
- let(:salt) { nil }
- let(:iterations) { nil }
-
- let(:events) { Chef::EventDispatch::Dispatcher.new }
-
- let(:node) do
- Chef::Node.new.tap do |node|
- node.automatic["os"] = "darwin"
- node.automatic["platform_version"] = "10.13.0"
- end
- end
-
- let(:run_context) { Chef::RunContext.new(node, {}, events) }
-
- let(:new_resource) do
- r = Chef::Resource::User::DsclUser.new("toor", run_context)
- r.password(password)
- r.salt(salt)
- r.iterations(iterations)
- r
- end
-
- let(:provider) do
- Chef::Provider::User::Dscl.new(new_resource, run_context)
- end
-
- let(:salted_sha512_password) do
- "0f543f021c63255e64e121a3585601b8ecfedf6d2\
-705ddac69e682a33db5dbcdb9b56a2520bc8fff63a\
-2ba6b7984c0737ff0b7949455071581f7affcd536d\
-402b6cdb097"
- end
-
- let(:salted_sha512_pbkdf2_password) do
- "c734b6e4787c3727bb35e29fdd92b97c\
-1de12df509577a045728255ec7c6c5f5\
-c18efa05ed02b682ffa7ebc05119900e\
-b1d4880833aa7a190afc13e2bf0936b8\
-20123e8c98f0f9bcac2a629d9163caac\
-9464a8c234f3919082400b4f939bb77b\
-c5adbbac718b7eb99463a7b679571e0f\
-1c9fef2ef08d0b9e9c2bcf644eed2ffc"
- end
-
- let(:salted_sha512_pbkdf2_salt) do
- "2d942d8364a9ccf2b8e5cb7ed1ff58f78\
-e29dbfee7f9db58859144d061fd0058"
- end
-
- let(:salted_sha512_pbkdf2_iterations) do
- 25000
- end
-
- let(:vagrant_sha_512) do
- "6f75d7190441facc34291ebbea1fc756b242d4f\
-e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
-ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30"
- end
-
- let(:vagrant_sha_512_pbkdf2) do
- "12601a90db17cbf\
-8ba4808e6382fb0d3b9d8a6c1a190477bf680ab21afb\
-6065467136e55cc208a6f74156e3daf20fb13369ef4b\
-7bafa047d80359fb46a48a4adccd548ebb33851b093\
-47cca84341a7f93a27147343f89fb843fb46c0017d2\
-64afa4976baacf941b915bd1ec1ca24c30b3e759e02\
-403e02f59fe7ff5938a7636c"
- end
-
- let(:vagrant_sha_512_pbkdf2_salt) do
- "ee954be472fdc60ddf89484781433993625f006af6ec810c08f49a7e413946a1"
- end
-
- let(:vagrant_sha_512_pbkdf2_iterations) do
- 34482
- end
-
- describe "when shelling out to dscl" do
- it "should run dscl with the supplied cmd /Path args" do
- shell_return = shellcmdresult.new("stdout", "err", 0)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect(provider.run_dscl("cmd", "/Path", "args")).to eq("stdout")
- end
-
- it "returns an empty string from delete commands" do
- shell_return = shellcmdresult.new("out", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-delete", "/Path", "args").and_return(shell_return)
- expect(provider.run_dscl("delete", "/Path", "args")).to eq("")
- end
-
- it "should raise an exception for any other command" do
- shell_return = shellcmdresult.new("out", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "arguments").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "arguments") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
-
- it "raises an exception when dscl reports 'no such key'" do
- shell_return = shellcmdresult.new("No such key: ", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "args") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
-
- it "raises an exception when dscl reports 'eDSRecordNotFound'" do
- shell_return = shellcmdresult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", "err", -14136)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "args") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
- end
-
- describe "get_free_uid" do
- before do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n")
- end
-
- describe "when the system property is set to true" do
- before do
- new_resource.system(true)
- end
-
- it "should return the first unused uid number on or above 200" do
- expect(provider.get_free_uid).to eq(202)
- end
- end
-
- it "should return the first unused uid number on or above 500" do
- expect(provider.get_free_uid).to eq(502)
- end
-
- it "should raise an exception when the search limit is exhausted" do
- search_limit = 1
- expect { provider.get_free_uid(search_limit) }.to raise_error(RuntimeError)
- end
- end
-
- describe "uid_used?" do
- it "should return false if not given any valid uid number" do
- expect(provider.uid_used?(nil)).to be_falsey
- end
-
- describe "when called with a user id" do
- before do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("\naj 500\n")
- end
-
- it "should return true for a used uid number" do
- expect(provider.uid_used?(500)).to be_truthy
- end
-
- it "should return false for an unused uid number" do
- expect(provider.uid_used?(501)).to be_falsey
- end
- end
- end
-
- describe "when determining the uid to set" do
- it "raises RequestedUIDUnavailable if the requested uid is already in use" do
- allow(provider).to receive(:uid_used?).and_return(true)
- expect(provider).to receive(:get_free_uid).and_return(501)
- expect { provider.dscl_set_uid }.to raise_error(Chef::Exceptions::RequestedUIDUnavailable)
- end
-
- it "finds a valid, unused uid when none is specified" do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("")
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UniqueID", 501)
- expect(provider).to receive(:get_free_uid).and_return(501)
- provider.dscl_set_uid
- expect(new_resource.uid).to eq(501)
- end
-
- it "sets the uid specified in the resource" do
- new_resource.uid(1000)
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UniqueID", 1000).and_return(true)
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("")
- provider.dscl_set_uid
- end
- end
-
- describe "current_home_exists?" do
- let(:current_resource) do
- new_resource.dup
- end
-
- before do
- provider.current_resource = current_resource
- end
-
- it "returns false for nil home dir" do
- current_resource.home nil
- expect(provider.current_home_exists?).to be_falsey
- end
-
- it "is false for empty string" do
- current_resource.home ""
- expect(provider.current_home_exists?).to be_falsey
- end
-
- it "is true for existing directory" do
- current_resource.home "/Users/blah"
- allow(::File).to receive(:exist?).with("/Users/blah").and_return(true)
- expect(provider.current_home_exists?).to be_truthy
- end
- end
-
- describe "when modifying the home directory" do
- let(:current_resource) do
- new_resource.dup
- end
-
- before do
- new_resource.manage_home true
- new_resource.home("/Users/toor")
-
- provider.current_resource = current_resource
- end
-
- it "deletes the home directory when resource#home is nil" do
- new_resource.instance_variable_set(:@home, nil)
- expect(provider).to receive(:run_dscl).with("delete", "/Users/toor", "NFSHomeDirectory").and_return(true)
- provider.dscl_set_home
- end
-
- it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
- new_resource.home("epic-fail")
- expect { provider.dscl_set_home }.to raise_error(Chef::Exceptions::InvalidHomeDirectory)
- end
-
- it "moves the users home to the new location if it exists and the target location is different" do
- new_resource.manage_home true
-
- current_home = CHEF_SPEC_DATA + "/old_home_dir"
- current_home_files = [current_home + "/my-dot-emacs", current_home + "/my-dot-vim"]
- current_resource.home(current_home)
- new_resource.gid(23)
- allow(::File).to receive(:exist?).with("/old/home/toor").and_return(true)
- allow(::File).to receive(:exist?).with("/Users/toor").and_return(true)
- allow(::File).to receive(:exist?).with(current_home).and_return(true)
-
- expect(FileUtils).to receive(:mkdir_p).with("/Users/toor").and_return(true)
- expect(FileUtils).to receive(:rmdir).with(current_home)
- expect(::Dir).to receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*", ::File::FNM_DOTMATCH).and_return(current_home_files)
- expect(FileUtils).to receive(:mv).with(current_home_files, "/Users/toor", force: true)
- expect(FileUtils).to receive(:chown_R).with("toor", "23", "/Users/toor")
-
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "NFSHomeDirectory", "/Users/toor")
- provider.dscl_set_home
- end
-
- it "should run createhomedir to create the user's new home folder" do
- expect(provider).to receive(:shell_out_compacted!).with("/usr/sbin/createhomedir", "-c", "-u", "toor")
- provider.ditto_home
- end
-
- it "creates the user's NFSHomeDirectory and home directory" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "NFSHomeDirectory", "/Users/toor").and_return(true)
- expect(provider).to receive(:ditto_home)
- provider.dscl_set_home
- end
- end
-
- describe "resource_requirements" do
- let(:dscl_exists) { true }
- let(:plutil_exists) { true }
-
- before do
- allow(::File).to receive(:exist?).with("/usr/bin/dscl").and_return(dscl_exists)
- allow(::File).to receive(:exist?).with("/usr/bin/plutil").and_return(plutil_exists)
- end
-
- def run_requirements
- provider.define_resource_requirements
- provider.action = :create
- provider.process_resource_requirements
- end
-
- describe "when dscl doesn't exist" do
- let(:dscl_exists) { false }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when plutil doesn't exist" do
- let(:plutil_exists) { false }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when password is SALTED-SHA512" do
- let(:password) { salted_sha512_password }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when password is SALTED-SHA512-PBKDF2" do
- let(:password) { salted_sha512_pbkdf2_password }
-
- describe "when salt and iteration is not set" do
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when salt and iteration is set" do
- let(:salt) { salted_sha512_pbkdf2_salt }
- let(:iterations) { salted_sha512_pbkdf2_iterations }
-
- it "should not raise an error" do
- expect { run_requirements }.not_to raise_error
- end
- end
- end
- end
-
- describe "load_current_resource" do
- # set this to any of the user plist files under spec/data
- let(:user_plist_file) { nil }
-
- before do
- expect(provider).to receive(:shell_out_compacted).with("dscacheutil", "-flushcache")
- expect(provider).to receive(:shell_out_compacted).with("plutil", "-convert", "xml1", "-o", "-", "/var/db/dslocal/nodes/Default/users/toor.plist") do
- if user_plist_file.nil?
- shellcmdresult.new("Can not find the file", "Sorry!!", 1)
- else
- shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0)
- end
- end
-
- unless user_plist_file.nil?
- expect(provider).to receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml")))
- end
- end
-
- describe "when user is not there" do
- it "shouldn't raise an error" do
- expect { provider.load_current_resource }.not_to raise_error
- end
-
- it "should set @user_exists" do
- provider.load_current_resource
- expect(provider.instance_variable_get(:@user_exists)).to be_falsey
- end
-
- it "should set username" do
- provider.load_current_resource
- expect(provider.current_resource.username).to eq("toor")
- end
- end
-
- describe "when user is there" do
- let(:password) { "something" } # Load password during load_current_resource
-
- let(:user_plist_file) { "10.9" }
-
- it "collects the user data correctly" do
- provider.load_current_resource
- expect(provider.current_resource.comment).to eq("vagrant")
- expect(provider.current_resource.uid).to eq("501")
- expect(provider.current_resource.gid).to eq("80")
- expect(provider.current_resource.home).to eq("/Users/vagrant")
- expect(provider.current_resource.shell).to eq("/bin/bash")
- expect(provider.current_resource.password).to eq(vagrant_sha_512_pbkdf2)
- expect(provider.current_resource.salt).to eq(vagrant_sha_512_pbkdf2_salt)
- expect(provider.current_resource.iterations).to eq(vagrant_sha_512_pbkdf2_iterations)
- end
-
- describe "when a plain password is set that is same" do
- let(:password) { "vagrant" }
-
- it "diverged_password? should report false" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_falsey
- end
- end
-
- describe "when a plain password is set that is different" do
- let(:password) { "not_vagrant" }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when iterations change" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
- let(:iterations) { 12345 }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when shadow hash changes" do
- let(:password) { salted_sha512_pbkdf2_password }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when salt change" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:salt) { salted_sha512_pbkdf2_salt }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when salt isn't found" do
- it "diverged_password? should report true" do
- provider.load_current_resource
- provider.current_resource.salt(nil)
- expect(provider.diverged_password?).to be_truthy
- end
- end
- end
- end
-
- describe "salted_sha512_pbkdf2?" do
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy
- end
-
- it "should return false otherwise" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_password)).to be_falsey
- expect(provider.salted_sha512_pbkdf2?("any other string")).to be_falsey
- end
- end
-
- describe "salted_sha512?" do
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy
- end
-
- it "should return false otherwise" do
- expect(provider.salted_sha512?(salted_sha512_pbkdf2_password)).to be_falsey
- expect(provider.salted_sha512?("any other string")).to be_falsey
- end
- end
-
- describe "prepare_password_shadow_info" do
- describe "when the password is plain text" do
- let(:password) { "vagrant" }
-
- it "password_shadow_info should have salted-sha-512 format" do
- shadow_info = provider.prepare_password_shadow_info
- expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations")
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first
- expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy
- end
- end
-
- describe "when the password is salted-sha-512" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
-
- it "password_shadow_info should have salted-sha-512 format" do
- shadow_info = provider.prepare_password_shadow_info
- expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations")
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first
- expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy
- expect(info).to eq(vagrant_sha_512_pbkdf2)
- end
- end
- end
-
- describe "set_password" do
- before do
- new_resource.password("something")
- end
-
- it "should sleep and flush the dscl cache before saving the password" do
- expect(provider).to receive(:prepare_password_shadow_info).and_return({})
- mock_shellout = double("Mock::Shellout")
- allow(mock_shellout).to receive(:run_command)
- expect(provider).to receive(:shell_out_compacted).and_return(mock_shellout)
- expect(provider).to receive(:read_user_info)
- expect(provider).to receive(:dscl_set)
- expect(provider).to receive(:sleep).with(3)
- expect(provider).to receive(:save_user_info)
- provider.set_password
- end
- end
-
- describe "when the user does not yet exist and chef is creating it" do
- context "with a numeric gid" do
- before do
- new_resource.comment "#mockssuck"
- new_resource.gid 1001
- end
-
- it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
- expect(provider).to receive :dscl_create_user
- expect(provider).to receive :dscl_create_comment
- expect(provider).to receive :dscl_set_uid
- expect(provider).to receive :dscl_set_gid
- expect(provider).to receive :dscl_set_home
- expect(provider).to receive :dscl_set_shell
- expect(provider).to receive :set_password
- provider.create_user
- end
-
- it "creates the user and sets the comment field" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor").and_return(true)
- provider.dscl_create_user
- end
-
- it "sets the comment field" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "RealName", "#mockssuck").and_return(true)
- provider.dscl_create_comment
- end
-
- it "sets the comment field to username" do
- new_resource.comment nil
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "RealName", "toor").and_return(true)
- provider.dscl_create_comment
- end
-
- it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", 1001).and_return(true)
- provider.dscl_set_gid
- end
-
- it "should run run_dscl with create /Users/user UserShell to set the users login shell" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UserShell", "/usr/bin/false").and_return(true)
- provider.dscl_set_shell
- end
- end
-
- context "with a non-numeric gid" do
- before do
- new_resource.comment "#mockssuck"
- new_resource.gid "newgroup"
- end
-
- it "should map the group name to a numeric ID when the group exists" do
- expect(provider).to receive(:run_dscl).with("read", "/Groups/newgroup", "PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", "1001").ordered.and_return(true)
- provider.dscl_set_gid
- end
-
- it "should raise an exception when the group does not exist" do
- shell_return = shellcmdresult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", "err", -14136)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-read", "/Groups/newgroup", "PrimaryGroupID").and_return(shell_return)
- expect { provider.dscl_set_gid }.to raise_error(Chef::Exceptions::GroupIDNotFound)
- end
- end
-
- it "should set group ID to 20 if it's not specified" do
- new_resource.gid nil
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", 20).ordered.and_return(true)
- provider.dscl_set_gid
- expect(new_resource.gid).to eq(20)
- end
- end
-
- describe "when the user exists and chef is managing it" do
- before do
- current_resource = new_resource.dup
- provider.current_resource = current_resource
-
- # These are all different from current_resource
- new_resource.username "mud"
- new_resource.uid 2342
- new_resource.gid 2342
- new_resource.home "/Users/death"
- new_resource.password "goaway"
- end
-
- it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
- expect(provider).to receive :dscl_create_user
- expect(provider).to receive :dscl_create_comment
- expect(provider).to receive :dscl_set_uid
- expect(provider).to receive :dscl_set_gid
- expect(provider).to receive :dscl_set_home
- expect(provider).to receive :dscl_set_shell
- expect(provider).to receive :set_password
- provider.create_user
- end
- end
-
- describe "when changing the gid" do
- before do
- current_resource = new_resource.dup
- provider.current_resource = current_resource
-
- # This is different from current_resource
- new_resource.gid 2342
- end
-
- it "sets the gid" do
- expect(provider).to receive :dscl_set_gid
- provider.manage_user
- end
- end
-
- describe "when the user exists" do
- before do
- expect(provider).to receive(:shell_out_compacted).with("dscacheutil", "-flushcache")
- expect(provider).to receive(:shell_out_compacted).with("plutil", "-convert", "xml1", "-o", "-", "/var/db/dslocal/nodes/Default/users/toor.plist") do
- shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0)
- end
- provider.load_current_resource
- end
-
- describe "when Chef is removing the user" do
- it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do
- new_resource.manage_home true
- expect(provider).to receive(:run_dscl).with("list", "/Groups").and_return("my_group\nyour_group\nreal_group\n")
- expect(provider).to receive(:run_dscl).with("read", "/Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group
- expect(provider).to receive(:run_dscl).with("read", "/Groups/your_group").and_return("GroupMembership: not_you")
- expect(provider).to receive(:run_dscl).with("read", "/Groups/real_group").and_return("GroupMembership: toor")
- expect(provider).to receive(:run_dscl).with("delete", "/Groups/real_group", "GroupMembership", "toor")
- expect(provider).to receive(:run_dscl).with("delete", "/Users/toor")
- expect(FileUtils).to receive(:rm_rf).with("/Users/vagrant")
- provider.remove_user
- end
- end
-
- describe "when user is not locked" do
- it "determines the user as not locked" do
- expect(provider).not_to be_locked
- end
- end
-
- describe "when user is locked" do
- before do
- auth_authority = provider.instance_variable_get(:@authentication_authority)
- provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;")
- end
-
- it "determines the user as not locked" do
- expect(provider).to be_locked
- end
-
- it "can unlock the user" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "AuthenticationAuthority", ";ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>")
- provider.unlock_user
- end
- end
- end
-
- describe "when locking the user" do
- it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
- expect(provider).to receive(:run_dscl).with("append", "/Users/toor", "AuthenticationAuthority", ";DisabledUser;")
- provider.lock_user
- end
- end
-
-end
diff --git a/spec/unit/provider/zypper_repository_spec.rb b/spec/unit/provider/zypper_repository_spec.rb
index f0686874e6..2cf4ed99c8 100644
--- a/spec/unit/provider/zypper_repository_spec.rb
+++ b/spec/unit/provider/zypper_repository_spec.rb
@@ -96,7 +96,7 @@ describe Chef::Provider::ZypperRepository do
it "skips key import if gpgautoimportkeys is false" do
new_resource.gpgautoimportkeys(false)
expect(provider).to receive(:declare_resource)
- expect(logger).to receive(:trace)
+ expect(logger).to receive(:debug)
provider.run_action(:create)
end
end
@@ -107,13 +107,6 @@ describe Chef::Provider::ZypperRepository do
end
end
- describe "#cookbook_name" do
- it "returns 'test' when the cookbook property is set" do
- new_resource.cookbook("test")
- expect(provider.cookbook_name).to eq("test")
- end
- end
-
describe "#key_type" do
it "returns :remote_file with an http URL" do
expect(provider.key_type("https://www.chef.io/key")).to eq(:remote_file)
@@ -167,10 +160,10 @@ describe Chef::Provider::ZypperRepository do
end
end
- describe "#install_gpg_key" do
- it "skips installing the key if a nil value for key is passed" do
- expect(logger).to receive(:trace)
- provider.install_gpg_key(nil)
+ describe "#install_gpg_keys" do
+ it "skips installing the key if an empty array for key URL is passed" do
+ expect(logger).to receive(:debug)
+ provider.install_gpg_keys([])
end
end
end
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index a8f0ede94e..28874bc0f3 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -189,12 +189,4 @@ describe Chef::Provider do
end
end
-
- context "when using use_inline_resources" do
- it "should log a deprecation warning" do
- pending Chef::VERSION.start_with?("14.1")
- expect(Chef).to receive(:deprecated).with(:use_inline_resources, kind_of(String))
- Class.new(described_class) { use_inline_resources }
- end
- end
end
diff --git a/spec/unit/resource/chef_client_cron_spec.rb b/spec/unit/resource/chef_client_cron_spec.rb
index 121758ac73..b738a20a3a 100644
--- a/spec/unit/resource/chef_client_cron_spec.rb
+++ b/spec/unit/resource/chef_client_cron_spec.rb
@@ -93,41 +93,41 @@ describe Chef::Resource::ChefClientCron do
it "creates a valid command if using all default properties" do
expect(provider.client_command).to eql(
- "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} -L /var/log/chef/client.log"
+ "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} >> /var/log/chef/client.log 2>&1"
)
end
it "uses daemon_options if set" do
resource.daemon_options ["--foo 1", "--bar 2"]
expect(provider.client_command).to eql(
- "/bin/sleep 123; /opt/chef/bin/chef-client --foo 1 --bar 2 -c #{root_path} -L /var/log/chef/client.log"
+ "/bin/sleep 123; /opt/chef/bin/chef-client --foo 1 --bar 2 -c #{root_path} >> /var/log/chef/client.log 2>&1"
)
end
it "uses custom config dir if set" do
resource.config_directory "/etc/some_other_dir"
- expect(provider.client_command).to eql("/bin/sleep 123; /opt/chef/bin/chef-client -c /etc/some_other_dir/client.rb -L /var/log/chef/client.log")
+ expect(provider.client_command).to eql("/bin/sleep 123; /opt/chef/bin/chef-client -c /etc/some_other_dir/client.rb >> /var/log/chef/client.log 2>&1")
end
it "uses custom log files / paths if set" do
resource.log_file_name "my-client.log"
resource.log_directory "/var/log/my-chef/"
expect(provider.client_command).to eql(
- "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} -L /var/log/my-chef/my-client.log"
+ "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} >> /var/log/my-chef/my-client.log 2>&1"
)
end
it "uses mailto if set" do
resource.mailto "bob@example.com"
expect(provider.client_command).to eql(
- "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} -L /var/log/chef/client.log || echo \"Chef Infra Client execution failed\""
+ "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} >> /var/log/chef/client.log 2>&1 || echo \"Chef Infra Client execution failed\""
)
end
it "uses custom chef-client binary if set" do
resource.chef_binary_path "/usr/local/bin/chef-client"
expect(provider.client_command).to eql(
- "/bin/sleep 123; /usr/local/bin/chef-client -c #{root_path} -L /var/log/chef/client.log"
+ "/bin/sleep 123; /usr/local/bin/chef-client -c #{root_path} >> /var/log/chef/client.log 2>&1"
)
end
@@ -141,7 +141,7 @@ describe Chef::Resource::ChefClientCron do
it "sets the license acceptance flag if set" do
resource.accept_chef_license true
expect(provider.client_command).to eql(
- "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} --chef-license accept -L /var/log/chef/client.log"
+ "/bin/sleep 123; /opt/chef/bin/chef-client -c #{root_path} --chef-license accept >> /var/log/chef/client.log 2>&1"
)
end
@@ -149,7 +149,7 @@ describe Chef::Resource::ChefClientCron do
allow(provider).to receive(:which).with("nice").and_return("/usr/bin/nice")
resource.nice(-15)
expect(provider.client_command).to eql(
- "/bin/sleep 123; /usr/bin/nice -n -15 /opt/chef/bin/chef-client -c #{root_path} -L /var/log/chef/client.log"
+ "/bin/sleep 123; /usr/bin/nice -n -15 /opt/chef/bin/chef-client -c #{root_path} >> /var/log/chef/client.log 2>&1"
)
end
end
diff --git a/spec/unit/resource/inspec_waiver_file_entry_spec.rb b/spec/unit/resource/inspec_waiver_file_entry_spec.rb
new file mode 100644
index 0000000000..716bf67464
--- /dev/null
+++ b/spec/unit/resource/inspec_waiver_file_entry_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Davin Taddeo (<davin@chef.io>)
+# Copyright:: Copyright (c) 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 "spec_helper"
+
+describe Chef::Resource::InspecWaiverFileEntry do
+ let(:log_str) { "this is my string to log" }
+ let(:resource) { Chef::Resource::InspecWaiverFileEntry.new("fakey_fakerton") }
+
+ it "has a name of inspec_waiver_file_entry" do
+ expect(resource.resource_name).to eq(:inspec_waiver_file_entry)
+ end
+
+ it "setting the control property to a string does not raise error" do
+ expect { resource.control "my_test_control" }.not_to raise_error
+ end
+
+ it "sets the default action as :add" do
+ expect(resource.action).to eql([:add])
+ end
+
+ it "supports :add action" do
+ expect { resource.action :add }.not_to raise_error
+ end
+
+ it "supports :remove action" do
+ expect { resource.action :remove }.not_to raise_error
+ end
+
+ it "expects expiration property to fail with date format YYYY/MM/DD" do
+ expect { resource.expiration "2022/09/23" }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "expects expiration property to fail with invalid date 2022-02-31" do
+ expect { resource.expiration "2022-02-31" }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "expects expiration property to match YYYY-MM-DD" do
+ expect { resource.expiration "2022-09-23" }.not_to raise_error
+ end
+
+ it "expects the run_test property to fail validation when not a true/false value" do
+ expect { resource.run_test "yes" }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "expects the run_test property to only accept true or false values" do
+ expect { resource.run_test true }.not_to raise_error
+ end
+
+ it "expects the justification property to accept a string value" do
+ expect { resource.justification "Because I don't want to run this compliance test" }.not_to raise_error
+ end
+
+ it "expects the justification property to fail if given a non-string value" do
+ expect { resource.justification true }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "expects the backup property to fail validation when set to true" do
+ expect { resource.backup true }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "expects the backup property to fail validation when passed a string" do
+ expect { resource.backup "please" }.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+end
diff --git a/spec/unit/resource/powershell_script_spec.rb b/spec/unit/resource/powershell_script_spec.rb
index a913583cfa..0e64a86279 100644
--- a/spec/unit/resource/powershell_script_spec.rb
+++ b/spec/unit/resource/powershell_script_spec.rb
@@ -47,9 +47,9 @@ describe Chef::Resource::PowershellScript do
expect(resource.convert_boolean_return).to eq(false)
end
- it "inherits exactly the :cwd, :domain, :environment, :group, :password, :path, :user, :umask, :architecture, :elevated, :interpreter properties from a parent resource class" do
+ it "inherits exactly the :cwd, :domain, :environment, :group, :password, :path, :user, :umask, :architecture, :elevated, :interpreter, :login properties from a parent resource class" do
inherited_difference = Chef::Resource::PowershellScript.guard_inherited_attributes -
- %i{cwd domain environment group password path user umask architecture elevated interpreter}
+ %i{cwd domain environment group password path user umask architecture elevated interpreter login}
expect(inherited_difference).to eq([])
end
diff --git a/spec/unit/resource/user/windows_user_spec.rb b/spec/unit/resource/user/windows_user_spec.rb
new file mode 100644
index 0000000000..32f8e69d99
--- /dev/null
+++ b/spec/unit/resource/user/windows_user_spec.rb
@@ -0,0 +1,36 @@
+#
+# Copyright:: Copyright (c) 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 "spec_helper"
+
+describe Chef::Resource::User::WindowsUser, "#uid" do
+ let(:resource) { Chef::Resource::User::WindowsUser.new("notarealuser") }
+
+ it "allows a string" do
+ resource.uid "100"
+ expect(resource.uid).to eql(100)
+ end
+
+ it "allows an integer" do
+ resource.uid 100
+ expect(resource.uid).to eql(100)
+ end
+
+ it "does not allow a hash" do
+ expect { resource.uid({ woot: "i found it" }) }.to raise_error(ArgumentError)
+ end
+end
diff --git a/spec/unit/resource/windows_certificate_spec.rb b/spec/unit/resource/windows_certificate_spec.rb
index 7c0df35571..71ef8a9498 100644
--- a/spec/unit/resource/windows_certificate_spec.rb
+++ b/spec/unit/resource/windows_certificate_spec.rb
@@ -80,4 +80,16 @@ describe Chef::Resource::WindowsCertificate do
resource.store_name "MY"
expect { resource.action :create }.not_to raise_error
end
+
+ it "the exportable property defaults to false" do
+ expect(resource.exportable).to be false
+ end
+
+ it "doesn't raise error if exportable option is passed" do
+ resource.pfx_password "chef$123"
+ resource.source "C:\\certs\\test-cert.pfx"
+ resource.store_name "MY"
+ resource.exportable true
+ expect { resource.action :create }.not_to raise_error
+ end
end
diff --git a/spec/unit/resource/windows_defender_exclusion_spec.rb b/spec/unit/resource/windows_defender_exclusion_spec.rb
new file mode 100644
index 0000000000..f776c07713
--- /dev/null
+++ b/spec/unit/resource/windows_defender_exclusion_spec.rb
@@ -0,0 +1,62 @@
+#
+# Copyright:: Copyright (c) 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 "spec_helper"
+
+describe Chef::Resource::WindowsDefenderExclusion do
+ let(:resource) { Chef::Resource::WindowsDefenderExclusion.new("fakey_fakerton") }
+
+ it "sets resource name as :windows_defender_exclusion" do
+ expect(resource.resource_name).to eql(:windows_defender_exclusion)
+ end
+
+ it "sets the default action as :add" do
+ expect(resource.action).to eql([:add])
+ end
+
+ it "supports :add, :remove actions" do
+ expect { resource.action :add }.not_to raise_error
+ expect { resource.action :remove }.not_to raise_error
+ end
+
+ it "paths property defaults to []" do
+ expect(resource.paths).to eql([])
+ end
+
+ it "paths coerces strings to arrays" do
+ resource.paths "foo,bar"
+ expect(resource.paths).to eq(%w{foo bar})
+ end
+
+ it "extensions property defaults to []" do
+ expect(resource.extensions).to eql([])
+ end
+
+ it "extensions coerces strings to arrays" do
+ resource.extensions "foo,bar"
+ expect(resource.extensions).to eq(%w{foo bar})
+ end
+
+ it "process_paths property defaults to []" do
+ expect(resource.process_paths).to eql([])
+ end
+
+ it "process_paths coerces strings to arrays" do
+ resource.process_paths "foo,bar"
+ expect(resource.process_paths).to eq(%w{foo bar})
+ end
+end
diff --git a/spec/unit/resource/windows_defender_spec.rb b/spec/unit/resource/windows_defender_spec.rb
new file mode 100644
index 0000000000..1cafd262a5
--- /dev/null
+++ b/spec/unit/resource/windows_defender_spec.rb
@@ -0,0 +1,71 @@
+#
+# Copyright:: Copyright (c) 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 "spec_helper"
+
+describe Chef::Resource::WindowsDefender do
+ let(:resource) { Chef::Resource::WindowsDefender.new("fakey_fakerton") }
+
+ it "sets resource name as :windows_defender" do
+ expect(resource.resource_name).to eql(:windows_defender)
+ end
+
+ it "sets the default action as :enable" do
+ expect(resource.action).to eql([:enable])
+ end
+
+ it "supports :enable, :disable actions" do
+ expect { resource.action :enable }.not_to raise_error
+ expect { resource.action :disable }.not_to raise_error
+ end
+
+ it "realtime_protection property defaults to true" do
+ expect(resource.realtime_protection).to eql(true)
+ end
+
+ it "intrusion_protection_system property defaults to true" do
+ expect(resource.intrusion_protection_system).to eql(true)
+ end
+
+ it "lock_ui property defaults to true" do
+ expect(resource.lock_ui).to eql(false)
+ end
+
+ it "scan_archives property defaults to true" do
+ expect(resource.scan_archives).to eql(true)
+ end
+
+ it "scan_scripts property defaults to true" do
+ expect(resource.scan_scripts).to eql(false)
+ end
+
+ it "scan_email property defaults to true" do
+ expect(resource.scan_email).to eql(false)
+ end
+
+ it "scan_removable_drives property defaults to true" do
+ expect(resource.scan_removable_drives).to eql(false)
+ end
+
+ it "scan_network_files property defaults to true" do
+ expect(resource.scan_network_files).to eql(false)
+ end
+
+ it "scan_mapped_drives property defaults to true" do
+ expect(resource.scan_mapped_drives).to eql(true)
+ end
+end
diff --git a/spec/unit/resource/windows_firewall_rule_spec.rb b/spec/unit/resource/windows_firewall_rule_spec.rb
index f4dfea1e0a..d73e7d222a 100644
--- a/spec/unit/resource/windows_firewall_rule_spec.rb
+++ b/spec/unit/resource/windows_firewall_rule_spec.rb
@@ -85,7 +85,12 @@ describe Chef::Resource::WindowsFirewallRule do
it "the remote_address property accepts strings" do
resource.remote_address("8.8.4.4")
- expect(resource.remote_address).to eql("8.8.4.4")
+ expect(resource.remote_address).to eql(["8.8.4.4"])
+ end
+
+ it "the remote_address property accepts comma separated lists" do
+ resource.remote_address(["10.17.3.101", "172.7.7.53"])
+ expect(resource.remote_address).to eql(%w{10.17.3.101 172.7.7.53})
end
it "the remote_port property accepts strings" do
@@ -223,8 +228,8 @@ describe Chef::Resource::WindowsFirewallRule do
end
it "aliases :remoteip to :remote_address" do
- resource.remoteip("8.8.8.8")
- expect(resource.remote_address).to eql("8.8.8.8")
+ resource.remoteip(["8.8.8.8"])
+ expect(resource.remote_address).to eql(["8.8.8.8"])
end
it "aliases :localport to :local_port" do
@@ -288,7 +293,7 @@ describe Chef::Resource::WindowsFirewallRule do
end
it "sets RemoteAddress" do
- resource.remote_address("8.8.8.8")
+ resource.remote_address(["8.8.8.8"])
expect(provider.firewall_command("New")).to eql("New-NetFirewallRule -Name 'test_rule' -DisplayName 'test_rule' -RemoteAddress '8.8.8.8' -Direction 'inbound' -Protocol 'TCP' -IcmpType 'Any' -Action 'allow' -Profile 'any' -InterfaceType 'any' -Enabled 'true'")
end
@@ -365,7 +370,7 @@ describe Chef::Resource::WindowsFirewallRule do
resource.group("new group")
resource.local_address("192.168.40.40")
resource.local_port("80")
- resource.remote_address("8.8.4.4")
+ resource.remote_address(["8.8.4.4"])
resource.remote_port("8081")
resource.direction(:outbound)
resource.protocol("UDP")
@@ -416,7 +421,7 @@ describe Chef::Resource::WindowsFirewallRule do
end
it "sets RemoteAddress" do
- resource.remote_address("8.8.8.8")
+ resource.remote_address(["8.8.8.8"])
expect(provider.firewall_command("Set")).to eql("Set-NetFirewallRule -Name 'test_rule' -NewDisplayName 'test_rule' -RemoteAddress '8.8.8.8' -Direction 'inbound' -Protocol 'TCP' -IcmpType 'Any' -Action 'allow' -Profile 'any' -InterfaceType 'any' -Enabled 'true'")
end
@@ -487,7 +492,7 @@ describe Chef::Resource::WindowsFirewallRule do
resource.displayname("some cool display name")
resource.local_address("192.168.40.40")
resource.local_port("80")
- resource.remote_address("8.8.4.4")
+ resource.remote_address(["8.8.4.4"])
resource.remote_port("8081")
resource.direction(:outbound)
resource.protocol("UDP")
diff --git a/spec/unit/resource/windows_pagefile_spec.rb b/spec/unit/resource/windows_pagefile_spec.rb
index 9f12a74ca8..e11b096fc0 100644
--- a/spec/unit/resource/windows_pagefile_spec.rb
+++ b/spec/unit/resource/windows_pagefile_spec.rb
@@ -18,14 +18,14 @@
require "spec_helper"
describe Chef::Resource::WindowsPagefile do
- let(:resource) { Chef::Resource::WindowsPagefile.new("C:\\pagefile.sys") }
+ let(:resource) { Chef::Resource::WindowsPagefile.new("c:\\pagefile.sys") }
it "sets resource name as :windows_pagefile" do
expect(resource.resource_name).to eql(:windows_pagefile)
end
it "the path property is the name_property" do
- expect(resource.path).to eql("C:\\pagefile.sys")
+ expect(resource.path).to eql("c:\\pagefile.sys")
end
it "sets the default action as :set" do
@@ -38,12 +38,7 @@ describe Chef::Resource::WindowsPagefile do
end
it "coerces forward slashes in the path property to back slashes" do
- resource.path "C:/pagefile.sys"
- expect(resource.path).to eql("C:\\pagefile.sys")
+ resource.path "c:/pagefile.sys"
+ expect(resource.path).to eql("c:\\pagefile.sys")
end
-
- it "automatic_managed property defaults to false" do
- expect(resource.automatic_managed).to eql(false)
- end
-
end
diff --git a/spec/unit/resource/windows_task_spec.rb b/spec/unit/resource/windows_task_spec.rb
index c39dcde115..b991cb89de 100644
--- a/spec/unit/resource/windows_task_spec.rb
+++ b/spec/unit/resource/windows_task_spec.rb
@@ -218,7 +218,7 @@ describe Chef::Resource::WindowsTask, :windows_only do
end
it "raise error when priority value less than 0" do
- expect { resource.priority (-1) }.to raise_error(Chef::Exceptions::ValidationFailed, "Option priority's value -1 should be in range of 0 to 10!")
+ expect { resource.priority(-1) }.to raise_error(Chef::Exceptions::ValidationFailed, "Option priority's value -1 should be in range of 0 to 10!")
end
it "raise error when priority values is greater than 10" do
diff --git a/spec/unit/resource/zypper_repository_spec.rb b/spec/unit/resource/zypper_repository_spec.rb
index b1b9cdf874..18ce0e47b3 100644
--- a/spec/unit/resource/zypper_repository_spec.rb
+++ b/spec/unit/resource/zypper_repository_spec.rb
@@ -85,7 +85,7 @@ describe Chef::Resource::ZypperRepository do
it "accepts the legacy 'key' property" do
resource.key "foo"
- expect(resource.gpgkey).to eql("foo")
+ expect(resource.gpgkey).to eql(["foo"])
end
it "accepts the legacy 'uri' property" do
diff --git a/spec/unit/resource_inspector_spec.rb b/spec/unit/resource_inspector_spec.rb
index f3a4b2aa0a..f786018b29 100644
--- a/spec/unit/resource_inspector_spec.rb
+++ b/spec/unit/resource_inspector_spec.rb
@@ -28,7 +28,11 @@ class DummyResource < Chef::Resource
introduced "14.0"
property :first, String, description: "My First Property", introduced: "14.0"
- action :dummy do
+ action :dummy, description: "Dummy action" do
+ return true
+ end
+
+ action :dummy_no_desc do
return true
end
end
@@ -39,7 +43,8 @@ describe Chef::ResourceInspector do
it "returns a hash with required data" do
expect(subject[:description]).to eq "A dummy resource"
- expect(subject[:actions]).to match_array %i{nothing dummy}
+ expect(subject[:actions]).to eq({ nothing: nil, dummy: "Dummy action",
+ dummy_no_desc: nil })
end
context "excluding built in properties" do
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 7a19e0e8e1..f7109cc680 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -1162,6 +1162,52 @@ describe Chef::Resource do
end
end
+ describe "#action_description" do
+ class TestResource < ::Chef::Resource
+ action :symbol_action, description: "a symbol test" do; end
+ action "string_action", description: "a string test" do; end
+ action :base_action0 do; end
+ action :base_action1, description: "unmodified base action 1 desc" do; end
+ action :base_action2, description: "unmodified base action 2 desc" do; end
+ action :base_action3, description: "unmodified base action 3 desc" do; end
+ end
+
+ it "returns nil when no description was provided for the action" do
+ expect(TestResource.action_description(:base_action0)).to eql(nil)
+ end
+
+ context "when action definition is a string" do
+ it "returns the description whether a symbol or string is used to look it up" do
+ expect(TestResource.action_description("string_action")).to eql("a string test")
+ expect(TestResource.action_description(:string_action)).to eql("a string test")
+ end
+ end
+
+ context "when action definition is a symbol" do
+ it "returns the description whether a symbol or string is used to look up" do
+ expect(TestResource.action_description("symbol_action")).to eql("a symbol test")
+ expect(TestResource.action_description(:symbol_action)).to eql("a symbol test")
+ end
+ end
+
+ context "when inheriting from an existing resource" do
+ class TestResourceChild < TestResource
+ action :base_action2, description: "modified base action 2 desc" do; end
+ action :base_action3 do; end
+ end
+
+ it "returns original description when a described action is not overridden in child resource" do
+ expect(TestResourceChild.action_description(:base_action1)).to eq "unmodified base action 1 desc"
+ end
+ it "returns original description when the child resource overrides an inherited action but NOT its description" do
+ expect(TestResourceChild.action_description(:base_action3)).to eq "unmodified base action 3 desc"
+ end
+ it "returns new description when the child resource overrides an inherited action and its description" do
+ expect(TestResourceChild.action_description(:base_action2)).to eq "modified base action 2 desc"
+ end
+ end
+ end
+
describe ".default_action" do
let(:default_action) {}
let(:resource_class) do
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index 24c24f2b78..a52272799c 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -201,7 +201,7 @@ describe Chef::User do
end
describe "API Interactions" do
- before (:each) do
+ before(:each) do
@user = Chef::User.new
@user.name "foobar"
@http_client = double("Chef::ServerAPI mock")
diff --git a/spec/unit/user_v1_spec.rb b/spec/unit/user_v1_spec.rb
index b4b41d832e..3984fb4dec 100644
--- a/spec/unit/user_v1_spec.rb
+++ b/spec/unit/user_v1_spec.rb
@@ -176,8 +176,10 @@ describe Chef::UserV1 do
expect(@user.to_json).to include(%{"display_name":"get_displayed"})
end
- it "does not include the display name if not present" do
- expect(@json).not_to include("display_name")
+ it "does not include the display name if user name not present" do
+ unless @user.username
+ expect(@json).not_to include("display_name")
+ end
end
it "includes the first name when present" do
@@ -314,7 +316,7 @@ describe Chef::UserV1 do
let(:response_406) { OpenStruct.new(code: "406") }
let(:exception_406) { Net::HTTPClientException.new("406 Not Acceptable", response_406) }
- before (:each) do
+ before(:each) do
@user = Chef::UserV1.new
allow(@user).to receive(:chef_root_rest_v0).and_return(double("chef rest root v0 object"))
allow(@user).to receive(:chef_root_rest_v1).and_return(double("chef rest root v1 object"))
@@ -535,7 +537,7 @@ describe Chef::UserV1 do
end # Versioned API Interactions
describe "API Interactions" do
- before (:each) do
+ before(:each) do
@user = Chef::UserV1.new
@user.username "foobar"
@http_client = double("Chef::ServerAPI mock")
diff --git a/spec/unit/windows_service_spec.rb b/spec/unit/windows_service_spec.rb
deleted file mode 100644
index 02a795426d..0000000000
--- a/spec/unit/windows_service_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-#
-# Author:: Mukta Aphale (<mukta.aphale@clogeny.com>)
-# Copyright:: Copyright (c) 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 "spec_helper"
-if ChefUtils.windows?
- require "chef/application/windows_service"
-end
-
-describe "Chef::Application::WindowsService", :windows_only do
- let(:shell_out_result) { double("shellout", stdout: nil, stderr: nil) }
- let(:config_options) do
- {
- log_location: STDOUT,
- config_file: "test_config_file",
- log_level: :info,
- }
- end
- let(:timeout) { 7200 }
- let(:shellout_options) do
- {
- timeout: timeout,
- logger: Chef::Log,
- }
- end
-
- before do
- monologger = instance_double("MonoLogger", :level= => nil, :add => nil, :formatter= => nil, :formatter => nil)
- allow(MonoLogger).to receive(:new).and_return(monologger)
-
- Chef::Config.merge!(config_options)
- allow(subject).to receive(:configure_chef)
- allow(subject).to receive(:parse_options)
- allow(subject).to receive(:running?).and_return(true, false)
- allow(subject).to receive(:state).and_return(4)
- subject.service_init
- end
-
- subject { Chef::Application::WindowsService.new }
-
- it "passes DEFAULT_LOG_LOCATION to chef-client instead of STDOUT" do
- expect(subject).to receive(:shell_out).with(
- "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}",
- shellout_options
- ).and_return(shell_out_result)
- subject.service_main
- end
-
- context "has a log location configured" do
- let(:tempfile) { Tempfile.new "log_file" }
- let(:config_options) do
- {
- log_location: tempfile.path,
- config_file: "test_config_file",
- log_level: :info,
- }
- end
-
- after do
- tempfile.unlink
- end
-
- it "uses the configured log location" do
- expect(subject).to receive(:shell_out).with(
- "chef-client.bat --no-fork -c test_config_file -L #{tempfile.path}",
- shellout_options
- ).and_return(shell_out_result)
- subject.service_main
- end
-
- context "configured to Event Logger" do
- let(:config_options) do
- {
- log_location: Chef::Log::WinEvt.new,
- config_file: "test_config_file",
- log_level: :info,
- }
- end
-
- it "does not pass log location to new process" do
- expect(subject).to receive(:shell_out).with(
- "chef-client.bat --no-fork -c test_config_file",
- shellout_options
- ).and_return(shell_out_result)
- subject.service_main
- end
- end
- end
-
- context "configures a watchdog timeout" do
- let(:timeout) { 10 }
-
- before do
- Chef::Config[:windows_service][:watchdog_timeout] = 10
- end
-
- it "passes watchdog timeout to new process" do
- expect(subject).to receive(:shell_out).with(
- "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}",
- shellout_options
- ).and_return(shell_out_result)
- subject.service_main
- end
- end
-end
diff --git a/tasks/bin/run_external_test b/tasks/bin/run_external_test
index e4d984e9ee..2d191adbb4 100755
--- a/tasks/bin/run_external_test
+++ b/tasks/bin/run_external_test
@@ -32,7 +32,7 @@ Dir.mktmpdir("chef-external-test") do |dir|
Dir.chdir(dir) do
shell_out!("git checkout #{git_thing}", live_stream: STDOUT)
Bundler.with_unbundled_env do
- shell_out!("bundle install --jobs=3 --retry=3", live_stream: STDOUT, env: env)
+ shell_out!("bundle install --jobs=3 --retry=3", live_stream: STDOUT, env: env, timeout: 3600)
shell_out!("bundle exec #{ARGV.join(" ")}", live_stream: STDOUT, env: env)
end
end
diff --git a/tasks/docs.rb b/tasks/docs.rb
index efb0d5420f..ce3af47c62 100755
--- a/tasks/docs.rb
+++ b/tasks/docs.rb
@@ -1,4 +1,4 @@
-RESOURCES_TO_SKIP = ["whyrun_safe_ruby_block", "l_w_r_p_base", "user_resource_abstract_base_class", "linux_user", "pw_user", "aix_user", "dscl_user", "solaris_user", "windows_user", "mac_user", ""].freeze
+RESOURCES_TO_SKIP = ["whyrun_safe_ruby_block", "l_w_r_p_base", "user_resource_abstract_base_class", "linux_user", "pw_user", "aix_user", "solaris_user", "windows_user", "mac_user", ""].freeze
namespace :docs_site do
@@ -35,6 +35,8 @@ namespace :docs_site do
text = ""
text << "#{resource_name} 'name' do\n"
properties.each do |p|
+ next if p["name"] == "sensitive" # we don't need to document sensitive twice
+
pretty_default = pretty_default(p["default"])
text << " #{p["name"].ljust(padding_size)}"
@@ -95,8 +97,7 @@ namespace :docs_site do
# and removing any nil values since those are less types in properties
# and more side effects of legacy design
# @return String
- # TODO:
- # - still does not include nil (?)
+ # @todo still does not include nil (?)
def friendly_types_list(arr)
fixed_arr = Array(arr).map do |x|
case x
@@ -126,23 +127,21 @@ namespace :docs_site do
#
# Build the actions section of the resource yaml
+ # as a hash of actions to markdown descriptions.
#
# @return [Hash]
#
- def action_list(actions)
- list = {}
- actions.sort.each do |action|
- # nothing is a special case that sources the content from the docs site
- list[action.to_sym] = (action == "nothing" ? { "shortcode" => "resources_common_actions_nothing.md" } : { "markdown" => nil })
- end
-
- list
+ def action_list(actions, default_action)
+ actions = actions.map { |k, v| [k.to_sym, { "markdown" => k == default_action.first ? "#{v} (default)" : v } ] }.to_h
+ actions[:nothing] = { "shortcode" => "resources_common_actions_nothing.md" }
+ actions
end
- # TODO:
- # - what to do about "lazy default" for default?
+ # @todo what to do about "lazy default" for default?
def properties_list(properties)
- properties.map do |property|
+ properties.filter_map do |property|
+ next if property["name"] == "sensitive" # we don't need to document sensitive twice
+
default_val = friendly_default_value(property)
values = {}
@@ -152,7 +151,7 @@ namespace :docs_site do
values["default_value"] = default_val unless default_val.nil?
values["new_in"] = property["introduced"] unless property["introduced"].nil?
values["allowed_values"] = property["equal_to"].join(", ") unless property["equal_to"].empty?
- values["description_list"] = [{ "markdown" => property["description"] }]
+ values["description_list"] = split_description_values(property["description"])
values
end
end
@@ -220,7 +219,7 @@ namespace :docs_site do
# using the markers "Note:" for "note" sections and "Warning:" for "warning" sections.
# TODO: has the limitation that the plain description section is assumed to come first,
# and is followed by one or more "note"s or "warning"s sections.
- def build_description(name, text)
+ def split_description_values(text)
return [{ "markdown" => nil }] if text.nil?
description_pattern = /(Note:|Warning:)?((?:(?!Note:|Warning:).)*)/m
@@ -245,8 +244,15 @@ namespace :docs_site do
end
end
+ description
+ end
+
+ # takes the resource description text, splits out warning/note fields and then adds multipackage based notes when appropriate
+ def build_resource_description(name, text)
+ description = split_description_values(text)
+
# if we're on a package resource, depending on the OS we want to inject a warning / note that you can just use 'package' instead
- description << { "notes_resource_based_on_package" => true } if %w{apt_package bff_package dnf_package homebrew_package ips_package openbsd_package pacman_package portage_package smartos_package windows_package yum_package zypper_package}.include?(name)
+ description << { "notes_resource_based_on_package" => true } if %w{apt_package bff_package dnf_package homebrew_package ips_package openbsd_package pacman_package portage_package smartos_package windows_package yum_package zypper_package pacman_package freebsd_package}.include?(name)
description
end
@@ -264,12 +270,12 @@ namespace :docs_site do
r.merge!(special_properties(name))
r["resource"] = name
- r["resource_description_list"] = build_description(name, data["description"])
+ r["resource_description_list"] = build_resource_description(name, data["description"])
r["resource_new_in"] = data["introduced"] unless data["introduced"].nil?
r["syntax_full_code_block"] = generate_resource_block(name, properties, data["default_action"])
r["syntax_properties_list"] = nil
r["syntax_full_properties_list"] = friendly_full_property_list(name, properties)
- r["actions_list"] = action_list(data["actions"])
+ r["actions_list"] = action_list(data["actions"], data["default_action"] )
r["properties_list"] = properties_list(properties)
r["examples"] = data["examples"]
diff --git a/tasks/rspec.rb b/tasks/rspec.rb
index 929e0f91b0..58dab33a0c 100644
--- a/tasks/rspec.rb
+++ b/tasks/rspec.rb
@@ -25,10 +25,12 @@ begin
desc "Run specs for Chef's Gem Components"
task :component_specs do
- %w{chef-utils chef-config}.each do |gem|
+ %w{chef-utils chef-config knife}.each do |gem|
Dir.chdir(gem) do
+ puts "--- Running #{gem} specs"
Bundler.with_unbundled_env do
- sh("bundle install --jobs=3 --retry=3")
+ puts "Executing tests in #{Dir.pwd}:"
+ sh("bundle install --jobs=3 --retry=3 --path=../vendor/bundle")
sh("bundle exec rake spec")
end
end
@@ -39,7 +41,7 @@ begin
task spec: :component_specs
- desc "Run all specs in spec directory"
+ desc "Run all chef specs in spec directory"
RSpec::Core::RakeTask.new(:spec) do |t|
t.verbose = false
t.rspec_opts = %w{--profile}
@@ -47,7 +49,7 @@ begin
end
namespace :spec do
- desc "Run all specs in spec directory"
+ desc "Run all chef specs in spec directory"
RSpec::Core::RakeTask.new(:all) do |t|
t.verbose = false
t.rspec_opts = %w{--profile}
@@ -61,7 +63,7 @@ begin
t.pattern = FileList["spec/**/*_spec.rb"]
end
- desc "Run the specs under spec/unit with activesupport loaded"
+ desc "Run chef's node and role unit specs with activesupport loaded"
RSpec::Core::RakeTask.new(:activesupport) do |t|
t.verbose = false
t.rspec_opts = %w{--require active_support/core_ext --profile}
@@ -70,8 +72,9 @@ begin
end
%i{unit functional integration stress}.each do |sub|
- desc "Run the specs under spec/#{sub}"
+ desc "Run the chef specs under spec/#{sub}"
RSpec::Core::RakeTask.new(sub) do |t|
+ puts "--- Running chef #{sub} specs"
t.verbose = false
t.rspec_opts = %w{--profile}
t.pattern = FileList["spec/#{sub}/**/*_spec.rb"]
diff --git a/tasks/spellcheck.rb b/tasks/spellcheck.rb
index 77b450e5d3..0eb8924a81 100644
--- a/tasks/spellcheck.rb
+++ b/tasks/spellcheck.rb
@@ -17,12 +17,7 @@
namespace :spellcheck do
task run: :prereqs do
- sh 'cspell "**/*"'
- end
-
- desc "List the unique unrecognized words in the project."
- task unknown_words: :prereqs do
- sh 'cspell "**/*" --wordsOnly --no-summary | sort | uniq'
+ sh 'cspell lint --no-progress "**/*"'
end
task prereqs: %i{cspell_check config_check fetch_common}