Skip to content

Commit

Permalink
feat(provider/aws) Fetch image information based on image id (#2285)
Browse files Browse the repository at this point in the history
* feat(provider/aws) Fetch image based on image id
  • Loading branch information
PerGon authored and robzienert committed Mar 21, 2018
1 parent 6d013b8 commit 4b4c9d2
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.aws.model;

import com.netflix.spinnaker.clouddriver.model.Image;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
public class AmazonImage extends com.amazonaws.services.ec2.model.Image implements Image {
public static final String AMAZON_IMAGE_TYPE = "aws/image";
String id;
String region;
List<AmazonServerGroup> serverGroups = new ArrayList<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.aws.provider.view;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.cats.cache.Cache;
import com.netflix.spinnaker.cats.cache.CacheData;
import com.netflix.spinnaker.clouddriver.aws.AmazonCloudProvider;
import com.netflix.spinnaker.clouddriver.aws.AwsConfiguration;
import com.netflix.spinnaker.clouddriver.aws.model.AmazonImage;
import com.netflix.spinnaker.clouddriver.aws.model.AmazonServerGroup;
import com.netflix.spinnaker.clouddriver.model.Image;
import com.netflix.spinnaker.clouddriver.model.ImageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.IMAGES;
import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.SERVER_GROUPS;


@Component
public class AmazonImageProvider implements ImageProvider {

private final Cache cacheView;
private final AwsConfiguration.AmazonServerGroupProvider amazonServerGroupProvider;
private final ObjectMapper objectMapper;

@Autowired
AmazonImageProvider(Cache cacheView, AwsConfiguration.AmazonServerGroupProvider amazonServerGroupProvider, ObjectMapper objectMapper) {
this.cacheView = cacheView;
this.amazonServerGroupProvider = amazonServerGroupProvider;
this.objectMapper = objectMapper;
}

@Override
public Optional<Image> getImageById(String imageId) {

if (!imageId.startsWith("ami-")) {
throw new RuntimeException("Image Id provided (" + imageId + ") is not a valid id for the provider " + getCloudProvider());
}

List<String> imageIdList = new ArrayList<>(cacheView.filterIdentifiers(IMAGES.toString(), "*" + imageId));

if (imageIdList.isEmpty()) {
return Optional.empty();
}

List<CacheData> imageCacheList = new ArrayList<>(cacheView.getAll(IMAGES.toString(), imageIdList));

AmazonImage image = objectMapper.convertValue(imageCacheList.get(0).getAttributes(), AmazonImage.class);
image.setId(image.getImageId());
String[] imageIdSplitKey = imageCacheList.get(0).getId().split(":");
image.setRegion(imageIdSplitKey[imageIdSplitKey.length - 2]);

List<AmazonServerGroup> serverGroupList = imageCacheList.stream()
.filter(imageCache -> imageCache.getRelationships().get(SERVER_GROUPS.toString()) != null)
.map(imageCache -> imageCache.getRelationships().get(SERVER_GROUPS.toString()))
.flatMap(Collection::stream)
.map(this::getServerGroupData)
.collect(Collectors.toList());

image.setServerGroups(serverGroupList);
return Optional.of(image);
}

@Override
public String getCloudProvider() {
return AmazonCloudProvider.ID;
}

private AmazonServerGroup getServerGroupData(String serverGroupCacheKey) {

String[] splittedKey = serverGroupCacheKey.split(":");
String account = splittedKey[splittedKey.length - 3];
String region = splittedKey[splittedKey.length - 2];
String serverGroupName = splittedKey[splittedKey.length - 1];

return amazonServerGroupProvider.getServerGroup(account, region, serverGroupName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.aws.provider.view

import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.cats.cache.Cache
import com.netflix.spinnaker.cats.cache.CacheData
import com.netflix.spinnaker.cats.cache.DefaultCacheData
import com.netflix.spinnaker.clouddriver.aws.AwsConfiguration
import com.netflix.spinnaker.clouddriver.aws.model.AmazonImage
import spock.lang.Specification
import spock.lang.Subject

import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.IMAGES
import com.netflix.spinnaker.clouddriver.aws.data.Keys

class AmazonImageProviderSpec extends Specification {
Cache cache = Mock(Cache)
AwsConfiguration.AmazonServerGroupProvider amazonServerGroupProvider = Mock(AwsConfiguration.AmazonServerGroupProvider)
ObjectMapper objectMapper = new ObjectMapper()

@Subject
AmazonImageProvider provider = new AmazonImageProvider(cache, amazonServerGroupProvider, objectMapper)

void "should return one image"() {
when:
def result = provider.getImageById("ami-123321")

then:
AmazonImage expectedImage = new AmazonImage()
expectedImage.setRegion("eu-west-1")
expectedImage.setId("ami-123321")
expectedImage.setImageId("ami-123321")
expectedImage.setOwnerId("1233211233231")
result == Optional.of(expectedImage)

and:
1 * cache.filterIdentifiers(IMAGES.ns, _ as String) >> [
"aws:images:test_account:eu-west-1:ami-123321"
]

1 * cache.getAll(IMAGES.ns, ["aws:images:test_account:eu-west-1:ami-123321"]) >>
[imageCacheData('aws:images:test_account:eu-west-1:ami-123321', [
ownerId: '1233211233231',
name : 'some_ami',
imageId: 'ami-123321'])]
}

void "should not find any image"() {
when:
def result = provider.getImageById("ami-123321")

then:
result == Optional.empty()

and:
1 * cache.filterIdentifiers(IMAGES.ns, _ as String) >> []
}

void "should throw exception of invalid ami name"() {
when:
provider.getImageById("amiz-123321")

then:
thrown(RuntimeException)
}

static private CacheData imageCacheData(String imageId, Map attributes) {
new DefaultCacheData(Keys.getImageKey(imageId, attributes.account, attributes.region), attributes, [:])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.netflix.spinnaker.clouddriver.model.ApplicationProvider
import com.netflix.spinnaker.clouddriver.model.CloudMetricProvider
import com.netflix.spinnaker.clouddriver.model.ClusterProvider
import com.netflix.spinnaker.clouddriver.model.ElasticIpProvider
import com.netflix.spinnaker.clouddriver.model.ImageProvider
import com.netflix.spinnaker.clouddriver.model.InstanceProvider
import com.netflix.spinnaker.clouddriver.model.InstanceTypeProvider
import com.netflix.spinnaker.clouddriver.model.KeyPairProvider
Expand All @@ -40,6 +41,7 @@ import com.netflix.spinnaker.clouddriver.model.NoopApplicationProvider
import com.netflix.spinnaker.clouddriver.model.NoopCloudMetricProvider
import com.netflix.spinnaker.clouddriver.model.NoopClusterProvider
import com.netflix.spinnaker.clouddriver.model.NoopElasticIpProvider
import com.netflix.spinnaker.clouddriver.model.NoopImageProvider
import com.netflix.spinnaker.clouddriver.model.NoopInstanceProvider
import com.netflix.spinnaker.clouddriver.model.NoopInstanceTypeProvider
import com.netflix.spinnaker.clouddriver.model.NoopKeyPairProvider
Expand Down Expand Up @@ -207,6 +209,12 @@ class CloudDriverConfig {
new NoopInstanceProvider()
}

@Bean
@ConditionalOnMissingBean(ImageProvider)
ImageProvider noopImageProvider() {
new NoopImageProvider()
}

@Bean
@ConditionalOnMissingBean(InstanceTypeProvider)
InstanceTypeProvider noopInstanceTypeProvider() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.model;

import java.util.Optional;

public interface ImageProvider {
Optional<Image> getImageById(String imageId);

String getCloudProvider();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.model;

import java.util.Optional;

public class NoopImageProvider implements ImageProvider {

@Override
public Optional<Image> getImageById(String imageId) {
return Optional.empty();
}

@Override
public String getCloudProvider() {
return "none";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2018 Schibsted ASA.
*
* 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.clouddriver.controllers;

import com.netflix.spinnaker.clouddriver.model.Image;
import com.netflix.spinnaker.clouddriver.model.ImageProvider;
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/images")
public class ImageController {

@Autowired
List<ImageProvider> imageProviders;

@RequestMapping(value = "/{provider}/{imageId}", method = RequestMethod.GET)
Image getImage(@PathVariable String provider, @PathVariable String imageId) {

List<ImageProvider> imageProviderList = imageProviders.stream()
.filter(imageProvider -> imageProvider.getCloudProvider().equals(provider))
.collect(Collectors.toList());

if (imageProviderList.isEmpty()) {
throw new UnsupportedOperationException("ImageProvider for provider " + provider + " not found.");
} else if (imageProviderList.size() > 1) {
throw new IllegalStateException("Found multiple ImageProviders for provider " + provider + ". Multiple ImageProviders for a single provider are not supported.");
} else {
return imageProviderList.get(0).getImageById(imageId).orElseThrow(() -> new NotFoundException("Image not found (id: " + imageId + ")"));
}
}
}

0 comments on commit 4b4c9d2

Please sign in to comment.