asgard icon indicating copy to clipboard operation
asgard copied to clipboard

User-defined Metadata for ASG Creation, ASG NextGroup & SWF AutoDeployment

Open Xorlev opened this issue 10 years ago • 10 comments

This change threads a new parameter through from all the places ASGs are built. userMetaData is a map of strings that can be defined during REST API calls, either to ASG creation, nextGroup, or autodeployment. In its default implementation, it does nothing, merely threads it through to the LaunchTemplateService and embeds it inside the LaunchContext to be accessed by userdata plugins. Once inside the LaunchContext, it's available for extensions to AdvancedUserDataProvider.

Our use case

Our shop doesn't use AMIs for versions yet. However, we love sequential ASGs and autodeployment flows with judgement steps and use them daily at the tradeoff of not specifying a particular artifact ID (always latest). Each deploy we need to start passing through an artifact ID for our base AMI to fetch, especially for fast rollbacks and safe autoscaling.

This change allows us to write an AdvancedUserDataProvider that adds all of the userMetaData KV pairs to the map of properties the PropertiesUserDataProvider normally writes as environmental variables. This way, we can pass in our APP_REVISION as an environmental variable (from a REST call) and our AMI can pull the proper artifact.

The change is general enough to support a variety of new use cases I haven't even thought of yet.

It's important to note that this has no bearing on the UI at present and does not copy userMetaData from ASG to ASG without explicit REST calls.

Xorlev avatar Jun 18 '14 05:06 Xorlev

asgard-pull-requests #453 FAILURE Looks like there's a problem with this pull request

asgard-pull-requests #454 UNSTABLE Looks like there's a problem with this pull request

asgard-pull-requests #455 FAILURE Looks like there's a problem with this pull request

Hi @Xorlev, it seems like a reasonable pull request, and thank you for taking the time to put it together. At this point, we are beyond a tipping point with the work that we are doing toward Asgard 2.0, and I'm afraid there isn't a code-based migration path. Given that, we won't be adding any new, non-trivial functionality to this version of Asgard.

We have an open source story for Asgard 2.0, and exactly the code that you've provided has a very real fit in the Asgard of the future. We're working diligently to get that code into a position that is open source-able, and I expect that you will hear more about our plans in the coming months.

Thanks again for the effort.

danveloper avatar Jun 18 '14 07:06 danveloper

Sorry to hear that.

Is there a timeline for Asgard 2.0? Until then, we'll just be maintaining a fork, but I imagine these changes would be helpful for other organizations who've used Asgard, given that it's been pitched as a better way to deploy on AWS. I understand that Netflix will not be contributing changes to Asgard 1.x, but the community you've built does seem interested.

Additionally, is it Netflix's intention to build Asgard 2.0 internally and only open source it ex post facto, vs. developing it in the open where the community could enrich the next iteration?

Xorlev avatar Jun 18 '14 18:06 Xorlev

I certainly see the value for other organizations using Asgard, and I think even the pattern of implementation fits well into the new model.

We'll be developing Asgard 2.0 in the open, and we'll be much better situated to engage the community than we have been with Asgard 1. We're taking all of the lessons learned in both technologies and open source project management and applying them wholesale to the coming changes.

Some more details on what we're doing here: https://github.com/Netflix/asgard/issues/486#issuecomment-43157790

We haven't made any official announcements about this yet, as we're still in a sprint to gain feature parity with the existing systems. Presently, we feel that we are close to that, and once that foundation is set we'll be in a better position to support our open source position. As soon as we're internally confident, we'll publish a TechBlog article outlining the roadmap.

danveloper avatar Jun 18 '14 23:06 danveloper

@Xorlev can you share an example request on how you are passing in custom user-data? I am trying to do something very similar.

kalosoid avatar Sep 09 '14 14:09 kalosoid

Sure:

curl -XPOST https://asgard.company.com/us-east-1/cluster/createNextGroup --data "name=my-asg-cluster&userMetaData.CLOUD_REVISION=33&selectedLoadBalancersForVpcId=myELB&min=3&max=9&checkHealth=1"

Where any property after userMetaData.<property> is then pushed into our environmental variables by a custom AdvancedUserDataProvider. Without this, the above metadata does nothing.

Note: I have made some improvements since posting this PR. I'll get the changes out of our fork and merge them into this PR. It was mainly bug fixes (places the metadata wasn't wired up properly).

Basic modification of the existing impl:

/**
 * Custom impl which mostly copies the default impl, just overlaying userMetaData on top of the generated properties
 * before emitting the export statements. Also quotes values.
 */
class FCAdvancedUserDataProvider implements AdvancedUserDataProvider {
    @Autowired
    ConfigService configService

    @Autowired
    ApplicationService applicationService

    @Override
    String buildUserData(LaunchContext launchContext) {

        // Call the legacy plugin by default, because that is what many users have already overwritten.
        UserContext userContext = launchContext.userContext
        String groupName = launchContext.autoScalingGroup?.autoScalingGroupName
        String launchConfigName = launchContext.launchConfiguration?.launchConfigurationName
        String appName = Relationships.appNameFromGroupName(groupName)

        buildUserDataForVariables(userContext, appName, groupName, launchConfigName, launchContext.userMetaData)
    }

    String buildUserDataForVariables(UserContext userContext, String appName, String autoScalingGroupName,
                                     String launchConfigName, Map<String, String> userMetaData) {

        Map<String, String> props = mapProperties(userContext, appName, autoScalingGroupName, launchConfigName)

        if (userMetaData) {
            props += userMetaData.collectEntries { k, v -> [k.toUpperCase(), v] }
        }

        String result = props.collect { k, v ->
            if (!v.startsWith("\"")) v = "\"$v\""

            "export ${k.toUpperCase()}=${v}"
        }.join('\n') + '\n'
        DatatypeConverter.printBase64Binary(result.bytes)
    }

    /**
     * Creates a map of environment keys to values for use in constructing user data strings, based on the specified
     * cloud objects associated with the current deployment.
     *
     * @param userContext who, where, why
     * @param appName the name of the application being deployed
     * @param autoScalingGroupName the name of the ASG which will launch and manage the instances
     * @param launchConfigName the name of the launch configuration for launching the instances
     * @return a map of keys to values for the deployment environment
     */
    Map<String, String> mapProperties(UserContext userContext, String appName, String autoScalingGroupName,
                                      String launchConfigName) {
        Names names = Names.parseName(autoScalingGroupName)
        String monitorBucket = applicationService.getMonitorBucket(userContext, appName, names.cluster)
        String appGroup = applicationService.getRegisteredApplication(userContext, appName)?.group
        [
                (prependNamespace(UserDataPropertyKeys.ENVIRONMENT)): configService.accountName ?: '',
                (prependNamespace(UserDataPropertyKeys.MONITOR_BUCKET)): monitorBucket ?: '',
                (prependNamespace(UserDataPropertyKeys.APP)): appName ?: '',
                (prependNamespace(UserDataPropertyKeys.APP_GROUP)): appGroup ?: '',
                (prependNamespace(UserDataPropertyKeys.STACK)): names.stack ?: '',
                (prependNamespace(UserDataPropertyKeys.CLUSTER)): names.cluster ?: '',
                (prependNamespace(UserDataPropertyKeys.AUTO_SCALE_GROUP)): autoScalingGroupName ?: '',
                (prependNamespace(UserDataPropertyKeys.LAUNCH_CONFIG)): launchConfigName ?: '',
                (UserDataPropertyKeys.EC2_REGION): userContext.region.code ?: '',
        ] + Relationships.labeledEnvVarsMap(names, configService.userDataVarPrefix)
    }

    private String prependNamespace(String key) {
        "${configService.userDataVarPrefix}${key}"
    }
}

Xorlev avatar Sep 09 '14 15:09 Xorlev

@Xorlev Thank you sir!

kalosoid avatar Sep 09 '14 15:09 kalosoid

asgard-pull-requests #468 FAILURE Looks like there's a problem with this pull request