A Day In The Lyf

The lyf so short, the craft so longe to lerne

Upgrading Typo

Posted by Brandon Byars Mon, 14 Jan 2008 23:48:00 GMT

Today I updated my blog. When I originally started blogging a year ago, I really knew nothing about blogging or shared hosting or Typo, this particular blog engine. After a period prolonged procrastination, I decided that the only way to learn was to do. So, after a little research, I signed up with Site5 hosting and used their auto-installer to give me a Typo 2.6 blog.

Recently, I had some problems adding an article. In fact, I never could add the last article through the application; I kept getting an ‘Application Error’. I checked the logs and found out where it was bombing, but it didn’t help me. I decided at that point that I really needed to upgrade Typo. Since the last article was in response to a mailing list question, I went ahead and manually added it to the database.

Upgrading your blogging engine can be a little intimidating, especially for those who aren’t too savvy in the technologies used. Below is my braindump of the steps I took. I’m writing them down here so I know where to find them in the future.

I found a particularly handy blog on theblatherskite that helped me out. The steps I took were a little different, but based on his upgrade. The most important thing was I wanted to keep my old blog intact in case I goofed. Which means:

1. Create a subdomain. I wouldn’t recommend installing over your production copy. Site5’s administrative interface allows you to create new subdomains, I called my dev.brandonbyars.com.

2. Backup your data. When I tried, however, I was getting an error about connecting to the MySQL socket. After a little googling, I found out that the default shell Site5 gives you (appropriately called ‘jailshell’) prevents you from doing a mysqldump. I emailed Site5 support, and within a very short amount of time they gave me a bash shell. Here’s the command:

-bash-3.00$ mysqldump -u username -p dbname > schema.sql

3. Get Typo. I decided to be brave and go for the trunk instead of the latest stable version.

-bash-3.00$ cd ~
-bash-3.00$ mkdir apps
-bash-3.00$ cd apps
-bash-3.00$ mkdir devblog
-bash-3.00$ svn co http://svn.typosphere.org/typo/trunk

Except it didn’t work… It downloaded typo and fetched the svn:externals for rails just fine. Except it kept timing out the connection trying to fetch the svn:externals for rspec. After doing a little svn propget investigation, I noticed that typo and rails exposed their repositories over http, while rspec exposed theirs over the svn protocol. Once again I emailed Site5 support, and once again they were very responsive in helping me. The tech whitelisted the svn port, and I was able to get typo.

4. Link your subdomain to your new typo blog

ln -s ~/apps/devblog/public ~/public_html/devblog

5. Create your new database. I used Site5’s admin site to do this. After creating the database and granting your user privileges, the following command should work:

mysql -u username -p dbname < schema.sql

6. Setup your config files. Change database.yml to point to your new database, and remove the commented out line setting RAILS_ENV in environment.rb.

7. Migrate your database.

-bash-3.00$ cd ~/apps/devblog
-bash-3.00$ rake db:migrate RAILS_ENV=production

That didn’t work. It complained I was missing rcov. I followed the directions to install local gems, installed rcov using ‘gem install rcov’, and tried again. No dice.

So I migrated the database locally. As I was doing all this, I was also setting up a local copy of my blog anyhow, so this wasn’t a big deal. After creating the local db and migrating it (which worked just fine), I deleted the sessions table (which was the bulk of the database), and copied the mysqldump over to my production blog host. I recreated my new devblog database using that dump.

8. Move over your extra files. I keep all extra files that you can download from my blog in the files directory of the public folder.

-bash-3.00$ cp -r ~/rails_auto_apps/blog/public/files ~/apps/devblog/public

9. Restart.

pkill -9 -u brandonb -f dispatch.fcgi

10. Login to your blog’s administrative section using your subdomain. My old theme was no longer there, so I had to change themes. Make sure everything still works.

11. Point your production blog to you new blog.

-bash-3.00$ rm ~/public_html/blog
-bash-3.00$ ln -s ~/apps/devbog blog

Now you’re set. And just in case, your old blog is entirely intact; all you’ll have to do is relink it from the public_html folder.

Anyway, kudos to Site5, which has been great. I’ll be trying to fix up the formatting and theming of the blog over the couple weeks, so let me know if you find anything that needs fixing.

no comments |

Managing Config Files

Posted by Brandon Byars Fri, 11 Jan 2008 03:39:00 GMT

There's a discussion on the altdotnet Yahoo group about managing configuration files. How do you manage updating multiple configuration files to change the appropriate values when deploying to a different environment?

The solution I hit on was to create a custom MSBuild task (yeah, I screwed up and used MSBuild instead of NAnt). When called from our build script, it looks something like this:

<ItemGroup>

    <ConfigFiles Include="$(DeployDir)/**/*.exe.config"/>

    <ConfigFiles Include="$(DeployDir)/**/*.dll.config"/>

    <ConfigFiles Include="$(DeployDir)/**/web.config"/>

</ItemGroup>

 

<ItemGroup>

    <HibernateFiles Include="$(DeployDir)/**/hibernate.cfg.xml"/>

</ItemGroup>

 

<ItemGroup>

    <Log4NetFiles Include="$(DeployDir)/**/log4net.config"/>

</ItemGroup>

<Target Name="UpdateConfig">

      <UpdateConfig

          ConfigFiles="@(ConfigFiles)"

          ConfigMappingFile="$(MSBuildProjectDirectory)\config\config.xml"

          Environment="$(Environment)"

      />

      <UpdateConfig

        ConfigFiles="@(HibernateFiles)"

        ConfigMappingFile="$(MSBuildProjectDirectory)\config\hibernate_config.xml"

        Environment="$(Environment)"

        NamespaceUri="urn:nhibernate-configuration-2.2"

        NamespacePrefix="hbm"

  />

      <UpdateConfig

        ConfigFiles="@(Log4NetFiles)"

        ConfigMappingFile="$(MSBuildProjectDirectory)\config\log4net_config.xml"

        Environment="$(Environment)"

  />

  </Target>

Notice that each call to UpdateConfig takes the list of config files that will be changed and a config mapping file. That mapping file is what is read to update the config files given the environment. Here's an example of what the mapping file looks like:

<configOptions>

    <add xpath="configuration/appSettings/add[@key='dbserver']">

        <staging>

            <add key="dbserver" value="stagingServer"/>

        </staging>

        <production>

            <add key="dbserver" value="productionServer"/>

        </production>

    </add>

</configOptions>

Each config file is scanned looking for each XPath expression in the mapping file. On each match, the entire node (and all its child nodes) of the original config file are replaced with the node under the appropriate environment tag in the mapping file. It's a bit verbose, but simple enough, and it supports as many environments as you want to have.

The MSBuild task itself is fairly simple, delegating most of its work to a separate object called XmlMerger:

private void MergeChanges()

{

    foreach (ITaskItem item in ConfigFiles)

    {

        string configFile = item.ItemSpec;

        XmlDocument configFileDoc = LoadXmlDocument(configFile);

        XmlDocument configMappingDoc = LoadXmlDocument(configMappingFile);

 

        XmlMerger merger = new XmlMerger(configFileDoc, configMappingDoc);

        if (!string.IsNullOrEmpty(NamespaceUri) && !string.IsNullOrEmpty(NamespacePrefix))

            merger.AddNamespace(NamespacePrefix, NamespaceUri);

 

        merger.Merge(environment.ToLower());

        configFileDoc.Save(configFile);

    }

}

XmlMerger just finds the nodes that need updating and replaces them from the mapping file. Notice that it also accepts namespace information (see the NHibernate example in the build script snippet above), which is occasionally needed:

public class XmlMerger

{

    private readonly XmlDocument configFile;

    private readonly XmlDocument configMapping;

    private readonly XmlNamespaceManager namespaces;

 

    public XmlMerger(XmlDocument configFile, XmlDocument configMapping)

    {

        this.configFile = configFile;

        this.configMapping = configMapping;

        namespaces = new XmlNamespaceManager(configFile.NameTable);

    }

 

    public void AddNamespace(string prefix, string uri)

    {

        namespaces.AddNamespace(prefix, uri);

    }

 

    public void Merge(string environment)

    {

        foreach (XmlNode mappingNode in configMapping.SelectNodes("/configOptions/add"))

        {

            string xpath = mappingNode.Attributes["xpath"].Value;

            XmlNode replacementNode = FindNode(mappingNode, environment).FirstChild;

            XmlNode nodeToReplace = configFile.SelectSingleNode(xpath, namespaces);

            if (nodeToReplace != null)

            {

                ReplaceNode(nodeToReplace, replacementNode);

            }

        }

    }

 

    private void ReplaceNode(XmlNode nodeToReplace, XmlNode replacementNode)

    {

        nodeToReplace.InnerXml = replacementNode.InnerXml;

 

        // Remove attributes not in nodeToReplace.  There's probably a cleaner solution,

        // but I didn't see it.

        for (int i = nodeToReplace.Attributes.Count - 1; i >= 0; i--)

        {

            if (replacementNode.Attributes[nodeToReplace.Attributes[i].Name] == null)

                nodeToReplace.Attributes.RemoveAt(i);

        }

        foreach (XmlAttribute attribute in replacementNode.Attributes)

        {

            if (nodeToReplace.Attributes[attribute.Name] == null)

            {

                nodeToReplace.Attributes.Append(configFile.CreateAttribute(attribute.Name));

            }

 

            nodeToReplace.Attributes[attribute.Name].Value = attribute.Value;

        }

    }

 

    private XmlNode FindNode(XmlNode node, string xpath)

    {

        XmlNode result = node.SelectSingleNode(xpath);

        if (result == null)

            throw new ApplicationException("Missing node for " + xpath);

        return result;

    }

}

That's it. Now the whole process is hands-free, so long as you remember to update the mapping file when needed. The config files we put into subversion are set to work in the development environment (everything is localhost), so anybody can checkout our code and start working without having to tweak a bunch of settings first. The deployment process calls our build script, which ensures that the appropriate config values get changed.

Posted in , | no comments |