Skip to content

Commit

Permalink
Initial commit providing openstack functionality for rosco (#97)
Browse files Browse the repository at this point in the history
provider/openstack: Initial commit providing openstack functionality for rosco.
  • Loading branch information
drmaas authored and Matt Duftler committed May 20, 2016
1 parent 1e9418b commit a636d6d
Show file tree
Hide file tree
Showing 10 changed files with 1,022 additions and 7 deletions.
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,4 @@ allprojects {
}
}


defaultTasks ':rosco-web:bootRun'
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import io.swagger.annotations.ApiModelProperty
/**
* A request to bake a new machine image.
*
* @see BakeryController#createBake
* @see BakeryController#createBake(String, BakeRequest, String)
*/
@Immutable(copyWith = true)
@CompileStatic
Expand Down Expand Up @@ -56,14 +56,15 @@ class BakeRequest {
String ami_name
String ami_suffix
Boolean upgrade
String instance_type

@ApiModelProperty("The explicit packer template to use, instead of resolving one from rosco's configuration")
String template_file_name
@ApiModelProperty("A map of key/value pairs to add to the packer command")
Map extended_attributes

static enum CloudProviderType {
aws, docker, gce
aws, docker, gce, openstack
}

static enum Label {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright 2016 Target, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.rosco.providers.openstack

import com.netflix.spinnaker.rosco.api.Bake
import com.netflix.spinnaker.rosco.api.BakeOptions
import com.netflix.spinnaker.rosco.api.BakeRequest
import com.netflix.spinnaker.rosco.providers.CloudProviderBakeHandler
import com.netflix.spinnaker.rosco.providers.openstack.config.RoscoOpenstackConfiguration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

/**
* TODO(drmaas): need to figure out the following:
* 1) packer openstack variables - required vs optional (currently have a best guess for what is required for v3)
* 2) openstack username/password management (currently will load via environment variable passed to java process)
* 3) v2 vs. v3 apis (currently implemented for v3)
*/
@Component
public class OpenstackBakeHandler extends CloudProviderBakeHandler {

private static final String BUILDER_TYPE = 'openstack'
private static final String IMAGE_NAME_TOKEN = 'openstack: An image was created:'

@Autowired
RoscoOpenstackConfiguration.OpenstackBakeryDefaults openstackBakeryDefaults

@Override
def getBakeryDefaults() {
return openstackBakeryDefaults
}

@Override
BakeOptions getBakeOptions() {
new BakeOptions(
cloudProvider: BakeRequest.CloudProviderType.openstack,
baseImages: openstackBakeryDefaults?.baseImages?.collect { it.baseImage }
)
}

@Override
String produceProviderSpecificBakeKeyComponent(String region, BakeRequest bakeRequest) {
region
}

@Override
def findVirtualizationSettings(String region, BakeRequest bakeRequest) {
def openstackOperatingSystemVirtualizationSettings = openstackBakeryDefaults?.baseImages.find {
it.baseImage.id == bakeRequest.base_os
}

if (!openstackOperatingSystemVirtualizationSettings) {
throw new IllegalArgumentException("No virtualization settings found for '$bakeRequest.base_os'.")
}

def openstackVirtualizationSettings = openstackOperatingSystemVirtualizationSettings?.virtualizationSettings.find {
it.region == region && it.instanceType == bakeRequest.instance_type
}

if (!openstackVirtualizationSettings) {
throw new IllegalArgumentException("No virtualization settings found for region '$region', operating system '$bakeRequest.base_os', and instance_type '${bakeRequest.instance_type}'.")
}

if (bakeRequest.base_ami) {
openstackVirtualizationSettings = openstackVirtualizationSettings.clone()
openstackVirtualizationSettings.sourceImageName = bakeRequest.base_ami
}

return openstackVirtualizationSettings
}

@Override
Map buildParameterMap(String region, def openstackVirtualizationSettings, String imageName, BakeRequest bakeRequest) {
def parameterMap = [
openstack_identity_endpoint: openstackBakeryDefaults.identityEndpoint,
openstack_region: region,
openstack_ssh_username: openstackVirtualizationSettings.sshUserName,
openstack_instance_type: openstackVirtualizationSettings.instanceType,
openstack_source_image_name: openstackVirtualizationSettings.sourceImageName,
openstack_image_name: imageName
]

if (openstackBakeryDefaults.username && openstackBakeryDefaults.password) {
parameterMap.openstack_username = openstackBakeryDefaults.username
parameterMap.openstack_password = openstackBakeryDefaults.password
}

if (openstackBakeryDefaults.domainName) {
parameterMap.openstack_domain_name = openstackBakeryDefaults.domainName
}

if (openstackBakeryDefaults.floatingIpPool) {
parameterMap.openstack_floating_ip_pool = openstackBakeryDefaults.floatingIpPool
}

if (openstackBakeryDefaults.insecure != null) {
parameterMap.openstack_insecure = openstackBakeryDefaults.insecure
}

if (openstackBakeryDefaults.securityGroups != null) {
parameterMap.openstack_security_groups = openstackBakeryDefaults.securityGroups
}

if (openstackBakeryDefaults.tenantName != null) {
parameterMap.openstack_tenant_name = openstackBakeryDefaults.tenantName
}

return parameterMap
}

@Override
String getTemplateFileName() {
return openstackBakeryDefaults.templateFile
}

@Override
boolean isProducerOf(String logsContentFirstLine) {
logsContentFirstLine =~ BUILDER_TYPE
}

@Override
Bake scrapeCompletedBakeResults(String region, String bakeId, String logsContent) {
String imageName

// TODO(duftler): Presently scraping the logs for the image name/id. Would be better to not be reliant on the log
// format not changing. Resolve this by storing bake details in redis and querying oort for amiId from amiName.
logsContent.eachLine { String line ->
if (line =~ IMAGE_NAME_TOKEN) {
imageName = line.split(" ").last()
}
}

new Bake(id: bakeId, image_name: imageName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2016 Target, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.netflix.spinnaker.rosco.providers.openstack.config

import com.netflix.spinnaker.rosco.api.BakeOptions
import com.netflix.spinnaker.rosco.api.BakeRequest
import com.netflix.spinnaker.rosco.providers.openstack.OpenstackBakeHandler
import com.netflix.spinnaker.rosco.providers.registry.CloudProviderBakeHandlerRegistry
import groovy.transform.AutoClone
import groovy.transform.AutoCloneStyle
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration

import javax.annotation.PostConstruct

@Configuration
@ConditionalOnProperty('openstack.enabled')
@ComponentScan('com.netflix.spinnaker.rosco.providers.openstack')
class RoscoOpenstackConfiguration {

@Autowired
CloudProviderBakeHandlerRegistry cloudProviderBakeHandlerRegistry

@Autowired
OpenstackBakeHandler openstackBakeHandler

@Bean
@ConfigurationProperties('openstack.bakeryDefaults')
OpenstackBakeryDefaults openstackBakeryDefaults() {
new OpenstackBakeryDefaults()
}

static class OpenstackBakeryDefaults {
String identityEndpoint
String domainName
String floatingIpPool
String securityGroups
String tenantName
String templateFile
Boolean insecure
String username
String password
List<OpenstackOperatingSystemVirtualizationSettings> baseImages = []
}

static class OpenstackOperatingSystemVirtualizationSettings {
BakeOptions.BaseImage baseImage
List<OpenstackVirtualizationSettings> virtualizationSettings = []
}

@AutoClone(style = AutoCloneStyle.SIMPLE)
static class OpenstackVirtualizationSettings {
String region
String instanceType
String sourceImageName
String sshUserName
}

@PostConstruct
void init() {
cloudProviderBakeHandlerRegistry.register(BakeRequest.CloudProviderType.openstack, openstackBakeHandler)
}

}
Loading

0 comments on commit a636d6d

Please sign in to comment.