Skip to content

Customizing User Data

Wojciech Gaca edited this page Feb 4, 2015 · 9 revisions

Out of the box, Asgard will generate a generic launch configuration for ASGs that exports some variables containing information about the ASG. However, it is possible to customize the generated user data with a Groovy based plugin.

User data defaults

The following is an example of the user data generated by Asgard by default

export CLOUD_ENVIRONMENT=The value of cloud.accountName in your Config.groovy file, default is prod
export CLOUD_MONITOR_BUCKET=Either the application name or the cluster name, depending on the application setup
export CLOUD_APP=The application name
export CLOUD_STACK=Value of stack provided when creating the ASG
export CLOUD_CLUSTER=Name of the cluster, derived from the ASG name
export CLOUD_AUTO_SCALE_GROUP=Name of the autoscaling group
export CLOUD_LAUNCH_CONFIG=Name of the launch config
export EC2_REGION=Region code for this launch config

#The following variables are exported if they were specified when creating the ASG
export CLOUD_COUNTRIES=
export CLOUD_DEV_PHASE=
export CLOUD_HARDWARE=
export CLOUD_PARTNERS=
export CLOUD_REVISION=

For implementation details, you can consult the source code for DefaultAdvancedUserDataProvider which for delegates to DefaultUserDataProvider in order to support legacy Asgard plugins.

Customizing user data

Asgard has a plugin mechanism based on Spring's Groovy bean support which can overwrite the default implementation of the advanced user data provider.

In the ASGARD_HOME directory (~/.asgard by default), create a plugins directory. In this directory, any .groovy files will be read in as Spring beans. You can use Spring features like @Autowired annotations to pull in other internal Asgard services into your plugin class or implement InitializingBean to trigger initialization logic.

Asgard contains an interface com.netflix.asgard.plugin.AdvancedUserDataProvider with a single method:

    String buildUserData(LaunchContext launchContext)

The LaunchContext object contains a variety of objects available at deployment time, which you can use as inputs for your custom logic about how to contruct user data in different situations. That set of objects could increase in the future without the need to alter the plugin interface or your implementation.

For example, Netflix uses a customized strategy for creating user data strings that is conditional on a certain characteristic of the Amazon Machine Image (AMI) getting launched. That conditional logic is provided as an open source example at NetflixAdvancedUserDataProvider. That logic isn't recommended for use outside of Netflix, but it shows the sort of things you can do. One branch of the Netflix user data logic delegates to the legacy UserDataProvider plugin interface where the implementation is closed source (it calls Perforce and concatenates a bunch of complex files in an ugly way.) The other branch of the logic uses the DefaultUserDataProvider class directly without the legacy plugin interface.

You can create a Groovy class in the plugins directory which implements the AdvancedUserDataProvider interface. Asgard can be told to use this plugin as the user data provider by adding an entry to the Config.groovy file in ASGARD_HOME:

plugin {
    advancedUserDataProvider = 'initechAdvancedUserDataProvider'
}

The value of advancedUserDataProvider will have to match the name of the Spring bean you store in your ~/.asgard/plugins/ directory (usually the class name as lower camel case).

Contrived example plugin:

import com.netflix.asgard.userdata.DefaultUserDataProvider
import com.netflix.asgard.UserContext
import com.netflix.asgard.plugin.AdvancedUserDataProvider
import com.netflix.asgard.model.LaunchContext
import javax.xml.bind.DatatypeConverter
import com.amazonaws.services.ec2.model.Image

/**
 * Builds user data Initech style.
 */
class InitechAdvancedUserDataProvider implements AdvancedUserDataProvider {

    String buildUserData(LaunchContext launchContext) {
        // Start with Asgard's default user data
        UserContext userContext = launchContext.userContext
        String appName = launchContext.application?.name ?: ''
        String asgName = launchContext.autoScalingGroup?.autoScalingGroupName ?: ''
        String lcName = launchContext.launchConfiguration?.launchConfigurationName ?: ''
        DefaultUserDataProvider defaultProvider = new DefaultUserDataProvider()
        String encoded = defaultProvider.buildUserDataForVariables(userContext, appName, asgName, lcName)
        String defaultUserData = new String(DatatypeConverter.parseBase64Binary(encoded))

        // Add company specific stuff
        String initechMantra = "export IS_THIS_BEST_FOR_THE_COMPANY=true\n"
        Image image = launchContext.image
        String imgDesc = "export IMAGE_DESC=${image.description ?: ''}\n"
        String concatenated = defaultUserData + initechMantra + imgDesc
        DatatypeConverter.printBase64Binary(concatenated.bytes)
    }
}
Clone this wiki locally