knife-opc 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Marc Paradise (<marc@getchef.com>)
3
- # Copyright:: Copyright 2014 Chef Software, Inc
3
+ # Copyright:: Copyright 2014-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,8 +22,15 @@ module Opc
22
22
  banner "knife opc org user remove ORG_NAME USER_NAME"
23
23
  attr_accessor :org_name, :username
24
24
 
25
+ option :force_remove_from_admins,
26
+ :long => "--force",
27
+ :short => "-f",
28
+ :description => "Force removal of user from the organization's admins and billing-admins group."
29
+
25
30
  deps do
26
- require 'chef/org'
31
+ require "chef/org"
32
+ require "chef/org/group_operations"
33
+ require "chef/json_compat"
27
34
  end
28
35
 
29
36
  def run
@@ -36,16 +43,61 @@ module Opc
36
43
  end
37
44
 
38
45
  org = Chef::Org.new(@org_name)
46
+
47
+ if config[:force_remove_from_admins]
48
+ if org.actor_delete_would_leave_admins_empty?
49
+ failure_error_message(org_name, username)
50
+ ui.msg <<-EOF
51
+ You ran with --force which force removes the user from the admins and billing-admins groups.
52
+ However, removing #{username} from the admins group would leave it empty, which breaks the org.
53
+ Please add another user to org #{org_name} admins group and try again.
54
+ EOF
55
+ exit 1
56
+ end
57
+ remove_user_from_admin_group(org, org_name, username, "admins")
58
+ remove_user_from_admin_group(org, org_name, username, "billing-admins")
59
+ end
60
+
39
61
  begin
40
62
  org.dissociate_user(@username)
41
63
  rescue Net::HTTPServerException => e
42
64
  if e.response.code == "404"
43
65
  ui.msg "User #{username} is not associated with organization #{org_name}"
44
66
  exit 1
67
+ elsif e.response.code == "403"
68
+ body = Chef::JSONCompat.from_json(e.response.body)
69
+ if body.has_key?("error") && body["error"] == "Please remove #{username} from this organization's admins group before removing him or her from the organization."
70
+ failure_error_message(org_name, username)
71
+ ui.msg <<-EOF
72
+ User #{username} is in the organization's admin group. Removing users from an organization without removing them from the admins group is not allowed.
73
+ Re-run this command with --force to remove this user from the admins prior to removing it from the organization.
74
+ EOF
75
+ exit 1
76
+ else
77
+ raise e
78
+ end
45
79
  else
46
80
  raise e
47
81
  end
48
82
  end
49
83
  end
84
+
85
+ def failure_error_message(org_name, username)
86
+ ui.error "Error removing user #{username} from organization #{org_name}."
87
+ end
88
+
89
+ def remove_user_from_admin_group(org, org_name, username, admin_group_string)
90
+ org.remove_user_from_group(admin_group_string, username)
91
+ rescue Net::HTTPServerException => e
92
+ if e.response.code == "404"
93
+ ui.warn <<-EOF
94
+ User #{username} is not in the #{admin_group_string} group for organization #{org_name}.
95
+ You probably don't need to pass --force.
96
+ EOF
97
+ else
98
+ raise e
99
+ end
100
+ end
101
+
50
102
  end
51
103
  end
@@ -1,6 +1,6 @@
1
1
  #
2
- # Author:: Steven Danna (<steve@opscode.com>)
3
- # Copyright:: Copyright 2011 Opscode, Inc.
2
+ # Author:: Steven Danna (<steve@chef.io>)
3
+ # Copyright:: Copyright 2011-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require 'chef/mixin/root_rest'
18
+ require "chef/mixin/root_rest"
19
19
 
20
20
  module Opc
21
21
  class OpcUserCreate < Chef::Knife
@@ -23,14 +23,19 @@ module Opc
23
23
  banner "knife opc user create USERNAME FIRST_NAME [MIDDLE_NAME] LAST_NAME EMAIL PASSWORD"
24
24
 
25
25
  option :filename,
26
- :long => '--filename FILENAME',
27
- :short => '-f FILENAME',
28
- :description => 'Write private key to FILENAME rather than STDOUT'
26
+ :long => "--filename FILENAME",
27
+ :short => "-f FILENAME",
28
+ :description => "Write private key to FILENAME rather than STDOUT"
29
29
 
30
30
  option :orgname,
31
- :long => '--orgname ORGNAME',
32
- :short => '-o ORGNAME',
33
- :description => 'Associate new user to an organization matching ORGNAME'
31
+ :long => "--orgname ORGNAME",
32
+ :short => "-o ORGNAME",
33
+ :description => "Associate new user to an organization matching ORGNAME"
34
+
35
+ option :passwordprompt,
36
+ :long => "--prompt-for-password",
37
+ :short => "-p",
38
+ :description => "Prompt for user password"
34
39
 
35
40
  include Chef::Mixin::RootRestv0
36
41
 
@@ -40,11 +45,18 @@ module Opc
40
45
  username, first_name, middle_name, last_name, email, password = @name_args
41
46
  when 5
42
47
  username, first_name, last_name, email, password = @name_args
48
+ when 4
49
+ username, first_name, last_name, email = @name_args
43
50
  else
44
51
  ui.fatal "Wrong number of arguments"
45
52
  show_usage
46
53
  exit 1
47
54
  end
55
+ password = prompt_for_password if config[:passwordprompt]
56
+ unless password
57
+ ui.fatal "You must either provide a password or use the --prompt-for-password (-p) option"
58
+ exit 1
59
+ end
48
60
  middle_name ||= ""
49
61
 
50
62
  user_hash = {
@@ -54,32 +66,36 @@ module Opc
54
66
  :last_name => last_name,
55
67
  :display_name => "#{first_name} #{last_name}",
56
68
  :email => email,
57
- :password => password
69
+ :password => password,
58
70
  }
59
71
 
60
72
  # Check the file before creating the user so the api is more transactional.
61
- if config[:filename]
62
- file = config[:filename]
63
- unless File.exists?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
64
- ui.fatal "File #{config[:filename]} is not writable. Check permissions."
65
- exit 1
66
- end
67
- end
73
+ if config[:filename]
74
+ file = config[:filename]
75
+ unless File.exists?(file) ? File.writable?(file) : File.writable?(File.dirname(file))
76
+ ui.fatal "File #{config[:filename]} is not writable. Check permissions."
77
+ exit 1
78
+ end
79
+ end
68
80
 
69
81
  result = root_rest.post("users/", user_hash)
70
82
  if config[:filename]
71
83
  File.open(config[:filename], "w") do |f|
72
- f.print(result['private_key'])
84
+ f.print(result["private_key"])
73
85
  end
74
86
  else
75
- ui.msg result['private_key']
87
+ ui.msg result["private_key"]
76
88
  end
77
89
  if config[:orgname]
78
- request_body = {:user => username}
90
+ request_body = { :user => username }
79
91
  response = root_rest.post("organizations/#{config[:orgname]}/association_requests", request_body)
80
92
  association_id = response["uri"].split("/").last
81
- root_rest.put("users/#{username}/association_requests/#{association_id}", {:response => 'accept'})
93
+ root_rest.put("users/#{username}/association_requests/#{association_id}", { :response => "accept" })
82
94
  end
83
95
  end
96
+
97
+ def prompt_for_password
98
+ ui.ask("Please enter the user's password: ") { |q| q.echo = false }
99
+ end
84
100
  end
85
101
  end
@@ -1,6 +1,6 @@
1
1
  #
2
- # Author:: Steven Danna (<steve@opscode.com>)
3
- # Copyright:: Copyright 2011 Opscode, Inc.
2
+ # Author:: Steven Danna (<steve@chef.io>)
3
+ # Copyright:: Copyright 2011-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,31 +15,130 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require 'chef/mixin/root_rest'
18
+ require "chef/mixin/root_rest"
19
19
 
20
20
  module Opc
21
21
  class OpcUserDelete < Chef::Knife
22
22
  category "OPSCODE PRIVATE CHEF ORGANIZATION MANAGEMENT"
23
- banner "knife opc user delete USERNAME [-d]"
23
+ banner "knife opc user delete USERNAME [-d] [-R]"
24
24
 
25
25
  option :no_disassociate_user,
26
- :long => "--no-disassociate-user",
27
- :short => "-d",
28
- :description => "Don't disassociate the user first"
26
+ :long => "--no-disassociate-user",
27
+ :short => "-d",
28
+ :description => "Don't disassociate the user first"
29
29
 
30
+ option :remove_from_admin_groups,
31
+ long: "--remove-from-admin-groups",
32
+ short: "-R",
33
+ 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."
34
+
35
+ attr_reader :username
30
36
  include Chef::Mixin::RootRestv0
31
37
 
38
+ deps do
39
+ require "chef/org"
40
+ require "chef/org/group_operations"
41
+ end
42
+
32
43
  def run
33
- username = @name_args[0]
44
+ @username = @name_args[0]
45
+ admin_memberships = []
46
+ unremovable_memberships = []
47
+
34
48
  ui.confirm "Do you want to delete the user #{username}"
49
+
35
50
  unless config[:no_disassociate_user]
36
- orgs = root_rest.get("users/#{username}/organizations")
37
- org_names = orgs.map {|o| o['organization']['name']}
38
- org_names.each do |org|
39
- ui.output root_rest.delete("organizations/#{org}/users/#{username}")
40
- end
51
+ ui.stderr.puts("Checking organization memberships...")
52
+ orgs = org_memberships(username)
53
+ if orgs.length > 0
54
+ ui.stderr.puts("Checking admin group memberships for #{orgs.length} org(s).")
55
+ admin_memberships, unremovable_memberships = admin_group_memberships(orgs, username)
56
+ end
57
+
58
+ unless admin_memberships.empty?
59
+ unless config[:remove_from_admin_groups]
60
+ error_exit_admin_group_member!(username, admin_memberships)
61
+ end
62
+
63
+ unless unremovable_memberships.empty?
64
+ error_exit_cant_remove_admin_membership!(username, unremovable_memberships)
65
+ end
66
+ remove_from_admin_groups(admin_memberships, username)
67
+ end
68
+ disassociate_user(orgs, username)
69
+ end
70
+
71
+ delete_user(username)
72
+ end
73
+
74
+ def disassociate_user(orgs, username)
75
+ orgs.each { |org| org.dissociate_user(username) }
76
+ end
77
+
78
+ def org_memberships(username)
79
+ org_data = root_rest.get("users/#{username}/organizations")
80
+ org_data.map { |org| Chef::Org.new(org["organization"]["name"]) }
81
+ end
82
+
83
+ def remove_from_admin_groups(admin_of, username)
84
+ admin_of.each do |org|
85
+ ui.stderr.puts "Removing #{username} from admins group of '#{org.name}'"
86
+ org.remove_user_from_group("admins", username)
41
87
  end
42
- ui.output root_rest.delete("users/#{username}")
88
+ end
89
+
90
+ def admin_group_memberships(orgs, username)
91
+ admin_of = []
92
+ unremovable = []
93
+ orgs.each do |org|
94
+ if org.user_member_of_group?(username, "admins")
95
+ admin_of << org
96
+ if org.actor_delete_would_leave_admins_empty?
97
+ unremovable << org
98
+ end
99
+ end
100
+ end
101
+ [admin_of, unremovable]
102
+ end
103
+
104
+ def delete_user(username)
105
+ ui.stderr.puts "Deleting user #{username}."
106
+ root_rest.delete("users/#{username}")
107
+ end
108
+
109
+ # Error message that says how to removed from org
110
+ # admin groups before deleting
111
+ # Further
112
+ def error_exit_admin_group_member!(username, admin_of)
113
+ message = "#{username} is in the 'admins' group of the following organization(s):\n\n"
114
+ admin_of.each { |org| message << "- #{org.name}\n" }
115
+ message << <<EOM
116
+
117
+ Run this command again with the --remove-from-admin-groups option to
118
+ remove the user from these admin group(s) automatically.
119
+
120
+ EOM
121
+ ui.fatal message
122
+ exit 1
123
+ end
124
+
125
+ def error_exit_cant_remove_admin_membership!(username, only_admin_of)
126
+ message = <<EOM
127
+
128
+ #{username} is the only member of the 'admins' group of the
129
+ following organization(s):
130
+
131
+ EOM
132
+ only_admin_of.each { |org| message << "- #{org.name}\n" }
133
+ message << <<EOM
134
+
135
+ Removing the only administrator of an organization can break it.
136
+ Assign additional users or groups to the admin group(s) before
137
+ deleting this user.
138
+
139
+ EOM
140
+ ui.fatal message
141
+ exit 1
43
142
  end
44
143
  end
45
144
  end
@@ -1,6 +1,6 @@
1
1
  #
2
- # Author:: Steven Danna (<steve@opscode.com>)
3
- # Copyright:: Copyright 2011 Opscode, Inc.
2
+ # Author:: Steven Danna (<steve@chef.io>)
3
+ # Copyright:: Copyright 2011-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require 'chef/mixin/root_rest'
18
+ require "chef/mixin/root_rest"
19
19
 
20
20
  module Opc
21
21
  class OpcUserEdit < Chef::Knife
@@ -23,14 +23,14 @@ module Opc
23
23
  banner "knife opc user edit USERNAME"
24
24
 
25
25
  option :input,
26
- :long => '--input FILENAME',
27
- :short => '-i FILENAME',
28
- :description => 'Name of file to use for PUT or POST'
26
+ :long => "--input FILENAME",
27
+ :short => "-i FILENAME",
28
+ :description => "Name of file to use for PUT or POST"
29
29
 
30
30
  option :filename,
31
- :long => '--filename FILENAME',
32
- :short => '-f FILENAME',
33
- :description => 'Write private key to FILENAME rather than STDOUT'
31
+ :long => "--filename FILENAME",
32
+ :short => "-f FILENAME",
33
+ :description => "Write private key to FILENAME rather than STDOUT"
34
34
 
35
35
  include Chef::Mixin::RootRestv0
36
36
 
@@ -43,7 +43,7 @@ module Opc
43
43
  exit 1
44
44
  end
45
45
 
46
- original_user = root_rest.get("users/#{user_name}")
46
+ original_user = root_rest.get("users/#{user_name}")
47
47
  if config[:input]
48
48
  edited_user = JSON.parse(IO.read(config[:input]))
49
49
  else
@@ -52,19 +52,18 @@ module Opc
52
52
  if original_user != edited_user
53
53
  result = root_rest.put("users/#{user_name}", edited_user)
54
54
  ui.msg("Saved #{user_name}.")
55
- if ! result['private_key'].nil?
55
+ if ! result["private_key"].nil?
56
56
  if config[:filename]
57
57
  File.open(config[:filename], "w") do |f|
58
- f.print(result['private_key'])
58
+ f.print(result["private_key"])
59
59
  end
60
60
  else
61
- ui.msg result['private_key']
61
+ ui.msg result["private_key"]
62
62
  end
63
63
  end
64
64
  else
65
65
  ui.msg("User unchanged, not saving.")
66
66
  end
67
-
68
67
  end
69
68
  end
70
69
  end
@@ -1,6 +1,6 @@
1
1
  #
2
- # Author:: Steven Danna (<steve@opscode.com>)
3
- # Copyright:: Copyright 2011 Opscode, Inc.
2
+ # Author:: Steven Danna (<steve@chef.io>)
3
+ # Copyright:: Copyright 2011-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require 'chef/mixin/root_rest'
18
+ require "chef/mixin/root_rest"
19
19
 
20
20
  module Opc
21
21
  class OpcUserList < Chef::Knife
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Tyler Cloke (<tyler@getchef.com>)
3
- # Copyright:: Copyright 2014 Chef, Inc.
3
+ # Copyright:: Copyright 2014-2016 Chef Software, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require 'chef/mixin/root_rest'
18
+ require "chef/mixin/root_rest"
19
19
 
20
20
  module Opc
21
21
  class OpcUserPassword < Chef::Knife
@@ -50,7 +50,7 @@ module Opc
50
50
  # true or false, there is no way of knowing if the user is using ldap or not,
51
51
  # so we will update the user every time, instead of checking if we are actually
52
52
  # changing anything before we PUT.
53
- user = root_rest.get("users/#{user_name}")
53
+ user = root_rest.get("users/#{user_name}")
54
54
 
55
55
  user["password"] = password if not password.nil?
56
56
 
@@ -64,9 +64,8 @@ module Opc
64
64
  root_rest.put("users/#{user_name}", user)
65
65
  rescue => e
66
66
  raise e
67
- exit 1
68
67
  end
69
- ui.msg user
68
+
70
69
  ui.msg("Authentication info updated for #{user_name}.")
71
70
  end
72
71
  end