Skip to content

Commit

Permalink
feat(bakery): Making bakery service selectable by parameters (#2973)
Browse files Browse the repository at this point in the history
- Using kork v2 selector to shard the bakery based on parameters
  • Loading branch information
jeyrschabu committed Jun 18, 2019
1 parent 3f2095b commit 74539b0
Show file tree
Hide file tree
Showing 10 changed files with 768 additions and 83 deletions.
2 changes: 2 additions & 0 deletions orca-bakery/orca-bakery.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-guava")
implementation("com.netflix.spinnaker.fiat:fiat-core:$fiatVersion")

api("com.netflix.spinnaker.kork:kork-web")

compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2019 Netflix, 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.orca.bakery;

import static com.netflix.spinnaker.orca.bakery.BakerySelector.ConfigFields.*;

import com.netflix.spinnaker.kork.web.selector.v2.SelectableService;
import com.netflix.spinnaker.kork.web.selector.v2.SelectableService.Parameter;
import com.netflix.spinnaker.orca.bakery.api.BakeryService;
import com.netflix.spinnaker.orca.bakery.config.BakeryConfigurationProperties;
import com.netflix.spinnaker.orca.pipeline.model.Execution;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import java.util.*;
import java.util.function.Function;

public class BakerySelector {
// Temporary flag in stage context allowing to specify if the bakery should be selectable
private static final String SELECT_BAKERY = "selectBakery";

private SelectableService<BakeryService> selectableService;
private final BakeryService defaultService;
private final Map<String, Object> defaultConfig;

public BakerySelector(
BakeryService defaultBakeryService,
BakeryConfigurationProperties bakeryConfigurationProperties,
Function<String, BakeryService> getBakeryServiceByUrlFx) {
this.defaultService = defaultBakeryService;
this.defaultConfig = getDefaultConfig(bakeryConfigurationProperties);
this.selectableService =
getSelectableService(bakeryConfigurationProperties.getBaseUrls(), getBakeryServiceByUrlFx);
}

/**
* Selects a bakery based on {@link SelectableFields} from the context
*
* @param stage bake stage
* @return a bakery service with associated configuration
*/
public SelectableService.SelectedService<BakeryService> select(Stage stage) {
if (selectableService == null
|| stage.getContext().get(SELECT_BAKERY) == null
|| !(Boolean) stage.getContext().get(SELECT_BAKERY)) {
return new SelectableService.SelectedService<>(defaultService, defaultConfig, null);
}

final String application = stage.getExecution().getApplication();
final String user =
Optional.ofNullable(stage.getExecution().getAuthentication())
.map(Execution.AuthenticationDetails::getUser)
.orElse("unknown");
final List<Parameter> parameters = new ArrayList<>();

stage
.getContext()
.forEach(
(key, value) -> {
Optional<String> paramName =
SelectableFields.contextFields.stream().filter(f -> f.equals(key)).findFirst();
paramName.ifPresent(
name ->
parameters.add(
new Parameter()
.withName(name)
.withValues(Collections.singletonList(value))));
});

parameters.add(
new Parameter()
.withName(SelectableFields.authenticatedUser)
.withValues(Collections.singletonList(user)));
parameters.add(
new Parameter()
.withName(SelectableFields.application)
.withValues(Collections.singletonList(application)));
return selectableService.byParameters(parameters);
}

private Map<String, Object> getDefaultConfig(BakeryConfigurationProperties properties) {
final Map<String, Object> config = new HashMap<>();
config.put(roscoApisEnabled, properties.isRoscoApisEnabled());
config.put(allowMissingPackageInstallation, properties.isAllowMissingPackageInstallation());
config.put(extractBuildDetails, properties.isExtractBuildDetails());
return config;
}

private SelectableService<BakeryService> getSelectableService(
List<SelectableService.BaseUrl> baseUrls,
Function<String, BakeryService> getBakeryServiceByUrlFx) {
if (baseUrls == null) {
return null;
}

return new SelectableService<>(
baseUrls, defaultService, defaultConfig, getBakeryServiceByUrlFx);
}

interface SelectableFields {
// Allows to shard the bakery based on the current user
String authenticatedUser = "authenticatedUser";

// Allows to shard the bakery based on current application
String application = "application";

// Allows to shard the bakery based on common bake stage fields
List<String> contextFields =
Arrays.asList("cloudProvider", "region", "baseOS", "cloudProviderType");
}

interface ConfigFields {
String roscoApisEnabled = "roscoApisEnabled";
String allowMissingPackageInstallation = "allowMissingPackageInstallation";
String extractBuildDetails = "extractBuildDetails";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package com.netflix.spinnaker.orca.bakery.config

import com.netflix.spinnaker.orca.bakery.BakerySelector
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.boot.context.properties.EnableConfigurationProperties
import retrofit.RequestInterceptor

import java.text.SimpleDateFormat
Expand All @@ -33,7 +35,6 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import retrofit.Endpoint
import retrofit.RestAdapter
import retrofit.RestAdapter.LogLevel
import retrofit.client.Client
Expand All @@ -50,22 +51,30 @@ import static retrofit.Endpoints.newFixedEndpoint
])
@CompileStatic
@ConditionalOnExpression('${bakery.enabled:true}')
@EnableConfigurationProperties(BakeryConfigurationProperties)
class BakeryConfiguration {

@Autowired Client retrofitClient
@Autowired LogLevel retrofitLogLevel
@Autowired RequestInterceptor spinnakerRequestInterceptor

@Bean
Endpoint bakeryEndpoint(@Value('${bakery.base-url}') String bakeryBaseUrl) {
newFixedEndpoint(bakeryBaseUrl)
BakeryService bakery(@Value('${bakery.base-url}') String bakeryBaseUrl) {
return buildService(bakeryBaseUrl)
}

@Bean
BakeryService bakery(Endpoint bakeryEndpoint) {
static ObjectMapper bakeryConfiguredObjectMapper() {
def objectMapper = new ObjectMapper()
.setPropertyNamingStrategy(new SnakeCaseStrategy())
.setDateFormat(new SimpleDateFormat("YYYYMMDDHHmm"))
.setSerializationInclusion(NON_NULL)
.disable(FAIL_ON_UNKNOWN_PROPERTIES)

}

new RestAdapter.Builder()
.setEndpoint(bakeryEndpoint)
BakeryService buildService(String url) {
return new RestAdapter.Builder()
.setEndpoint(newFixedEndpoint(url))
.setRequestInterceptor(spinnakerRequestInterceptor)
.setConverter(new JacksonConverter(bakeryConfiguredObjectMapper()))
.setClient(retrofitClient)
Expand All @@ -75,12 +84,13 @@ class BakeryConfiguration {
.create(BakeryService)
}

static ObjectMapper bakeryConfiguredObjectMapper() {
def objectMapper = new ObjectMapper()
.setPropertyNamingStrategy(new SnakeCaseStrategy())
.setDateFormat(new SimpleDateFormat("YYYYMMDDHHmm"))
.setSerializationInclusion(NON_NULL)
.disable(FAIL_ON_UNKNOWN_PROPERTIES)

@Bean
BakerySelector bakerySelector(BakeryService bakery,
BakeryConfigurationProperties bakeryConfigurationProperties) {
return new BakerySelector(
bakery,
bakeryConfigurationProperties,
{ url -> buildService(url as String) }
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2019 Netflix, 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.orca.bakery.config;

import com.netflix.spinnaker.kork.web.selector.v2.SelectableService;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "bakery")
public class BakeryConfigurationProperties {
private String baseUrl;
private boolean roscoApisEnabled = false;
private boolean extractBuildDetails = false;
private boolean allowMissingPackageInstallation = false;
private List<SelectableService.BaseUrl> baseUrls;

public String getBaseUrl() {
return baseUrl;
}

public List<SelectableService.BaseUrl> getBaseUrls() {
return baseUrls;
}

public boolean isRoscoApisEnabled() {
return roscoApisEnabled;
}

public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}

public void setRoscoApisEnabled(boolean roscoApisEnabled) {
this.roscoApisEnabled = roscoApisEnabled;
}

public void setBaseUrls(List<SelectableService.BaseUrl> baseUrls) {
this.baseUrls = baseUrls;
}

public boolean isExtractBuildDetails() {
return extractBuildDetails;
}

public void setExtractBuildDetails(boolean extractBuildDetails) {
this.extractBuildDetails = extractBuildDetails;
}

public boolean isAllowMissingPackageInstallation() {
return allowMissingPackageInstallation;
}

public void setAllowMissingPackageInstallation(boolean allowMissingPackageInstallation) {
this.allowMissingPackageInstallation = allowMissingPackageInstallation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

package com.netflix.spinnaker.orca.bakery.tasks

import com.netflix.spinnaker.kork.artifacts.model.Artifact
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.Task
import com.netflix.spinnaker.orca.TaskResult
import com.netflix.spinnaker.orca.bakery.BakerySelector
import com.netflix.spinnaker.orca.bakery.api.BakeStatus
import com.netflix.spinnaker.orca.bakery.api.BakeryService
import com.netflix.spinnaker.orca.pipeline.model.Stage
import groovy.transform.CompileStatic
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -32,13 +31,13 @@ import org.springframework.stereotype.Component
class CompletedBakeTask implements Task {

@Autowired
BakeryService bakery
BakerySelector bakerySelector

@Override
TaskResult execute(Stage stage) {
def region = stage.context.region as String
def bakery = bakerySelector.select(stage)
def bakeStatus = stage.context.status as BakeStatus
def bake = bakery.lookupBake(region, bakeStatus.resourceId).toBlocking().first()
def bake = bakery.service.lookupBake(stage.context.region as String, bakeStatus.resourceId).toBlocking().first()
// This treatment of ami allows both the aws and gce bake results to be propagated.
def results = [
ami: bake.ami ?: bake.imageName,
Expand Down
Loading

0 comments on commit 74539b0

Please sign in to comment.