Class: Chef::Knife::EcRestore

Inherits:
Chef::Knife show all
Includes:
EcBase
Defined in:
lib/chef/knife/ec_restore.rb

Constant Summary collapse

PATHS =
%w(chef_repo_path cookbook_path environment_path data_bag_path role_path node_path client_path acl_path group_path container_path)

Instance Method Summary collapse

Methods included from EcBase

#configure_chef, #ensure_webui_key_exists!, included, #org_admin, #rest, #server, #set_client_config!, #set_dest_dir_from_args!, #set_skip_user_acl!, #user_acl_rest

Instance Method Details

#add_users_to_org(orgname) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/chef/knife/ec_restore.rb', line 87

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_rest("organizations/#{orgname}/association_requests", { 'user' => username })
      association_id = response["uri"].split("/").last
      rest.put_rest("users/#{username}/association_requests/#{association_id}", { 'response' => 'accept' })
    rescue Net::HTTPServerException => e
      if e.response.code != "409"
        raise
      end
    end
  end
end

#create_organization(orgname) ⇒ Object



63
64
65
66
67
68
69
70
71
72
# File 'lib/chef/knife/ec_restore.rb', line 63

def create_organization(orgname)
  org = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/org.json"))
  rest.post_rest('organizations', org)
rescue Net::HTTPServerException => e
  if e.response.code == "409"
    rest.put_rest("organizations/#{orgname}", org)
  else
    raise
  end
end

#ec_key_importObject



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/chef/knife/ec_restore.rb', line 150

def ec_key_import
  @ec_key_import ||= begin
                       require 'chef/knife/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] = false
                       k.config[:sql_host] = config[:sql_host]
                       k.config[:sql_port] = config[:sql_port]
                       k.config[:sql_user] = config[:sql_user]
                       k.config[:sql_password] = config[:sql_password]
                       k
                     end
end

#for_each_organizationObject



123
124
125
126
127
128
129
# File 'lib/chef/knife/ec_restore.rb', line 123

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_userObject



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/chef/knife/ec_restore.rb', line 111

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) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/chef/knife/ec_restore.rb', line 266

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

#put_acl(rest, url, acls) ⇒ Object



306
307
308
309
310
311
312
313
314
315
# File 'lib/chef/knife/ec_restore.rb', line 306

def put_acl(rest, url, acls)
  old_acls = rest.get_rest(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
    Chef::ChefFS::FileSystem::AclEntry::PERMISSIONS.each do |permission|
      rest.put_rest("#{url}/#{permission}", { permission => acls[permission] })
    end
  end
end

#restore_group(chef_fs_config, group_name, includes = {:users => true, :clients => true}) ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/chef/knife/ec_restore.rb', line 279

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

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

  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)
end

#restore_key_sqlObject



173
174
175
176
177
178
179
180
# File 'lib/chef/knife/ec_restore.rb', line 173

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) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/chef/knife/ec_restore.rb', line 74

def restore_open_invitations(orgname)
  invitations = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/invitations.json"))
  invitations.each do |invitation|
    begin
      rest.post_rest("organizations/#{orgname}/association_requests", { 'user' => invitation['username'] })
    rescue Net::HTTPServerException => e
      if e.response.code != "409"
        ui.error("Cannot create invitation #{invitation['id']}")
      end
    end
  end
end

#restore_user_aclsObject



103
104
105
106
107
108
109
# File 'lib/chef/knife/ec_restore.rb', line 103

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_sqlObject



165
166
167
168
169
170
171
# File 'lib/chef/knife/ec_restore.rb', line 165

def restore_user_sql
  k = ec_key_import
  k.config[:skip_users_table] = false
  k.config[:skip_keys_table] = !config[:with_key_sql]
  k.config[:users_only] = true
  k.run
end

#restore_usersObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/chef/knife/ec_restore.rb', line 131

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_rest('users', user_with_password)
    rescue Net::HTTPServerException => e
      if e.response.code == "409"
        rest.put_rest("users/#{name}", user)
      else
        raise
      end
    end
  end
end

#runObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/chef/knife/ec_restore.rb', line 37

def run
  set_dest_dir_from_args!
  set_client_config!
  ensure_webui_key_exists!
  set_skip_user_acl!

  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
end

#sort_groups_for_upload(groups) ⇒ Object

Takes an array of group objects and topologically sorts them



262
263
264
# File 'lib/chef/knife/ec_restore.rb', line 262

def sort_groups_for_upload(groups)
  Chef::Tsorter.new(group_array_to_sortable_hash(groups)).tsort
end

#upload_org_data(name) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/chef/knife/ec_restore.rb', line 183

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 group and billing-admins acls
    ui.msg "Restoring org admin data"
    chef_fs_config = Chef::ChefFS::Config.new

    # Handle Admins and Billing Admins seperately
    #
    # 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.
    #
    ['admins', 'billing-admins'].each do |group|
      restore_group(chef_fs_config, group, :clients => false)
    end

    pattern = Chef::ChefFS::FilePattern.new('/acls/groups/billing-admins.json')
    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)})

    Chef::Config.node_name = org_admin

    # Restore the entire org skipping the admin data and restoring groups and acls last
    ui.msg "Restoring the rest of the org"
    Chef::Log.debug "Using admin user: #{org_admin}"
    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
    unsorted_groups = Chef::ChefFS::FileSystem.list(chef_fs_config.local_fs, Chef::ChefFS::FilePattern.new('/groups/*')).select { |entry| entry.name != 'billing-admins.json' }.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| entry.name != 'billing-admins.json' }.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::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new(path), chef_fs_config.local_fs, chef_fs_config.chef_fs, nil, config, ui, proc { |entry| chef_fs_config.format_path(entry) })
    end

    # restore clients to groups, using the pivotal user again
    Chef::Config[:node_name] = 'pivotal'
    ['admins', 'billing-admins'].each do |group|
      restore_group(Chef::ChefFS::Config.new, group)
    end
   ensure
    Chef::Config.restore(old_config)
  end
end