Friends with Code

Credential Management in DBIx::Class

Most companies have a method of storing passwords and configuration details in a configuration file. For programmers working on on DBIx::Class, this usually means writing your own functions to load a configuration file and supplying them to ->connect(). If you're working on something like a web framework, chances are you're writing something similar like Dancer's Plugin::DBIC or Catalyst's Model::DBIC::Schema.

DBIx::Class::Schema::Config can be added to any DBIC Schema to allow it to connect to a database with pre-configured credentials in any format that Config::Any recognizes, takes one additional line of code in a schema file, one change to your ->connect() statement, and takes less than a minute to configure after it's been installed.

That's cool, so how do I use it?

I will use an example schema and example DBIx::Class for this demonstration. DBIx::Class::Schema::Loader was used to create the schema based on the following SQL in PostgreSQL.

schema.sql:

My/Schema.pm:

packageMy::Schema;# Created by DBIx::Class::Schema::Loader# DO NOT MODIFY THE FIRST PART OF THIS FILEusestrict;usewarnings;usebase'DBIx::Class::Schema';__PACKAGE__->load_namespaces;# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-10-02 19:07:53# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9pRJATkJbKVsD7JMXxShyA# You can replace this text with custom code or comments.1;

Now we'll make a test file to show that we can connect and interact with the database.

#!/usr/bin/perlusewarnings;usestrict;useTest::More;useMy::Schema;sub run_tests{my($db)=@_;ok$db->resultset('Data')->create({key=>"Hello",val=>"World"}),"Adding a row to the database seems to work.";is$db->resultset('Data')->find({key=>"Hello"})->val,"World","Looking up a row from the database seems to work.";ok$db->resultset('Data')->find({key=>"Hello"})->delete,"Deleting a row from the database seems to work.";is$db->resultset('Data')->find({key=>"Hello"}),undef,"It seems that a deleted row is actually deleted.";is$db->resultset('Data')->find({key=>"Init"})->val,"It works!","It seems that the inital row from schema.sql exists.";}okmy$db=My::Schema->connect("dbi:Pg:database=dbic_test",# DSN Line"symkat",# Username'fP37e1Y^21!dV$$y'# Password);run_tests($db);done_testing;

Let's practice test driven development to make the changes. We'll modify our test to make a second DBIC instance, connecting to it as connections are made with DBIx::Class::Schema::Config. The following diff outlines the changes we've made to the test file.

$ prove -l
t/01_connect.t .. 1/? DBIx::Class::ResultSet::create(): Your storage class (DBIx::Class::Storage::DBI) does not set sql_limit_dialect and you have not supplied an explicit limit_dialect in your connection_info. DBIC will attempt to use the GenericSubQ dialect, which works on most databases but can be (and often is) painfully slow. Please file an RT ticket against 'DBIx::Class::Storage::DBI' . at t/01_connect.t line 10
DBIx::Class::ResultSet::create(): DBI Connection failed: Can't connect to data source 'MY_DATABASE' because I can't work out what driver to use (it doesn't seem to contain a 'dbi:driver:' prefix and the DBI_DRIVER env var is not set) at /home/symkat/perl5/lib/perl5/DBIx/Class/Storage/DBI.pm line 1248 at t/01_connect.t line 10
# Tests were run but no plan was declared and done_testing() was not seen.
t/01_connect.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
All 7 subtests passed
Test Summary Report
-------------------
t/01_connect.t (Wstat: 65280 Tests: 7 Failed: 0)
Non-zero exit status: 255
Parse errors: No plan found in TAP output
Files=1, Tests=7, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.15 cusr 0.02 csys = 0.19 CPU)
Result: FAIL

Now we'll make a configuration file in the current directory called dbic.conf. We could use any file format that Config::Any recognizes, and we could put the file in /home/$USER/dbic.conf or /etc/dbic.conf as well and it would be used.

With the change to My/Schema.pm and the addition of dbic.conf, the test file should now pass if we did everything correctly.

We'll notice that while we added a second DBIC instance that was made to work with DBIx::Class::Schema::Config, the initial DBIC instance still works as well. BIx::Class::Schema will always allow normal credentials to pass through unadulterated. This allows the component to be loaded when instances that are not configured to work with DBIx::Class::Schema::Config are in use.

We've created a new file, dbic.conf in the current directory, added a load_components statement to the DBIx::Class::Schema, and added a connect statement with the name of a hash in the configuration file. These changes took a matter of seconds to give us a central location to manage our database credentials.