diff options
author | Tim Smith <tsmith@chef.io> | 2020-05-27 09:52:20 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-27 09:52:20 -0700 |
commit | fbf2b4ffe4ed57a83c8182bebde4f228046546b2 (patch) | |
tree | ed6729ddcfa3347f3bcec4f19dbd7316ae3efdf5 | |
parent | a7439473ac2575b35afb5c88e90576af03bd93e0 (diff) | |
parent | 448c95de608c33905a82fcbbc8f66579f941703b (diff) | |
download | chef-fbf2b4ffe4ed57a83c8182bebde4f228046546b2.tar.gz |
Merge pull request #9910 from chef/add-input-property-to-execute
Add an input property to the execute resource for passing input on STDIN
-rw-r--r-- | lib/chef/provider/execute.rb | 3 | ||||
-rw-r--r-- | lib/chef/resource/apt_package.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/execute.rb | 544 |
3 files changed, 543 insertions, 6 deletions
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb index 4ae1e794fc..20b8a40bf1 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 + def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated, :default_env, :timeout, :input def load_current_resource current_resource = Chef::Resource::Execute.new(new_resource.name) @@ -91,6 +91,7 @@ class Chef opts[:group] = group if group opts[:cwd] = cwd if cwd opts[:umask] = umask if umask + opts[:input] = input if input opts[:default_env] = default_env opts[:log_level] = :info opts[:log_tag] = new_resource.to_s diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb index 369330be9f..0a31f89af3 100644 --- a/lib/chef/resource/apt_package.rb +++ b/lib/chef/resource/apt_package.rb @@ -46,7 +46,7 @@ class Chef apt_package %(package1 package2 package3) ``` - **Install without using recommend packages as a dependency** + **Install without using recommend packages as a dependency**: ```ruby package 'apache2' do diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb index 5a6b7ee960..1fc8922e71 100644 --- a/lib/chef/resource/execute.rb +++ b/lib/chef/resource/execute.rb @@ -27,10 +27,542 @@ class Chef provides :execute, target_mode: true - description "Use the **execute** resource to execute a single command. 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 <<~DESC + Use the **execute** resource to execute a single command. 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. + + Note: Use the **script** resource to execute a script using a specific + interpreter (Ruby, Python, Perl, csh, or Bash).' + DESC + + examples <<~EXAMPLES + **Run a command upon notification**: + + ```ruby + execute 'slapadd' do + command 'slapadd < /tmp/something.ldif' + creates '/var/lib/slapd/uid.bdb' + + action :nothing + end + + template '/tmp/something.ldif' do + source 'something.ldif' + + notifies :run, 'execute[slapadd]', :immediately + end + ``` + + **Run a touch file only once while running a command**: + + ```ruby + execute 'upgrade script' do + command 'php upgrade-application.php && touch /var/application/.upgraded' + + creates '/var/application/.upgraded' + action :run + end + ``` + + **Run a command which requires an environment variable**: + + ```ruby + execute 'slapadd' do + command 'slapadd < /tmp/something.ldif' + creates '/var/lib/slapd/uid.bdb' + + action :run + environment ({'HOME' => '/home/myhome'}) + end + ``` + + **Delete a repository using yum to scrub the cache**: + + ```ruby + # the following code sample thanks to gaffneyc @ https://gist.github.com/918711 + execute 'clean-yum-cache' do + command 'yum clean all' + action :nothing + end + + file '/etc/yum.repos.d/bad.repo' do + action :delete + notifies :run, 'execute[clean-yum-cache]', :immediately + + notifies :create, 'ruby_block[reload-internal-yum-cache]', :immediately + end + ``` + + **Install repositories from a file, trigger a command, and force the internal cache to reload**: + + The following example shows how to install new Yum repositories from a file, + where the installation of the repository triggers a creation of the Yum cache + that forces the internal cache for Chef Infra Client to reload. + + ```ruby + execute 'create-yum-cache' do + command 'yum -q makecache' + action :nothing + end + + ruby_block 'reload-internal-yum-cache' do + block do + Chef::Provider::Package::Yum::YumCache.instance.reload + end + action :nothing + end + + cookbook_file '/etc/yum.repos.d/custom.repo' do + source 'custom' + mode '0755' + notifies :run, 'execute[create-yum-cache]', :immediately + notifies :create, 'ruby_block[reload-internal-yum-cache]', :immediately + end + ``` + + **Prevent restart and reconfigure if configuration is broken**: + + Use the `:nothing` action (common to all resources) to prevent the test from + starting automatically, and then use the `subscribes` notification to run a + configuration test when a change to the template is detected. + + ```ruby + execute 'test-nagios-config' do + command 'nagios3 --verify-config' + action :nothing + subscribes :run, 'template[/etc/nagios3/configures-nagios.conf]', :immediately + end + ``` + + **Notify in a specific order**: + + To notify multiple resources, and then have these resources run in a certain + order, do something like the following. + + ```ruby + execute 'foo' do + command '...' + notifies :create, 'template[baz]', :immediately + notifies :install, 'package[bar]', :immediately + notifies :run, 'execute[final]', :immediately + end + + template 'baz' do + #... + notifies :run, 'execute[restart_baz]', :immediately + end + + package 'bar' + execute 'restart_baz' + execute 'final' do + command '...' + end + ``` + + where the sequencing will be in the same order as the resources are listed in + the recipe: `execute 'foo'`, `template 'baz'`, `execute [restart_baz]`, + `package 'bar'`, and `execute 'final'`. + + **Execute a command using a template**: + + The following example shows how to set up IPv4 packet forwarding using the + **execute** resource to run a command named `forward_ipv4` that uses a template + defined by the **template** resource. + + ```ruby + execute 'forward_ipv4' do + command 'echo > /proc/.../ipv4/ip_forward' + action :nothing + end + + template '/etc/file_name.conf' do + source 'routing/file_name.conf.erb' + + notifies :run, 'execute[forward_ipv4]', :delayed + end + ``` + + where the `command` property for the **execute** resource contains the command + that is to be run and the `source` property for the **template** resource + specifies which template to use. The `notifies` property for the **template** + specifies that the `execute[forward_ipv4]` (which is defined by the **execute** + resource) should be queued up and run at the end of a Chef Infra Client run. + + **Add a rule to an IP table**: + + The following example shows how to add a rule named `test_rule` to an IP table + using the **execute** resource to run a command using a template that is defined + by the **template** resource: + + ```ruby + execute 'test_rule' do + command 'command_to_run + --option value + --option value + --source \#{node[:name_of_node][:ipsec][:local][:subnet]} + -j test_rule' + + action :nothing + end + + template '/etc/file_name.local' do + source 'routing/file_name.local.erb' + notifies :run, 'execute[test_rule]', :delayed + end + ``` + + where the `command` property for the **execute** resource contains the command + that is to be run and the `source` property for the **template** resource + specifies which template to use. The `notifies` property for the **template** + specifies that the `execute[test_rule]` (which is defined by the **execute** + resource) should be queued up and run at the end of a Chef Infra Client run. + + **Stop a service, do stuff, and then restart it**: + + The following example shows how to use the **execute**, **service**, and + **mount** resources together to ensure that a node running on Amazon EC2 is + running MySQL. This example does the following: + + - Checks to see if the Amazon EC2 node has MySQL + - If the node has MySQL, stops MySQL + - Installs MySQL + - Mounts the node + - Restarts MySQL + + ```ruby + # the following code sample comes from the ``server_ec2`` + # recipe in the following cookbook: + # https://github.com/chef-cookbooks/mysql + + if (node.attribute?('ec2') && !FileTest.directory?(node['mysql']['ec2_path'])) + service 'mysql' do + action :stop + end + + execute 'install-mysql' do + command "mv \#{node['mysql']['data_dir']} \#{node['mysql']['ec2_path']}" + not_if do + FileTest.directory?(node['mysql']['ec2_path']) + end + end + + [node['mysql']['ec2_path'], node['mysql']['data_dir']].each do |dir| + directory dir do + owner 'mysql' + group 'mysql' + end + end + + mount node['mysql']['data_dir'] do + device node['mysql']['ec2_path'] + fstype 'none' + options 'bind,rw' + action [:mount, :enable] + end + + service 'mysql' do + action :start + end + end + ``` + + where + + - the two **service** resources are used to stop, and then restart the MySQL service + - the **execute** resource is used to install MySQL + - the **mount** resource is used to mount the node and enable MySQL + + **Use the platform_family? method**: + + The following is an example of using the `platform_family?` method in the Recipe + DSL to create a variable that can be used with other resources in the same + recipe. In this example, `platform_family?` is being used to ensure that a + specific binary is used for a specific platform before using the **remote_file** + resource to download a file from a remote location, and then using the + **execute** resource to install that file by running a command. + + ```ruby + if platform_family?('rhel') + pip_binary = '/usr/bin/pip' + else + pip_binary = '/usr/local/bin/pip' + end + + remote_file "\#{Chef::Config[:file_cache_path]}/distribute_setup.py" do + source 'http://python-distribute.org/distribute_setup.py' + mode '0755' + + not_if { File.exist?(pip_binary) } + end + + execute 'install-pip' do + cwd Chef::Config[:file_cache_path] + command <<~EOF + # command for installing Python goes here + EOF + not_if { File.exist?(pip_binary) } + end + ``` + + where a command for installing Python might look something like: + + ```ruby + \#{node['python']['binary']} distribute_setup.py \#{::File.dirname(pip_binary)}/easy_install pip + ``` + + **Control a service using the execute resource**: + + <div class="admonition-warning"> + <p class="admonition-warning-title">Warning</p> + <div class="admonition-warning-text"> + This is an example of something that should NOT be done. Use the **service** + resource to control a service, not the **execute** resource. + </div> + </div> + + Do something like this: + + ```ruby + service 'tomcat' do + action :start + end + ``` + + and NOT something like this: + + ```ruby + execute 'start-tomcat' do + command '/etc/init.d/tomcat6 start' + action :run + end + ``` + + There is no reason to use the **execute** resource to control a service because + the **service** resource exposes the `start_command` property directly, which + 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**: + + The following example shows how to use the `search` method in the Recipe DSL to + search for users: + + ```ruby + # the following code sample comes from the openvpn cookbook: https://github.com/chef-cookbooks/openvpn + + search("users", "*:*") do |u| + execute "generate-openvpn-\#{u['id']}" do + command "./pkitool \#{u['id']}" + cwd '/etc/openvpn/easy-rsa' + + environment( + 'EASY_RSA' => '/etc/openvpn/easy-rsa', + 'KEY_CONFIG' => '/etc/openvpn/easy-rsa/openssl.cnf', + 'KEY_DIR' => node['openvpn']['key_dir'], + 'CA_EXPIRE' => node['openvpn']['key']['ca_expire'].to_s, + 'KEY_EXPIRE' => node['openvpn']['key']['expire'].to_s, + 'KEY_SIZE' => node['openvpn']['key']['size'].to_s, + 'KEY_COUNTRY' => node['openvpn']['key']['country'], + 'KEY_PROVINCE' => node['openvpn']['key']['province'], + 'KEY_CITY' => node['openvpn']['key']['city'], + 'KEY_ORG' => node['openvpn']['key']['org'], + 'KEY_EMAIL' => node['openvpn']['key']['email'] + ) + not_if { File.exist?("\#{node['openvpn']['key_dir']}/\#{u['id']}.crt") } + end + + %w{ conf ovpn }.each do |ext| + template "\#{node['openvpn']['key_dir']}/\#{u['id']}.\#{ext}" do + source 'client.conf.erb' + variables :username => u['id'] + end + end + + execute "create-openvpn-tar-\#{u['id']}" do + cwd node['openvpn']['key_dir'] + command <<~EOH + tar zcf \#{u['id']}.tar.gz ca.crt \#{u['id']}.crt \#{u['id']}.key \#{u['id']}.conf \#{u['id']}.ovpn + EOH + not_if { File.exist?("\#{node['openvpn']['key_dir']}/\#{u['id']}.tar.gz") } + end + end + ``` + + where + + - the search will use both of the **execute** resources, unless the condition + specified by the `not_if` commands are met + - the `environments` property in the first **execute** resource is being used to + define values that appear as variables in the OpenVPN configuration + - the **template** resource tells Chef Infra Client which template to use + + **Enable remote login for macOS**: + + ```ruby + execute 'enable ssh' do + command '/usr/sbin/systemsetup -setremotelogin on' + not_if '/usr/sbin/systemsetup -getremotelogin | /usr/bin/grep On' + action :run + end + ``` + + **Execute code immediately, based on the template resource**: + + By default, notifications are `:delayed`, that is they are queued up as they are + triggered, and then executed at the very end of a Chef Infra Client run. To run + kan action immediately, use `:immediately`: + + ```ruby + template '/etc/nagios3/configures-nagios.conf' do + # other parameters + notifies :run, 'execute[test-nagios-config]', :immediately + end + ``` + + and then Chef Infra Client would immediately run the following: + + ```ruby + execute 'test-nagios-config' do + command 'nagios3 --verify-config' + action :nothing + end + ``` + + **Sourcing a file**: + + The **execute** resource cannot be used to source a file (e.g. `command 'source + filename'`). The following example will fail because `source` is not an + executable: + + ```ruby + execute 'foo' do + command 'source /tmp/foo.sh' + end + ``` + + + Instead, use the **script** resource or one of the **script**-based resources + (**bash**, **csh**, **perl**, **python**, or **ruby**). For example: + + ```ruby + bash 'foo' do + code 'source /tmp/foo.sh' + end + ``` + + **Run a Knife command**: + + ```ruby + execute 'create_user' do + command <<~EOM + knife user create \#{user} + --admin + --password password + --disable-editing + --file /home/vagrant/.chef/user.pem + --config /tmp/knife-admin.rb + EOM + end + ``` + + **Run install command into virtual environment**: + + The following example shows how to install a lightweight JavaScript framework + into Vagrant: + + ```ruby + execute "install q and zombiejs" do + cwd "/home/vagrant" + user "vagrant" + environment ({'HOME' => '/home/vagrant', 'USER' => 'vagrant'}) + command "npm install -g q zombie should mocha coffee-script" + action :run + end + ``` + + **Run a command as a named user**: + + The following example shows how to run `bundle install` from a Chef Infra Client + run as a specific user. This will put the gem into the path of the user + (`vagrant`) instead of the root user (under which the Chef Infra Client runs): + + ```ruby + execute '/opt/chefdk/embedded/bin/bundle install' do + cwd node['chef_workstation']['bundler_path'] + user node['chef_workstation']['user'] + + environment ({ + 'HOME' => "/home/\#{node['chef_workstation']['user']}", + 'USER' => node['chef_workstation']['user'] + }) + not_if 'bundle check' + end + ``` + + **Run a command as an alternate user**: + + *Note*: When Chef is running as a service, this feature requires that the user + that Chef runs as has 'SeAssignPrimaryTokenPrivilege' (aka + 'SE_ASSIGNPRIMARYTOKEN_NAME') user right. By default only LocalSystem and + NetworkService have this right when running as a service. This is necessary + even if the user is an Administrator. + + This right can be added and checked in a recipe using this example: + + ```ruby + # Add 'SeAssignPrimaryTokenPrivilege' for the user + Chef::ReservedNames::Win32::Security.add_account_right('<user>', 'SeAssignPrimaryTokenPrivilege') + + # Check if the user has 'SeAssignPrimaryTokenPrivilege' rights + Chef::ReservedNames::Win32::Security.get_account_right('<user>').include?('SeAssignPrimaryTokenPrivilege') + ``` + + The following example shows how to run `mkdir test_dir` from a Chef Infra Client + run as an alternate user. + + ```ruby + # Passing only username and password + execute 'mkdir test_dir' do + cwd Chef::Config[:file_cache_path] + + user "username" + password "password" + end + + # Passing username and domain + execute 'mkdir test_dir' do + cwd Chef::Config[:file_cache_path] + + domain "domain-name" + user "user" + password "password" + end + + # Passing username = 'domain-name\\username'. No domain is passed + execute 'mkdir test_dir' do + cwd Chef::Config[:file_cache_path] + + user "domain-name\\username" + password "password" + end + + # Passing username = 'username@domain-name'. No domain is passed + execute 'mkdir test_dir' do + cwd Chef::Config[:file_cache_path] + + user "username@domain-name" + password "password" + end + ``` + + **Run a command with an external input file**: + + execute 'md5sum' do + input File.read(__FILE__) + end + EXAMPLES # The ResourceGuardInterpreter wraps a resource's guards in another resource. That inner resource # needs to behave differently during (for example) why_run mode, so we flag it here. For why_run mode @@ -103,6 +635,10 @@ class Chef description: "Determines whether the script will run with elevated permissions to circumvent User Access Control (UAC) interactively blocking the process.\nThis will cause the process to be run under a batch login instead of an interactive login. The user running #{Chef::Dist::CLIENT} needs the 'Replace a process level token' and 'Adjust Memory Quotas for a process' permissions. The user that is running the command needs the 'Log on as a batch job' permission.\nBecause this requires a login, the user and password properties are required.", introduced: "13.3" + property :input, [String], + introduced: "16.2", + description: "An optional property to set the input sent to the command as STDIN." + alias :env :environment def self.set_guard_inherited_attributes(*inherited_attributes) |