knife-opc 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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