Class: Jamf::DBConnection

Inherits:
Object show all
Includes:
Singleton
Defined in:
lib/jamf/db_connection.rb

Overview

A mysql connection to the JSS database.

This is a singleton class, only one can exist at a time, and it is created, but not connected, automatically when the module loads.

Use it via the Jamf::DB_CNX constant (for connection metadata) and the Jamf::DB_CNX.db attribute (which contains the actual mysql query interface) for making queries

Direct MySQL access is minimal and discouraged, since it bypasses the API, and can be very dangerous. However, it’s necessary to overcome some limitations of the API or to access custom tables.

While a database connction isn’t required for most things, warnings will be sent to stderr when functionality is limited due to a lack of a database connection i.e. when Jamf::DB_CNX.connected? == false

To make a connection with credentials, just call the #connect method thus:

Jamf::DB_CNX.connect :server => 'server.company.com', :user => "user", :pw => "pw"

Other options include:

:db_name => which database to connect to, defaults to 'jamfsoftware'
:port => tcp port for connection to server, defaults to the standard mysql port.
:connect_timeout => seconds to wait before giving up on connection, defaults to 120
:read_timeout => seconds to wait before giving up on recieving data, defaults to 120
:write_timeout => seconds to wait before giving up on sending data, defaults to 120
:timeout => sets all three timeouts to the same value, defaults to 120

Calling Jamf::DB_CNX.connect again will re-use any values not provided. but will create a new connection.

Constant Summary collapse

DEFAULT_DB_NAME =

The name of the JSS database on the mysql server

'jamfsoftware'.freeze
DFT_TIMEOUT =

give the connection a 60 second timeout, for really slow net connections (like… from airplanes)

60
DFT_SOCKET =
'/var/mysql/mysql.sock'.freeze
DFT_PORT =

the default MySQL port

3306
DFT_CHARSET =

The default encoding in the tables - JAMF wisely uses UTF-8

'utf8'.freeze
SQL_DATE_FORMAT =

the strftime format for reading/writing dates in the db

'%Y-%m-%d %H:%M:%S'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDBConnection

Returns a new instance of DBConnection.



99
100
101
102
# File 'lib/jamf/db_connection.rb', line 99

def initialize
  @mysql = Mysql.init
  @connected = false
end

Instance Attribute Details

#connect_timeoutObject (readonly)

Returns the value of attribute connect_timeout.



94
95
96
# File 'lib/jamf/db_connection.rb', line 94

def connect_timeout
  @connect_timeout
end

#connectedObject (readonly) Also known as: connected?

Returns the value of attribute connected.



97
98
99
# File 'lib/jamf/db_connection.rb', line 97

def connected
  @connected
end

#db_nameObject (readonly)

Returns the value of attribute db_name.



93
94
95
# File 'lib/jamf/db_connection.rb', line 93

def db_name
  @db_name
end

#portObject (readonly)

Returns the value of attribute port.



90
91
92
# File 'lib/jamf/db_connection.rb', line 90

def port
  @port
end

#read_timeoutObject (readonly)

Returns the value of attribute read_timeout.



95
96
97
# File 'lib/jamf/db_connection.rb', line 95

def read_timeout
  @read_timeout
end

#serverObject (readonly)

Returns the value of attribute server.



89
90
91
# File 'lib/jamf/db_connection.rb', line 89

def server
  @server
end

#socketObject (readonly)

Returns the value of attribute socket.



91
92
93
# File 'lib/jamf/db_connection.rb', line 91

def socket
  @socket
end

#userObject (readonly)

Returns the value of attribute user.



92
93
94
# File 'lib/jamf/db_connection.rb', line 92

def user
  @user
end

#write_timeoutObject (readonly)

Returns the value of attribute write_timeout.



96
97
98
# File 'lib/jamf/db_connection.rb', line 96

def write_timeout
  @write_timeout
end

Instance Method Details

#connect(**args) ⇒ true

Connect to the JSS MySQL database.

Parameters:

  • args (Hash)

    the keyed arguments for connection.

Options Hash (**args):

  • :server (String)

    Required, the hostname of the JSS API server

  • :port (Integer)

    the port number to connect with, defaults to the default Mysql TCP port

  • :socket (String, Pathname)

    when the server is ‘localhost’, the path to the connection socket.

  • :db_name (String)

    the name of the database to use, defaults to ‘jamfsoftware’

  • :user (String)

    Required, the mysql user to connect as

  • :pw (String, Symbol)

    Required, the password for that user, or :prompt, or :stdin If :prompt, the user is promted on the commandline to enter the password for the :user. If :stdin#, the password is read from a line of std in represented by the digit at #, so :stdin3 reads the passwd from the third line of standard input. defaults to line 2, if no digit is supplied. see Jamf.stdin

  • :connect_timeout (Integer)

    the number of seconds to wait for an initial response, defaults to 120

  • :read_timeout (Integer)

    the number of seconds before read-request times out, defaults to 120

  • :write_timeout (Integer)

    the number of seconds before write-request times out, defaults to 120

  • :timeout (Integer)

    used for any of the timeouts that aren’t explicitly set.

Returns:

  • (true)

    the connection was successfully made.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/jamf/db_connection.rb', line 135

def connect(**args)
  begin
    disconnect if @connected
  rescue Mysql::ClientError::ServerGoneError
    @connected = false
  end

  # server might come frome several places
  # if not given in the args, use #hostname to figure out
  # which
  @server = args[:server] ? args[:server] : hostname

  # settings from config if they aren't in the args
  args[:port] ||= Jamf.config.db_server_port ? Jamf.config.db_server_port : Mysql::MYSQL_TCP_PORT
  args[:socket] ||= Jamf.config.db_server_socket ? Jamf.config.db_server_socket : DFT_SOCKET
  args[:db_name] ||= Jamf.config.db_name ? Jamf.config.db_name : DEFAULT_DB_NAME
  args[:user] ||= Jamf.config.db_username
  args[:connect_timeout] ||= Jamf.config.db_connect_timeout
  args[:read_timeout] ||= Jamf.config.db_read_timeout
  args[:write_timeout] ||= Jamf.config.db_write_timeout
  args[:charset] ||= DFT_CHARSET

  ### if one timeout was given, use it for all three
  args[:connect_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT
  args[:read_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT
  args[:write_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT

  @port = args[:port]
  @socket = args[:socket]
  @mysql_name = args[:db_name]
  @user = args[:user]
  @connect_timeout = args[:connect_timeout]
  @read_timeout = args[:read_timeout]
  @write_timeout = args[:write_timeout]

  # make sure we have a user, pw, server
  raise Jamf::MissingDataError, 'No MySQL user specified, or defined in configuration.' unless args[:user]
  raise Jamf::MissingDataError, "Missing :pw (or :prompt/:stdin) for user '#{@user}'" unless args[:pw]
  raise Jamf::MissingDataError, 'No MySQL Server hostname specified, or listed in configuration.' unless @server

  @pw = if args[:pw] == :prompt
          JSS.prompt_for_password "Enter the password for the MySQL user #{@user}@#{@server}:"
        elsif args[:pw].is_a?(Symbol) && args[:pw].to_s.start_with?('stdin')
          args[:pw].to_s =~ /^stdin(\d+)$/
          line = Regexp.last_match(1)
          line ||= 2
          JSS.stdin line
        else
          args[:pw]
        end

  @mysql = Mysql.init

  @mysql.options Mysql::OPT_CONNECT_TIMEOUT, @connect_timeout
  @mysql.options Mysql::OPT_READ_TIMEOUT, @read_timeout
  @mysql.options Mysql::OPT_WRITE_TIMEOUT, @write_timeout
  @mysql.charset = args[:charset]
  @mysql.connect @server, @user, @pw, @mysql_name, @port, @socket

  @connected = true

  @server
rescue Mysql::ServerError::NotSupportedAuthMode => e
  raise Mysql::ServerError::AccessDeniedError, "Probable unknown MySQL user '#{@user}'. Original error was 'Mysql::ServerError::NotSupportedAuthMode: #{e}' which is sometimes raised when the user does not exist on the server."
end

#dbMysql

Returns The mysql database connection itself.

Returns:

  • (Mysql)

    The mysql database connection itself

Raises:



204
205
206
207
# File 'lib/jamf/db_connection.rb', line 204

def db
  raise Jamf::InvalidConnectionError, 'No database connection. Please use Jamf::DB_CNX.connect' unless Jamf::DB_CNX.connected?
  @mysql
end

#disconnectObject

close the connection to the database it’ll have to be re-connected before using again



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/jamf/db_connection.rb', line 213

def disconnect
  @mysql.close! if @mysql.protocol
  @server = nil
  @port = nil
  @socket = nil
  @user = nil
  @connection_timeout = DFT_TIMEOUT
  @read_timeout = DFT_TIMEOUT
  @write_timeout = DFT_TIMEOUT
  @connected = false
  nil
end

#hostnameString

The server to which we are connected, or will try connecting to if none is specified with the call to #connect

Returns:

  • (String)

    the hostname of the server



263
264
265
266
267
268
269
270
271
# File 'lib/jamf/db_connection.rb', line 263

def hostname
  # return it if already set
  return @server if @server
  # otherwise, from the config
  srvr = Jamf.config.db_server_name
  # otherwise, assume its on the JSS server to which this client talks
  srvr ||= Jamf::Client.jss_server
  srvr
end

#valid_server?(server, port = DFT_PORT) ⇒ Boolean

Test that a given hostname is a MySQL server

Parameters:

  • server (String)

    The hostname to test

Returns:

  • (Boolean)

    does the server host a MySQL server?



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/jamf/db_connection.rb', line 232

def valid_server?(server, port = DFT_PORT)
  mysql = Mysql.init
  mysql.options Mysql::OPT_CONNECT_TIMEOUT, 60
  mysql.charset = DFT_CHARSET

  begin
    # this connection should get an access denied error if there is
    # a mysql server there. I'm assuming no one will use this username
    # and pw for anything real
    # Also with newer versions of mysql, a Mysql instance that has
    # never authenticated will raise Mysql::ServerError::NotSupportedAuthMode
    # rather than Mysql::ServerError::AccessDeniedError, until a
    # successful connection is made. After that, re-connecting will
    # raise AccessDeniedError when credentials are invalid.

    mysql.connect server, 'notArealUser', "definatelyNotA#{$PROCESS_ID}password", 'not_a_db', port

  rescue Mysql::ServerError::AccessDeniedError, Mysql::ServerError::NotSupportedAuthMode
    return true
  rescue
    return false
  end
  false
end