-
Notifications
You must be signed in to change notification settings - Fork 24
Environment Platform Specific Properties
- Introduction
- Two Solutions
- Solution for Mach-II 1.6 or less
- Solution for Mach-II 1.8+
- Configuring the Environment Property
- Additional Information and Considerations
-
Comments
- Comment by anonymous on Thu 19 Feb 2009 05:31:35 AM EST
- Comment by peterfarrell on Thu 19 Feb 2009 12:24:27 PM EST
- Comment by peterfarrell on Thu 19 Feb 2009 03:14:05 PM EST
- Comment by anonymous on Wed 11 Mar 2009 11:12:10 PM UTC
- Comment by anonymous on Wed 11 Mar 2009 11:12:53 PM UTC
- Comment by mattwoodward on Thu 12 Mar 2009 10:17:49 PM UTC
- Comment by mattwoodward on Fri 13 Mar 2009 12:14:30 AM UTC
- Comment by peterfarrell on Wed 25 Mar 2009 07:44:03 AM UTC
It is very common situation that your application requires specific properties or settings based on where your application is deployed (i.e. development, staging, production). A lot of the time, properties such as datasource names or boolean flags to turn on caching / logging are different when your application is designed to be deployed to different environments. The solution to these settings can vary from extremely simple to complex however they share a common thread. In this wiki entry, we will examine the most basic method of setting these properties.
Mach-II 1.8 ships with a bundled environment property that allows you to set environment properties based on the server name where the application is deployed. Any Mach-II applications using Mach-II 1.6 or less must roll their own solutions.
Mach-II 1.5 introduced a Property CFCs that allowed you to write properties that were CFCs and not just static name / value pairs as in older version of Mach-II. A Property.cfc is the perfect Mach-II hook to allow us to set properties based on the environment our application is getting deployed to. If you are unfamiliar with Property CFCs, we encourage you read up on Property CFCs.
We define an Environment
Property.cfc in the Mach-II XML configuration
file like this:
<mach-ii>
<properties>
<property name="environment" type="dot.path.to.EnvironmentProperty" />
</properties>
... other XML sections ...
</mach-ii>
As you can see, we are registering a CFC named EnvironmentProperty
to
a Mach-II property named environment
.
We've already registered a property, but we have not looked at what our property does for us. Remember this is an example at the simplest and could be further expanded to support additional environments or configured via an XML file for easier configuration.
<cfcomponent
extends="MachII.framework.Property"
output="false"
hint="Provides configuration settings based on deployment environment.">
<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="configure" access="public" returntype="void" output="false"
hint="Sets up the configuration settings.">
<cfset loadEnvironment() />
</cffunction>
<!---
PROTECTED FUNCTIONS
--->
<cffunction name="loadEnvironment" returntype="void" output="false"
hint="Loads environment specific properties in the property manager.">
<!---
Asking for the server name of the CGI is bad however the
workarounds are no better
--->
<cfset var domain = cgi.SERVER_NAME />
<!--- DEVELOPMENT / STAGING --->
<cfif ListFindNoCase("myApp,myAppDev.example.com", domain)>
<cfset setProperty("dsn", "myApp_dev") />
<cfset setProperty("svrDevMode", true) />
<cfset setProperty("svrCachingEnabled", false) />
<cfset setProperty("svrLoggingEnabled", true) />
<!--- PRODUCTION (is the default for safety) --->
<cfelse>
<cfset setProperty("dsn", "myApp_prod") />
<cfset setProperty("svrDevMode", false) />
<cfset setProperty("svrCachingEnabled", true) />
<cfset setProperty("svrLoggingEnabled", false) />
</cfif>
</cffunction>
</cfcomponent>
The CFC simply calls the loadEnvironment
method when Mach-II
automatically calls the configure
method. This sets properties based
on the server name using the server name from the cgi scope. By default,
we load the production properties for safety in case the server we are
deploying the application to is not in the list of development or
staging environments and we do not want to expose any additional
debugging information to the outside world if this situation occurs.
Beginning with Mach-II 1.8, the framework ships with an environment property CFC that allows you to set the environment mode in the framework and corresponding properties. The environment property uses the server name to detect and load the correct environment properties based on where the application is loaded.
The property provides the ability to set properties for four deployment
environments development
, staging
, qualityAssurance
and
production
as supported by the core framework. Also, the property
plays nice when used in a module by using the environment mode from the
parent application when this property is defined in a module.
The environment property is easy to configure by defining it in your
configuration file. While the property offers the ability to configure
as many environments as you wish. A lot of developers only utilize the
development
and production
environments. If you do not need to use a
particular environment, simply do not define it in your configuration
file.
The property allows for an unlimited number of environments to be setup. Each environment can be named anything except for a short list of reserved names as they conflict with other parameter names used by environment property.
The names are reserved and cannot be used as names for environments:
defaultEnvironmentName
serverPropertyName
The property provides the ability to set properties for five deployment
environment groups local
, development
, staging
, qa
and
production
as supported by the core framework for each environment.
This allows modules to change their behavior based on the environment
groups (i.e. conditionals by environment group name instead of
explicitly checked for a concert environment name) instead of looking
for a specific environment name.
Environments are resolved by server name (see serverKeyName
). Since
the property allows for environment resolution with server patterns, the
server lists are ordered by environment groups and searched in the
following order:
- production
- qa
- staging
- development
- local
The search order driven by the environment groups (which by default "production" environments should be more "secure" than development environment groups).
Remember that Mach-II loads property CFCs in the order they are defined
in the XML configuration file. Base configuration file first followed by
any include configuration files in the order the includes were defined.
So if propertyA
needs properties set by the EnvironmentProperty
and
the EnvironmentProperty
is defined after propertyA
, the
environment has not been resolved and therefore the environment specific
properties have not been set. The solution is to place your
EnvironmentProperty
before any other properties that may need the
properties defined by the EnvironmentProperty
.
The EnvironmentProperty
uses (via inheritance) the environment name
and group from the base application if used in a module for environment
resolution. This means your environment names in your module must match
the environment names in your base application.
<property name="environment" type="MachII.properties.EnvironmentProperty">
<parameters>
<!-- Optional: Name of default environment to use if no server matches -->
<parameter name="defaultEnvironmentName" value="production" />
<!-- Optional: Name of property to place in name of the resolved server -->
<parameter name="serverPropertyName" value="serverName" />
<!-- Optional (in Mach-II 1.9.5+): Name of the key containing the server name -->
<parameter name="serverKeyName" value="cgi.SERVER_NAME" />
<!-- Name of environment (can be any name as 'dev-main' is an example) -->
<parameter name="dev-main">
<struct>
<!-- Name of generic environment group to assign this environment to -->
<key name="environmentGroup" value="development" />
<!-- List or array of developer servers -->
<key name="servers" value="dev01.example.com,dev02.example.com" />
- or -
<key name="servers">
<array>
<element value="dev01.example.com" />
<element value="dev02.example.com" />
</array>
</key>
<!-- Struct of development properties to set -->
<key name="properties">
<struct>
<key name="" value="" />
</struct>
</key>
</struct>
</parameter>
<parameter name="production-main">
<struct>
<!-- Name of generic environment group to assign this environment to -->
<key name="environmentGroup" value="production" />
<!-- List or array of developer servers -->
<key name="servers" value="prod01.example.com,prod02.example.com" />
- or -
<key name="servers">
<array>
<element value="prod01.example.com" />
<element value="prod02.example.com" />
</array>
</key>
<!-- Struct of development properties to set -->
<key name="properties">
<struct>
<key name="" value="" />
</struct>
</key>
</struct>
</parameter>
</parameters>
</property>
The defaultEnvironmentName
parameter optionally indicates which
environment to load if there is no server name to another environment
when loaded. For security, if no parameter is defined the environment
property will throws an exception if no environment can be resolved. If
the defaultEnvironmentName
parameter is defined, the default
environment will be loaded if no server can be resolved against the
defined server names in all environments.
The serverPropertyName
parameter optionally sets the name of the
property used to populate the name of the server found when resolving
environments. Defaults to serverName
.
In Mach-II 1.9.5, we added an optional serverKeyName
parameter that
defines the key containing the server name. By default,
EnvironmentProperty
will use the value stored in cgi.SERVER_NAME
.
However, the server name can be resolved using a Mach-II property
(properties.*
), an HTTP request header (headers.*
), or value from
the CGI scope (cgi.*
). For example, if the server name is being passed
in an HTTP header by a load balancer or application proxy as
X-Server-Name: app01.example.com
, the EnvironmentProperty
can access
the value using a serverKeyName
of headers.X-Server-Name
. Another
example: a property is defined earlier in the Mach-II configuration file
that performs the server name resolution and places the result into a
Mach-II property named resolvedServerName
. The EnvironmentProperty
will use this value by setting serverKeyName
to
properties.resolvedServerName
.
The useResolvedEnvironmentNameFromParent
parameter optionally changes
the behavior of the environment property when it is defined in a module.
This parameter only applies to environment properties defined in modules
and is completely ignored if set in a base application. By default the
value of this parameter is true
. When set to true
, the environment
name when resolving the environment for the module is inherited from the
base application. This allows you set all the server names in the base
application and leave the environment name resolution to the parent
application environment property. You must have a parent environment
property defined for this to work otherwise set the value to false
if
you want to have independent environment name resolution. When set to
true
, the names of the environments defined the module environment
property must correspond to the same environment names defined in the
parent application.
The environmentGroup
key takes a value from the list
[local|development|staging|qa|productionServers] that is used to
indicate the environment group in which the environment belongs to. This
allows for modules, filters, plugins, etc. to change their behavior
based on which 'generic' environment the resolved and loaded environment
belongs to.
The servers
key takes a list or array of server names (see
serverKeyName
) that are designated for the named environment. This key
supports basic pattern matching using the * wilcard which is useful if
you deploy to a cluster (i.e. web*.cluster.example.com would match
web01.cluster.example.com). For more information on how to use patterns
when defining servers, check out the simple pattern matching with the
SimplePatternMatcher.cfc
documentation.
This key is optional in the
EnvironmentProperty is defined in a module and the
useResolvedEnvironmentNameFromParent`
parameter is true (which it by default). This is because the environment
name is gotten from the parent base application instead being resolved
in the module.
The properties
key takes an struct of properties to be set. Each key
in the properties struct can take complex datatypes like structs and
arrays. These properties are set in the Mach-II property manager
(<property>
) and are accessed like all other properties using API
methods like getProperty()
.
While the EnvironmentProperty
takes care of resolving the environment
and loading the properties, the core framework gives you access to
environment information such as the current environment name and
environment group. From any Mach-II extended component (i.e. listeners,
filters, plugins and properties), you can access this information:
getAppManager().getEnvironmentName()
getAppManager().getEnvironmentGroup()
In Mach-II Integrity (1.9), we added helper methods available from the
appManager
and from inside views to check if the application is
currently in an environment group or name.
Calling the appManager
getAppManager().inEnvironmentGroup(listOrArrayOfGroupNames)
getAppManager().inEnvironmentName(listOfArrayOfNames)
Inside views:
inEnvironmentGroup(listOrArrayOfGroupNames)
inEnvironmentName(listOfArrayOfNames)
- Remember that the
configure
method of Property CFCs are run in the order the Property CFCs are defined in the Mach-II XML configuration file. If another Property CFC depends on values set by your environment property, be sure that your environment property is defined above the other Property CFC. This is especially important if you are using ColdSpring and want to use the${db.dsn}
style parameter placeholders offered by ColdSpring. If your environment property is defined below the ColdSpring property, ColdSpring will not be able to resolve those placeholders because the properties have not been set yet by your environment property. - A more sophisticated environment property might set properties based off an user defined XML file for easier maintenance instead of using CFML syntax as demonstrated above.
Can you have multiple development environments as each developer may have different paths or dsns locally?
No you cannot. This really falls into the idiom that all environments mirror each other as closely as possible with only minor changes between them. This is really a best practice as everybody should have a common setup otherwise you run the huge risk of introducing scary edge case defects (which is more common than you might think). We don't want to promote this type of "dangerous" environment setup so we are opting for the more enterprise development idiom.
Just wanted to clarify what I meant by "minor changes" between
environments. This would be things like properties values such as all
environments have a dsn
property -- it is the value that differs
between the environments. I'd recommend having all developers each the
same dsn name for local development (depends on if you use a shared
database server for local dev or each developer has a local database
server on their development machine).
To answer your paths, you should strive for using relative paths that
are computed from the root of your application (you can define the root
with something like expandPath('.')
. IMO, you're just asking for a
fight if you use absolute paths.
Couldn't you allow generic *Servers and matching *Properties parameters so folks could have as many tiers as they want - named however they want?
At macromedia.com, we had localhost, six different shared dev environments, six matching shared QA environments, integration (pre-production) and production - because we had multiple streams of development going on at the same time.
If you allowed any fooServers / fooProperties parameters to match, you could cater to a more flexible environment setup instead of just "forcing" development / staging / QA / production on folks.
Weird, I gave it an email address and it still said I was anonymous.
-- Sean Corfield
Peter and I discussed this at some length this afternoon and think we came up with a more flexible alternative that will still give us some sense of order with respect to things like integration of third-party plugins, modules, etc.
The issue is if everything's literally a free-for-all then it's a constant battle of updating third-party code. The obvious example is the M2 dashboard. It probably makes sense to allow people to disable the login for local and dev boxes. But if they need to modify the environment to match theirs, then auto-updating the dashboard (which is a feature that's forthcoming--this is auto-updating the dashboard mind you, not M2 as a whole) becomes tricky because it would break on every update.
So what Peter and I came up with is modifying what we have now to support a name for a group of servers, which can be anything, as well as an environmentGroup, which will be one value from a prescribed list.
I'll send an email out to the list this evening with some details and to ask for feedback on the specific environmentGroup list people would like to see.
Seems to us that this will strike a good balance between flexibility and still having some semblance of order to things. Any additional feedback is welcome.
Also for those interested we do have a Mach-II developers list on Google--I'll send out details to the mailing list.
Following up on the above comment--here are more details we'd love to get feedback on.
What Peter and I came to was that rather than mandating parameter names such as "productionServers", etc. that parameter name can be anything. We are proposing adding a new environmentGroup key, however, that would need to be one of a prescribed list of options. The syntax would be something like this:
<!-- parameter name can be anything -->
<parameter name="mySuperCoolProductionServers">
<struct>
<!-- list of servers in the mySuperCoolProductionServers group -->
<key name="servers" value="prod1.sample.com,prod2.sample.com" />
<!-- value of environmentGroup is one of a specific list of values; if no match 'production' is used -->
<key name="environmentGroup" value="production" />
<!-- property values that apply to this group -->
<key name="properties">
<struct>
<key name="dsn" value="myProductionDSN" />
</struct>
</key>
</struct>
</parameter>
The main reason for the change is if we have no control at all over anything, it becomes difficult to do things like disable the login for the Dashboard module in development environments. It would be simple enough to require people to change a setting to match one in their parent application, but then upgrading Dashboard becomes difficult since the potential for a custom value to be overwritten on the upgrade is high.
So we have the following questions for people:
-
Does this change make sense and will it be useful? Any feedback on syntax or higher-level issues with the EnvironmentProperty? in general?
-
Does "environmentGroup" make sense as a key name? Is "environmentType" or something else better?
-
Regarding the list of environmentGroup/Type values, Peter and I came up with this as a starting point:
local development testing staging qa (or qualityAssurance maybe?) production
Is there other terminology people use that we're missing?
Thanks for any feedback you may have--as always the success of Mach-II depends on your input!
FYI, Matt's comment above has been entered as ticket [#254]