class Chef::Knife::EcRestore

Constants

PATHS
PERMISSIONS

Public Instance Methods

add_users_to_org(orgname) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 97
def add_users_to_org(orgname)
  members = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/members.json"))
  members.each do |member|
    username = member['user']['username']
    begin
      response = rest.post("organizations/#{orgname}/association_requests", { 'user' => username })
      association_id = response["uri"].split("/").last
      rest.put("users/#{username}/association_requests/#{association_id}", { 'response' => 'accept' })
    rescue Net::HTTPServerException => ex
      knife_ec_error_handler.add(ex) if ex.response.code != "409"
    end
  end
end
chef_fs_copy_pattern(pattern_str, chef_fs_config) click to toggle source

ChefFS copy pattern inside the EcRestore class will copy from the local_fs to the Chef Server.

NOTE: Do not get confused, this is the other way around from how we implemented in EcBackup. Therefor we can't abstract it inside EcBase.

# File lib/chef/knife/ec_restore.rb, line 313
def chef_fs_copy_pattern(pattern_str, chef_fs_config)
  ui.msg "Copying #{pattern_str}"
  pattern = Chef::ChefFS::FilePattern.new(pattern_str)
  Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs_config.local_fs,
                                   chef_fs_config.chef_fs, nil,
                                   config, ui,
                                   proc { |entry| chef_fs_config.format_path(entry) })
rescue Net::HTTPServerException,
       Chef::ChefFS::FileSystem::NotFoundError,
       Chef::ChefFS::FileSystem::OperationFailedError => ex
  knife_ec_error_handler.add(ex)
end
create_organization(orgname) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 72
def create_organization(orgname)
  org = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/org.json"))
  rest.post('organizations', org)
rescue Net::HTTPServerException => ex
  if ex.response.code == "409"
    rest.put("organizations/#{orgname}", org)
  else
    knife_ec_error_handler.add(ex)
  end
end
ec_key_import() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 180
def ec_key_import
  @ec_key_import ||= begin
                       require_relative 'ec_key_import'
                       k = Chef::Knife::EcKeyImport.new
                       k.name_args = ["#{dest_dir}/key_dump.json", "#{dest_dir}/key_table_dump.json"]
                       k.config[:skip_pivotal] = true
                       k.config[:skip_ids] = config[:skip_ids]
                       k.config[:sql_host] = config[:sql_host]
                       k.config[:sql_port] = config[:sql_port]
                       k.config[:sql_db] = config[:sql_db]
                       k.config[:sql_user] = config[:sql_user]
                       k.config[:sql_password] = config[:sql_password]
                       k
                     end
end
for_each_organization() { |name| ... } click to toggle source
# File lib/chef/knife/ec_restore.rb, line 131
def for_each_organization
  Dir.foreach("#{dest_dir}/organizations") do |name|
    next if name == '..' || name == '.' || !File.directory?("#{dest_dir}/organizations/#{name}")
    next unless (config[:org].nil? || config[:org] == name)
    yield name
  end
end
for_each_user() { |name| ... } click to toggle source
# File lib/chef/knife/ec_restore.rb, line 119
def for_each_user
  Dir.foreach("#{dest_dir}/users") do |filename|
    next if filename !~ /(.+)\.json/
    name = $1
    if name == 'pivotal' && !config[:overwrite_pivotal]
      ui.warn("Skipping pivotal user.  To overwrite pivotal, pass --overwrite-pivotal.")
      next
    end
    yield name
  end
end
group_array_to_sortable_hash(groups) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 332
def group_array_to_sortable_hash(groups)
  ret = {}
  groups.each do |group|
    name = group["name"]
    ret[name] = if group.key?("groups")
                  group["groups"]
                else
                  []
                end
  end
  ret
end
purge_users_on_restore() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 168
def purge_users_on_restore
  return unless config[:purge]
  users_for_purge do |user|
    ui.msg "Deleting user #{user} from remote (purge is on)"
    begin
      rest.delete("/users/#{user}")
    rescue Net::HTTPServerException => e
      ui.warn "Failed deleting user #{user} from remote #{e}"
    end
  end
end
put_acl(rest, url, acls) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 377
def put_acl(rest, url, acls)
  old_acls = rest.get(url)
  old_acls = Chef::ChefFS::DataHandler::AclDataHandler.new.normalize(old_acls, nil)
  acls = Chef::ChefFS::DataHandler::AclDataHandler.new.normalize(acls, nil)
  if acls != old_acls
    PERMISSIONS.each do |permission|
      rest.put("#{url}/#{permission}", { permission => acls[permission] })
    end
  end
rescue Net::HTTPServerException => ex
  knife_ec_error_handler.add(ex)
end
restore_group(chef_fs_config, group_name, includes = {:users => true, :clients => true}) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 345
def restore_group(chef_fs_config, group_name, includes = {:users => true, :clients => true})
  includes[:users] = true unless includes.key? :users
  includes[:clients] = true unless includes.key? :clients

  ui.msg "Copying /groups/#{group_name}.json"
  group = Chef::ChefFS::FileSystem.resolve_path(
    chef_fs_config.chef_fs,
    "/groups/#{group_name}.json"
  )

  # Will throw NotFoundError if JSON file does not exist on disk. See below.
  members_json = Chef::ChefFS::FileSystem.resolve_path(
    chef_fs_config.local_fs,
    "/groups/#{group_name}.json"
  ).read

  members = JSON.parse(members_json).select do |member|
    if includes[:users] and includes[:clients]
      member
    elsif includes[:users]
      member == 'users'
    elsif includes[:clients]
      member == 'clients'
    end
  end

  group.write(members.to_json)
rescue Chef::ChefFS::FileSystem::NotFoundError
  Chef::Log.warn "Could not find #{group.display_path} on disk. Will not restore."
end
restore_key_sql() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 205
def restore_key_sql
  k = ec_key_import
  k.config[:skip_users_table] = true
  k.config[:skip_keys_table] = false
  k.config[:users_only] = false
  k.config[:clients_only] = true
  k.run
end
restore_open_invitations(orgname) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 83
def restore_open_invitations(orgname)
  invitations = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/invitations.json"))
  invitations.each do |invitation|
    begin
      rest.post("organizations/#{orgname}/association_requests", { 'user' => invitation['username'] })
    rescue Net::HTTPServerException => ex
      if ex.response.code != "409"
        ui.error("Cannot create invitation #{invitation['id']}")
        knife_ec_error_handler.add(ex)
      end
    end
  end
end
restore_user_acls() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 111
def restore_user_acls
  ui.msg "Restoring user ACLs"
  for_each_user do |name|
    user_acl = JSONCompat.from_json(File.read("#{dest_dir}/user_acls/#{name}.json"))
    put_acl(user_acl_rest, "users/#{name}/_acl", user_acl)
  end
end
restore_user_sql() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 196
def restore_user_sql
  k = ec_key_import
  k.config[:knife_ec_error_handler] = knife_ec_error_handler
  k.config[:skip_users_table] = false
  k.config[:skip_keys_table] = !config[:with_key_sql]
  k.config[:users_only] = true
  k.run
end
restore_users() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 139
def restore_users
  ui.msg "Restoring users"
  for_each_user do |name|
    user = JSONCompat.from_json(File.read("#{dest_dir}/users/#{name}.json"))
    begin
      # Supply password for new user
      user_with_password = user.dup
      user_with_password['password'] = SecureRandom.hex
      rest.post('users', user_with_password)
    rescue Net::HTTPServerException => ex
      if ex.response.code == "409"
        rest.put("users/#{name}", user)
        next
      end
      knife_ec_error_handler.add(ex)
    end
  end
  purge_users_on_restore
end
run() click to toggle source
# File lib/chef/knife/ec_restore.rb, line 42
def run
  set_dest_dir_from_args!
  set_client_config!
  ensure_webui_key_exists!
  set_skip_user_acl!

  warn_on_incorrect_clients_group(dest_dir, "restore")

  restore_users unless config[:skip_users]
  restore_user_sql if config[:with_user_sql]

  for_each_organization do |orgname|
    ui.msg "Restoring organization[#{orgname}]"
    create_organization(orgname)
    restore_open_invitations(orgname)
    add_users_to_org(orgname)
    upload_org_data(orgname)
  end

  restore_key_sql if config[:with_key_sql]

  if config[:skip_useracl]
    ui.warn("Skipping user ACL update. To update user ACLs, remove --skip-useracl or upgrade your Enterprise Chef Server.")
  else
    restore_user_acls
  end

  completion_banner
end
sort_groups_for_upload(groups) click to toggle source

Takes an array of group objects and topologically sorts them

# File lib/chef/knife/ec_restore.rb, line 328
def sort_groups_for_upload(groups)
  Chef::Tsorter.new(group_array_to_sortable_hash(groups)).tsort
end
upload_org_data(name) click to toggle source
# File lib/chef/knife/ec_restore.rb, line 215
def upload_org_data(name)
  old_config = Chef::Config.save

  begin
    # Clear out paths
    PATHS.each do |path|
      Chef::Config.delete(path.to_sym)
    end

    Chef::Config.chef_repo_path = "#{dest_dir}/organizations/#{name}"
    Chef::Config.versioned_cookbooks = true
    Chef::Config.chef_server_url = "#{server.root_url}/organizations/#{name}"

    # Upload the admins, public_key_read_access and billing-admins groups and acls
    ui.msg "Restoring org admin data"
    chef_fs_config = Chef::ChefFS::Config.new

    # Handle Admins, Billing Admins and Public Key Read Access separately
    #
    # admins: We need to upload admins first so that we
    # can upload all of the other objects as a user in the org
    # rather than as pivotal.  Because the clients, and groups, don't
    # exist yet, we first upload the group with only the users.
    #
    # billing-admins: The default permissions on the
    # billing-admin group only give update permissions to
    # pivotal and members of the billing-admins group. Since we
    # can't unsure that the admin we choose for uploading will
    # be in the billing admins group, we have to upload this
    # group as pivotal.  Thus, we upload its users and ACL here,
    # and then update it again once all of the clients and
    # groups are uploaded.
    #
    # public_key_read_access: Similarly for public_key_read_access,
    # the default permissions only give read/update to
    # pivotal and members of the admins group. Use the same strategy
    # above here.
    #
    groups = ['admins', 'billing-admins']
    groups.push('public_key_read_access') if
      ::File.exist?(::File.join(chef_fs_config.local_fs.child_paths['groups'], 'public_key_read_access.json'))

    groups.each do |group|
      restore_group(chef_fs_config, group, :clients => false)
    end

    acls_groups_paths = ['/acls/groups/billing-admins.json']
    acls_groups_paths.push('/acls/groups/public_key_read_access.json') if
      ::File.exist?(::File.join(chef_fs_config.local_fs.child_paths['acls'], 'groups', 'public_key_read_access.json'))

    acls_groups_paths.each do |acl|
      chef_fs_copy_pattern(acl, chef_fs_config)
    end

    Chef::Config.node_name = if config[:skip_version]
                               org_admin
                             else
                               server.supports_defaulting_to_pivotal? ? 'pivotal' : org_admin
                             end

    # Restore the entire org skipping the admin data and restoring groups and acls last
    ui.msg "Restoring the rest of the org"
    chef_fs_config = Chef::ChefFS::Config.new
    top_level_paths = chef_fs_config.local_fs.children.select { |entry| entry.name != 'acls' && entry.name != 'groups' }.map { |entry| entry.path }

    # Topologically sort groups for upload
    filenames = ['billing-admins.json', 'public_key_read_access.json']
    unsorted_groups = Chef::ChefFS::FileSystem.list(chef_fs_config.local_fs, Chef::ChefFS::FilePattern.new('/groups/*')).select { |entry| ! filenames.include?(entry.name) }.map { |entry| JSON.parse(entry.read) }
    group_paths = sort_groups_for_upload(unsorted_groups).map { |group_name| "/groups/#{group_name}.json" }

    group_acl_paths = Chef::ChefFS::FileSystem.list(chef_fs_config.local_fs, Chef::ChefFS::FilePattern.new('/acls/groups/*')).select { |entry| ! filenames.include?(entry.name) }.map { |entry| entry.path }
    acl_paths = Chef::ChefFS::FileSystem.list(chef_fs_config.local_fs, Chef::ChefFS::FilePattern.new('/acls/*')).select { |entry| entry.name != 'groups' }.map { |entry| entry.path }

    # Store organization data in a particular order:
    # - clients must be uploaded before groups (in top_level_paths)
    # - groups must be uploaded before any acl's
    # - groups must be uploaded twice to account for Chef Server versions that don't
    #   accept group members on POST
    (top_level_paths + group_paths*2 + group_acl_paths + acl_paths).each do |path|
      chef_fs_copy_pattern(path, chef_fs_config)
    end

    # restore clients to groups, using the pivotal user again
    Chef::Config[:node_name] = 'pivotal'
    groups.each do |group|
      restore_group(Chef::ChefFS::Config.new, group)
    end
   ensure
    Chef::Config.restore(old_config)
  end
end
users_for_purge() { |user| ... } click to toggle source
# File lib/chef/knife/ec_restore.rb, line 159
def users_for_purge
  purge_list = remote_user_list - local_user_list
  # failsafe - don't delete pivotal
  purge_list -= ['pivotal']
  purge_list.each do |user|
    yield user
  end
end