chef-boneyard / chef-provisioning

A library for creating machines and infrastructures idempotently in Chef.
Apache License 2.0
524 stars 164 forks source link

SchTask Support for ConvergenceStrategy::InstallMsi.converge #443

Open hh opened 9 years ago

hh commented 9 years ago

It's a dependency for getting machine_image supported for run_lists that include wsus updates and other tasks required to be run on \LocalMachine.

chef/mixlib-install#9 should get us to a point where MixLib install support SchTask by creating a install_command but I think we'll need make changes to ConvergenceStrategy::InstallMsi.converge to support creation / usage of a chef-client-schtask

hh commented 9 years ago

Related if we want a pure SchTask based install and run:

schtask support for installing chef-client on windows : chef/mixlib-install#9

Background, where we got the powershell script working:

Upload the installation script and run it from the host. : chef/chef-provisioning#409 Upload the PS1 script and run directly : chef/chef-provisioning#410

hh commented 9 years ago

We need updates applied, and they can only be applied successfully via schtask criteo-cookbooks/wsus-client#16. This is a spike that works, but I'd definitely feeling the pain of our lack of schtask provisioning to do this properly. (It sorta works, I get a 401 authentication fails after the wsus-updates apply, but that a different issue) @adamedx @tyler-ball @mwrock ?

require 'chef/provisioning/aws_driver'
require 'pry-byebug'
current_dir = File.dirname(__FILE__) # to get relative files... encrypted databag

with_chef_server "https://api.chef.io/organizations/#{ENV['CHEF_ORG']}",
                 :client_name => Chef::Config[:node_name],
                 :signing_key_filename => Chef::Config[:client_key]

with_machine_options bootstrap_options: {
                       instance_type: 'c3.2xlarge',
                       subnet_id: 'subnet-6021b417',
                       security_group_ids: [
                         'sg-689c5a0c',
                         'sg-6f9c5a0b',
                         'sg-0a9a5c6e'
                       ]
                     },
                     aws_tags: {
                       :created_by => 'chris',
                       :please_destroy => 'true'
                     },
                     transport_address_location: :private_ip,
                     image_id: 'ami-7bc3e04b' # aws-marketplace/CIS Microsoft Windows Server 2012 R2 Benchmark v1.1.0-26bb465c-ce26-4da9-afb8-040b2f8c9a7f-ami-7a88f312.2

machine_name = 'base-2012-hardened-xv'

m = machine "#{machine_name}" do
  action :allocate
end

# converge a machine with chef-client::task
machine "#{machine_name}" do
  #action :nothing
  action :converge
  attribute %w{ client_client task frequency}, 'once'
  recipe 'chef-client::task'
  # these attributes will be use in the wsus convergence via schtask
  attribute %w{ wsus_client no_reboot_with_logged_users }, false
  attribute %w{ wsus_client automatic_update_behavior }, :install
  attribute %w{ wsus_client reboot_warning }, 1
  attribute %w{ wsus_client reboot_prompt_timeout }, 1
end

ruby_block "Run wsus-update via schtask on #{machine_name}" do
  #action :nothing
  block do
    mr=resources(machine: machine_name).provider_for_action(:ready)
    mr.load_current_resource
    machine=mr.action_ready
    driver = node.run_state[:chef_provisioning].drivers.values.first
    i=driver.ec2.instances[machine.machine_spec.reference['instance_id']]
    #not sure how to add recipes in a ruby_block...
    # it also seems that any machine 'thismachine' { recipe 'foo' }
    # resources that come later in __FILE__ update the run_list before
    # they execute, probably something to do with execution contexts
    ah = Chef::Provisioning::ChefProviderActionHandler.new(mr)
    if machine.execute_always(
         "knife node show #{machine_name} -a run_list -c c:\\chef\\client.rb"
       ).stdout.lines.grep(/wsus-client/).empty?
      machine.execute(ah,
        "knife node run_list add #{machine_name} wsus-client -c c:\\chef\\client.rb"
      )
    end
    machine.execute(ah, 'schtasks /run /tn chef-client')
    finished_running = false
    timer = 0
    until finished_running
      task_query = machine.execute_always('schtasks /query /tn chef-client').stdout.lines.grep(/chef-client/)
      finished_running = task_query.grep(/Running/).empty?
      Chef::Log.warn "chef-client wsus-client schtask running for #{timer} seconds"
      sleep 10
      timer += 10
    end
  end
end
tyler-ball commented 9 years ago

@hh rather than loading the machine from the resource_collection, I think you could just do m = machine "#{machine_name}" do so that m would be in scope during the ruby_block.

The bottom of your example (where the scheduled task is ran then queried) LGTM for getting into the convergence strategy. But I would hope this is ultimately something handled by Mixlib::Install. In that case I could see us still having to query the node to see when the scheduled task is finished (unless Mixlib::Install would do this for us).

mwrock commented 9 years ago

A couple thoughts here.

  1. I have found that running updates during a machine provisioning is generally best avoided if possible. Even on 2012R2 this can possibly take hours, cause a converge to time out, require reboots and have all the winrm/scheduled task issues you refer to. I found it best to do this in creating the base image like in a packer template. Here is a blog I wrote on this process. However I realize sometimes business constraints require this to be done during provisioning.
  2. If you are gonna do the updates during a chef run, I'd be inclined to create a cookbook/recipe dedicated to this and use the scheduled task resource to wrap the updates. Here is an example of a recipe I wrote to use a scheduled task to install a service that required some certificate manipulationi not possible in a remote session:
#create the batch file from a template which does the stuff you want done in the task
batch_path = "#{node['platform_octopus']['home_path']}/octopus.bat"
template batch_path do
  source 'octopus.bat.erb'
  action :create
end

#create the task. Note this will create and run the task immediately and delete itself when done
windows_task 'octopus' do
  user 'administrator'
  password provisioner_bootstrap_password
  cwd 'C:\\Program Files\\Octopus Deploy\\Tentacle'
  command batch_path
  frequency :monthly
  frequency_modifier 12
  run_level :highest
  action [:create, :run]
  notifies :delete, 'windows_task[octopus]', :delayed
  guard_interpreter :powershell_script
  not_if "(Get-Service | ? {$_.name -Contains 'OctopusDeploy Tentacle'}).count -gt 0"
end

ruby_block "Discover Octopus Machine (#{node['platform_octopus']['server_uri']})" do # ignore ~FC014
  block do
   # your code that checks if the task has finished
  end
end

I find this to be alot cleaner and can separate the wsus stuff from the provisioning script. This does not address possible issues with timeouts or reboots but like your code above, it will run the updates driven by a winrm based run.

Not sure if this helps at all in your scenario. Windows updates are a major pain (I say as I test a packer template against windows 7 with about 250 updates).

tyler-ball commented 9 years ago

We should create a new convergence strategy called install_msi_schedule.rb and either set that as the default or let users specify that they want to use that. We should also add a toggle for whether to run system updates since those can take forevvvvvvver. This will also probably depend on https://github.com/chef/chef-provisioning/issues/362

hh commented 9 years ago

Looks like we should be using https://github.com/WinRb/winrm-elevated

hh commented 8 years ago

@tyler-ball @sneal is https://github.com/WinRb/winrm-elevated/ working well enough for me to try to create a stake in the ground for this?

tyler-ball commented 8 years ago

@hh I haven't tested it

hh commented 8 years ago

Looks like the dependency may have been moved from winrm-elevated into WinRb/winrm-fs#22 supplanting test-kitchen/winrm-transport#16 and directly using winrm-elevated. @mwrock is this correct?

hh commented 8 years ago

I'm guessing I should wait for WinRb/winrm-fs#22 to support schtask before tackling this. Willing to help on that, but didn't want to overlap.

mwrock commented 8 years ago

winrm-elevated would be the gem to leverage for elevated winrm commands that use a scheduled task under the hood. I know @sneal extracted that out of the current vagrant code. If that is what vagrant master is using, then I'd say it is pretty solid.