Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

Xorlev
Copy link

@Xorlev Xorlev commented Jun 18, 2014

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 Xorlev changed the title User-defined Metadata for ASG & SWF AutoDeployment User-defined Metadata for ASG Creation, ASG NextGroup & SWF AutoDeployment Jun 18, 2014
@cloudbees-pull-request-builder

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

@cloudbees-pull-request-builder

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

@cloudbees-pull-request-builder

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

@danveloper
Copy link
Contributor

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.

@Xorlev
Copy link
Author

Xorlev commented Jun 18, 2014

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?

@danveloper
Copy link
Contributor

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: #486 (comment)

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.

@kalosoid
Copy link

kalosoid commented Sep 9, 2014

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

@Xorlev
Copy link
Author

Xorlev commented Sep 9, 2014

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}"
    }
}

@kalosoid
Copy link

kalosoid commented Sep 9, 2014

@Xorlev Thank you sir!

@cloudbees-pull-request-builder

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants