Automated Coder

Exploring the Code of CruiseControl.Net

Archive for September 5th, 2008

A Whole New World of Configuration

Posted by Craig Sutherland on 5 September, 2008

A Quick Review

In my last post I looked at where the configuration is stored, how it is converted into objects and started looked at how to extend the configuration. However the extensions only handled extending the existing interfaces.

In this post I’ll look at what needs to be done to add a new interface. And then I’ll look at breaking out of project-based configuration altogether!

It’s Not a Task, Trigger or Source Control Block

As part of the security extensions we want to be able to configure individual projects. Therefore it would be nice to add the following type of configuration:

<project>
   <!-- Rest of the project configuration -->
   <security defaultRight="Denied" type="default">
      <assertions>
         <userAssertion name="johndoe" permission="ForceBuild" right="Allowed"/>
      </assertions>
   </security>
</project>

This is a completely different type of config section from any existing ones. So Ican’t just use what has gone before. Now the question is, how is it done?

First I added a couple of interfaces, plus some enumerations to use in them (see below). Note: these are merely example types for this post, when I do my series on the security implementation I’ll look at how it’ll actually be implemented.

Configuration Security Types
Configuration Security Types

The two enumerations are for the allowed permissions and the allowed rights. IProjectSecurity maps to the <security> element, while ISecurityAssertion maps to the <user> element. The next step is to add a couple of classes that implement these interfaces: ProjectSecurity and UserAssertion. For the moment these classes only have the properties required by the interfaces.

Now I can start exposing these to Exortech.NetReflector (since this is the library that handles XML de-serialisation in CruiseControl.Net). The first thing to do is register the types. This is done by adding a ReflectorType attribute to each of the implementation classes (not the interfaces unlike WCF). For example for ProjectSecurity I added:

[ReflectorType("default")]

And for UserAssertion I added:

[ReflectorType("userAssertion")]

For ProjectSecurity the name matches the type attribute and for UserAssertion it matches the tag name. This is how Exortech.NetReflector knows to map between the type and the XML (I’ll explain the difference later on). Each property is then marked with a ReflectorProperty attribute. The DefaultRight property has the allowing attribute:

[ReflectorProperty("defaultRight", Required = true)]

And the Assertions property has the following attribute:

[ReflectorProperty("assertions", Required = true)]

Again, the name matches either the element name or the attribute name in the XML.Notice I’ve also made this property required – Exortech.NetReflector will validate the XML in this case. If it wasn’t required I could have set this to false.

A couple of notes:

  • XML is case-sensitive – the attribute name must match what is in the XML, otherwise it won’t be able to map.
  • For arrays the type name comes from the tag name (e.g. UserAssertion is deduced from the <userAssertion> tag. For singletons the type name comes from an attribute – in our case from the type attribute.

And that’s it for decorating my new classes. Next question, how do I add them into the project? This requires modifying Project to include the new type. In this case I’m only going to allow a single security block per project, so I just added a Security property and decorated it with the ReflectorProperty attribute. Since we have a singleton (there’s only ever going to be one security instance), we need to add an attribute telling Exortech.NetReflector which type we’re using. The following shows how this is done:

[ReflectorProperty("security", InstanceTypeKey="type", Required=false)]

The InstanceTypeKey links the attribute (type in this example) with the name of the .Net type. Basically it’s the map to show Exortech.NetReflector the way to go (it took me a while to figure this one out :( )

And now Exortech.NetReflector takes care of everything else for me!

Beyond Projects

The current modification allows us to extend the project configuration. However for authentication I don’t want to have to copy the list of users and their password into every project. I’d rather have some thing like the following:

<cruisecontrol>
   <!-- Project definitions would go here -->
   <userAuthentication>
      <users>
         <userLogin name="johndoe" password="whoami"/>
      </users>
   </userAuthentication>
</cruisecontrol>

I still have our project definitions in the file, but now I also have an userAuthentication section. Most of adding a new configuration area involves the same principals as above (using ReflectorType and ReflectorProperty attributes), but here we have an extra complication. If I try to run CruiseControl.Net with just decorating the types an “Object reference not set” exception occurs. Why?

Well, it turns out that Project is a top level entity in myXML schema, at least from Exortech.NetReflector’s viewpoint. NetReflectorConfigurationReader iterates through each node under the <cruisecontrol> node in our config file and expects it to be an IProject. It then loads this into the Projects array in the Configuration instance. Since our configuration doesn’t implement IProject an exception occurs when it is added to the projects array. This is no good for me!

To add a non-project area to the configuration I needed to modify IConfiguration, Configuration and NetReflectorConfigurationReader. IConfiguration and Configuration were modified to include an authentication property. NetReflectorConfigurationReader was modified to read in the node, check to see which interfaces it implemented (i.e. IProject or my new authentication interface) and then put the new instance in the correct place.

A little bit of extra work, but now I have everything in place for expanding the configuration.

Some Rules

Since it took me a while to figure this out, I thought I’d better summarise the rules I learnt along the way.

  • XML is case-sensitive. The names for the ReflectorType and ReflectorProperty attributes must match what is in the XML.
  • For array entities, they must have a holder element and the name of the tag must match the ReflectorType attribute name.
  • For singleton entities, they must have a type attribute in the holder element. This attribute must be mapped in the ReflectorProperty attribute and the value of this attribute must match the ReflectorType attribute name.
  • ReflectorType attributes must be unique! You cannot have the same name for a ReflectorType attribute in the code, otherwise Exortech.NetReflector doesn’t know which type to load.
  • Classes that are exposed this way must have a parameter-less constructor.
  • Properties must have both a getter and a setter for the property (of they can be a field).

These are the rules I figured out while writing this post. There may be others, but I haven’t found them yet ;)

Where To Next?

Now that I’ve figured out how to extend the configuration, it’s time to actually add some extensions. Before I move onto implementing the security I want to add some extra functionality to the queues. But as this functionality requires some additional configuration, I’ll cover it next. Then I can finally move onto implementing some security!

To be continued…

Posted in CruiseControl.Net | Tagged: | 1 Comment »