-
Notifications
You must be signed in to change notification settings - Fork 30
cgrouper
Chris Lasell edited this page Nov 18, 2017
·
2 revisions
#!/usr/bin/ruby
# Create or change the membership of a computer group in the JSS
# Load in the JSS library
require 'jss-api'
# Load other libs
require 'getoptlong'
require 'ostruct'
class App
### Constants
USAGE = "Usage: #{File.basename($0)} [-LsmcdlarRC] [--help] [-n newname]
[-S server] [-U user] [-T timeout] [-V] [--debug]
group [-f /file/path ] [computer [computer ...]]"
ACTIONS_NEEDING_GROUP = [ :create_group, :rename_group, :delete_group, :add_members, :remove_members, :remove_all, :list_members]
ACTIONS_FOR_STATIC_GROUPS_ONLY = [:create_group, :add_members, :remove_members, :remove_all]
### Attributes
attr_reader :debug
### set up
###
def initialize(args)
@debug = false
# define the options
cli_opts = GetoptLong.new(
[ '--help', '-h', '-H', GetoptLong::NO_ARGUMENT ],
[ '--list-groups', '-L', GetoptLong::NO_ARGUMENT ],
[ '--list-static', '-s', GetoptLong::NO_ARGUMENT ],
[ '--list-smart', '-m', GetoptLong::NO_ARGUMENT ],
[ '--create-group', '--create', '-c', GetoptLong::NO_ARGUMENT ],
[ '--rename-group', '--rename', '-n', GetoptLong::REQUIRED_ARGUMENT ],
[ '--delete-group', '--delete', '-d', GetoptLong::NO_ARGUMENT ],
[ '--list-members', '--list-computers', '-l', GetoptLong::NO_ARGUMENT ],
[ '--add-members', '--add', '-a', GetoptLong::NO_ARGUMENT ],
[ '--remove-members', '--remove', '-r', GetoptLong::NO_ARGUMENT ],
[ '--remove-all-members', '-R', GetoptLong::NO_ARGUMENT ],
[ '--file', '-f', GetoptLong::REQUIRED_ARGUMENT ],
[ '--server', '-S', GetoptLong::OPTIONAL_ARGUMENT],
[ '--port', '-P', GetoptLong::OPTIONAL_ARGUMENT],
[ '--user', '-U', GetoptLong::OPTIONAL_ARGUMENT],
[ '--no-verify-cert', '-V', GetoptLong::NO_ARGUMENT],
[ '--timeout', '-T', GetoptLong::OPTIONAL_ARGUMENT],
[ '--no-confirm', '-C', GetoptLong::NO_ARGUMENT],
[ '--debug', GetoptLong::NO_ARGUMENT]
)
# here's where we hold cmdline args and other user options
@options = OpenStruct.new
# set defaults
@options.action = :none
# if stdin is not a tty, then we must assume
# we're being passed a password
@options.getpass = $stdin.tty? ? :prompt : :stdin
# parse the options
cli_opts.each do |opt, arg|
case opt
when '--help'
show_help
when '--list-groups'
@options.action = :list_groups
when '--list-static'
@options.action = :list_static
when '--list-smart'
@options.action = :list_smart
when '--list-members'
@options.action = :list_members
when '--create-group'
@options.action = :create_group
when '--rename-group'
@options.action = :rename_group
@options.new_name = arg
when '--delete-group'
@options.action = :delete_group
when '--add-members'
@options.action = :add_members
when '--remove-members'
@options.action = :remove_members
when '--remove-all-members'
@options.action = :remove_all
when '--file'
@options.input_file = Pathname.new arg
when '--server'
@options.server = arg
when '--port'
@options.port = arg
when '--user'
@options.user = arg
when '--no-verify-cert'
@options.verify_cert = false
when '--timeout'
@options.timeout = arg
when '--no-confirm'
@options.no_confirm = true
when '--debug'
@debug = true
end # case
end # opts.each
@options.group = ARGV.shift
# if we were given a file of computer names, read it in
@options.computers = @options.input_file ? get_computers_from_file : []
# and add any computers on the commandline
@options.computers += ARGV
# will we say anything when finished?
@done_msg = nil
end # init
### Do It
###
def run
if @options.action == :none
puts USAGE
return
end
# use any config settings defined....
@options.user ||= JSS::CONFIG.api_username
@options.server ||= JSS::CONFIG.api_server_name
raise JSS::MissingDataError, "No JSS Username provided or found in the JSS gem config." unless @options.user
raise JSS::MissingDataError, "No JSS Server provided or found in the JSS gem config." unless @options.server
JSS.api.connect( :server => @options.server,
:port => @options.port,
:verify_cert => @options.verify_cert,
:user => @options.user,
:pw => @options.getpass,
:stdin_line => 1,
:timeout => @options.timeout
)
if ACTIONS_NEEDING_GROUP.include? @options.action
raise JSS::MissingDataError, "Please specify a group name" unless @options.group
# get the group from the API
if @options.action == :create_group
@group = JSS::ComputerGroup.new :id => :new, :name => @options.group, :type => :static
else
@group = JSS::ComputerGroup.new :name => @options.group
end
end # if ACTIONS_NEEDING_GROUP
# smart groups can't have some things done to them
raise InvalidTypeError, "You can't do that to a smart group. Use the JSS WebApp if needed." if ACTIONS_FOR_STATIC_GROUPS_ONLY.include? @options.action and @group.smart?
case @options.action
when :list_groups
list_groups
when :list_static
list_groups :static
when :list_smart
list_groups :smart
when :list_members
list_members
when :create_group
create_group
when :rename_group
rename_group
when :delete_group
delete_group
when :add_members
add_members
when :remove_members
remove_members
when :remove_all
remove_all
end # case @options.action
puts "Done! #{@done_msg}" if @done_msg
end # run
#####################################
###
### Show Help
###
def show_help
puts <<-FULLHELP
A tool for working with computer groups in the JSS.
#{USAGE}
Options:
-L, --list-groups - list all computer groups in the JSS
-s, --list-static - list all static computer groups in the JSS
-m, --list-smart - list all smart computer groups in the JSS
-c, --create-group - create a new static computer group in the JSS
-n, --rename newname - rename the specified computer group to newname
-d, --delete - delete the specified computer group (static groups only)
-l, --list-members - list all the computers in the group specified
-a, --add-members - add the specified computer(s) to the specified group
-r, --remove-members - remove the specified computer(s) from the specified group
-R, --remove-all - remove all computers from the specified group
-f, --file /path/... - read computer names/ids from the file at /path/...
-S, --server srvr - specify the JSS API server name
-P, --port portnum - specify the JSS API port
-U, --user username - specify the JSS API user
-V, --no-verify-cert - Allow self-signed, unverified SSL certificate
-T, --timeout secs - specify the JSS API timeout
-C - don't ask for confirmation before acting
--debug - show the ruby backtrace when errors occur
-H, --help - show this help
Notes:
- If no API settings are provided, they will be read from /etc/jss_gem.conf
and ~/.jss_gem.conf. See the JSS Gem docs for details.
- The password for the connection will be read from STDIN or prompted if needed
- Computers can be specified by name or JSS id number. If a name exists
more than once in the JSS, the machine is skipped. Use IDs to avoid this.
- Only static groups can be modified. Use the JSS WebUI for editing smart groups
- If a file is used to specify computers, they are combined with any
specified on the commandline.
- Files of computers must be whitespace-separated
(spaces, tabs, & returns in any number or combination)
FULLHELP
return
end
#####################################
###
### Spit out a list of all computer groups
###
def list_groups(show = :all)
case show
when :all
label = "All"
groups_to_show = JSS::ComputerGroup.all
when :static
label = "Static"
groups_to_show = JSS::ComputerGroup.all_static
when :smart
label = "Smart"
groups_to_show = JSS::ComputerGroup.all_smart
end #case
puts "# #{label} computer groups in the JSS"
puts "#---------------------------------------------"
groups_to_show.sort{|a,b| a[:name].downcase <=> b[:name].downcase}.each do |grp|
puts grp[:name]
end
end
#####################################
###
### Spit out a list of all computers in a group
###
def list_members
puts "# All members of JSS #{@group.smart? ? 'smart' : 'static'} computer group '#{@options.group}'"
puts "#--- name (id) ---------------------------------"
# put them into a tmp array, so that
# we can sort by computer name, remembering that
# there can be duplicate names.
list = []
@group.members.each{|mem| list << "#{mem[:name]} (#{mem[:id]})" }
puts list.sort #.join("\n")
end
#####################################
###
### Create a new group
###
def create_group
return unless confirm "create a new static group named '#{@options.group}'"
@group.create
unless @options.computers.empty?
add_members
end
end
#####################################
###
### rename a group
###
def rename_group
return unless confirm "rename group '#{@group.name}' to '#{@options.new_name}'"
@group.name = @options.new_name
@group.update
end
#####################################
###
### delete a group
###
def delete_group
return unless confirm "DELETE group '#{@group.name}'"
@group.delete
end
#####################################
###
### add members to a group
###
def add_members
raise JSS::MissingDataError, "No computer names provided" if @options.computers.empty?
raise JSS::UnsupportedError, "Smart group members can't be changed." if @group.smart?
return unless @options.action == :create_group or confirm "add computers to group '#{@group.name}'"
@options.computers.each do |c|
begin
@group.add_member c
rescue JSS::NoSuchItemError
puts "#{$!} - skipping"
end # begin
end # each
@group.update
end
#####################################
###
### remove members from a group
###
def remove_members
raise JSS::MissingDataError, "No computer names provided" if @options.computers.empty?
raise JSS::UnsupportedError, "Smart group members can't be changed." if @group.smart?
return unless confirm "remove computers from group '#{@group.name}'"
@options.computers.each do |c|
begin
@group.remove_member c
rescue JSS::NoSuchItemError
puts "#{$!} - skipping"
end
end
@group.update
end
#####################################
###
### remove all members from a group
###
def remove_all
raise JSS::UnsupportedError, "Smart group members can't be changed." if @group.smart?
return unless confirm "remove ALL computers from group '#{@group.name}'"
@group.clear
@group.update
end
#####################################
###
### Read computer names from a file
### Generally the names should be one per line, but
### they can be separated by any whitespace.
### Returns an array of computer names from the file.
###
def get_computers_from_file
raise JSS::NoSuchItemError "File #{@options.input_file} isn't a file or isn't readable." unless \
@options.input_file.file? and @options.input_file.readable?
@options.input_file.read.split(/\s+/)
end
#####################################
###
### Get confirmation before doing something
### Returns true or false
###
def confirm (action)
return true if @options.no_confirm
print "Really #{action}? (y/n): "
$stdin.reopen '/dev/tty'
reply = $stdin.gets.strip
return true if reply =~ /^y/i
return false
end # confirm
end # class App
#######################################
begin
app = App.new(ARGV)
app.run
rescue
# handle exceptions not handled elsewhere
puts "An error occurred: #{$!}"
puts "Backtrace:" if app.debug
puts $@ if app.debug
ensure
end
Copyright 2017 Pixar
Licensed under the Apache License, Version 2.0 (the "Apache License")
with the following modification; you may not use this file except in
compliance with the Apache License and the following modification to it:
Section 6. Trademarks. is deleted and replaced with:
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor
and its affiliates, except as required to comply with Section 4(c) of
the License and to reproduce the content of the NOTICE file.
You may obtain a copy of the Apache License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the Apache License with the above modification is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the Apache License for the specific
language governing permissions and limitations under the Apache License.