software.amazon.smithy.aws.cloudformation.traits.CfnResourceIndex Maven / Gradle / Ivy
Show all versions of smithy-aws-cloudformation-traits Show documentation
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.smithy.aws.cloudformation.traits;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.BottomUpIndex;
import software.amazon.smithy.model.knowledge.IdentifierBindingIndex;
import software.amazon.smithy.model.knowledge.KnowledgeIndex;
import software.amazon.smithy.model.knowledge.PropertyBindingIndex;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeVisitor;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.OptionalUtils;
import software.amazon.smithy.utils.SetUtils;
/**
* Index of resources to their CloudFormation identifiers and properties.
*
* This index performs no validation that the identifiers and reference
* valid shapes.
*/
public final class CfnResourceIndex implements KnowledgeIndex {
static final Set FULLY_MUTABLE = SetUtils.of(
Mutability.CREATE, Mutability.READ, Mutability.WRITE);
static final Set INHERITED_MUTABILITY = SetUtils.of(
Mutability.CREATE, Mutability.READ);
private final Map resourceDefinitions = new HashMap<>();
/**
* Mutability options derived through lifecycle operations or traits.
* These mutability options are used to derive the CloudFormation
* mutabilities of the properties they are associated with.
*
* @see CfnResource#getCreateOnlyProperties
* @see CfnResource#getReadOnlyProperties
* @see CfnResource#getWriteOnlyProperties
*/
public enum Mutability {
/**
* Applied when a property was derived from a lifecycle operation that
* creates a resource or the {@link CfnMutabilityTrait} specifying {@code full},
* {@code create}, or {@code create-and-read} mutability.
*/
CREATE,
/**
* Applied when a property was derived from a lifecycle operation that
* retrieves a resource or the {@link CfnMutabilityTrait} specifying {@code full},
* {@code read}, or {@code create-and-read} mutability.
*/
READ,
/**
* Applied when a property was derived from a lifecycle operation that
* updates a resource or the {@link CfnMutabilityTrait} specifying {@code full}
* or {@code write} mutability.
*/
WRITE
}
public CfnResourceIndex(Model model) {
PropertyBindingIndex propertyIndex = PropertyBindingIndex.of(model);
BottomUpIndex bottomUpIndex = BottomUpIndex.of(model);
model.shapes(ResourceShape.class)
.filter(shape -> shape.hasTrait(CfnResourceTrait.ID))
.forEach(resource -> {
CfnResource.Builder builder = CfnResource.builder();
ShapeId resourceId = resource.getId();
Set parentResources = model.getServiceShapes()
.stream()
.map(service -> bottomUpIndex.getResourceBinding(service, resourceId))
.flatMap(OptionalUtils::stream)
.collect(Collectors.toSet());
// Start with the explicit resource identifiers.
builder.primaryIdentifiers(resource.getIdentifiers().keySet());
setIdentifierMutabilities(builder, resource, parentResources);
// Use the read lifecycle's input to collect the additional identifiers
// and its output to collect readable properties.
resource.getRead().ifPresent(operationId -> {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
StructureShape input = model.expectShape(operation.getInputShape(), StructureShape.class);
addAdditionalIdentifiers(builder, computeResourceAdditionalIdentifiers(input));
StructureShape output = propertyIndex.getOutputPropertiesShape(operation);
updatePropertyMutabilities(builder, model, resourceId, operationId, output,
SetUtils.of(Mutability.READ), this::addReadMutability);
});
// Use the put lifecycle's input to collect put-able properties.
resource.getPut().ifPresent(operationId -> {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
StructureShape input = propertyIndex.getInputPropertiesShape(operation);
updatePropertyMutabilities(builder, model, resourceId, operationId, input,
SetUtils.of(Mutability.CREATE, Mutability.WRITE), this::addPutMutability);
});
// Use the create lifecycle's input to collect creatable properties.
resource.getCreate().ifPresent(operationId -> {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
StructureShape input = propertyIndex.getInputPropertiesShape(operation);
updatePropertyMutabilities(builder, model, resourceId, operationId, input,
SetUtils.of(Mutability.CREATE), this::addCreateMutability);
});
// Use the update lifecycle's input to collect writeable properties.
resource.getUpdate().ifPresent(operationId -> {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
StructureShape input = propertyIndex.getInputPropertiesShape(operation);
updatePropertyMutabilities(builder, model, resourceId, operationId, input,
SetUtils.of(Mutability.WRITE), this::addWriteMutability);
});
// Apply any members found through the trait's additionalSchemas property.
CfnResourceTrait trait = resource.expectTrait(CfnResourceTrait.class);
for (ShapeId additionalSchema : trait.getAdditionalSchemas()) {
// These shapes should be present given the @idRef failWhenMissing
// setting, but gracefully handle if they're not.
model.getShape(additionalSchema)
.flatMap(Shape::asStructureShape)
.ifPresent(shape -> {
addAdditionalIdentifiers(builder, computeResourceAdditionalIdentifiers(shape));
updatePropertyMutabilities(builder, model, resourceId, null, shape,
SetUtils.of(), Function.identity());
});
}
resourceDefinitions.put(resourceId, builder.build());
});
}
public static CfnResourceIndex of(Model model) {
return model.getKnowledge(CfnResourceIndex.class, CfnResourceIndex::new);
}
/**
* Gets the definition of the specified CloudFormation resource.
*
* @param resource ShapeID of a resource
* @return The resource definition.
*/
public Optional getResource(ToShapeId resource) {
return Optional.ofNullable(resourceDefinitions.get(resource.toShapeId()));
}
private boolean identifierIsInherited(String identifier, Set parentResources) {
return parentResources.stream()
.anyMatch(parentResource -> parentResource.getIdentifiers().containsKey(identifier));
}
private void setIdentifierMutabilities(
CfnResource.Builder builder,
ResourceShape resource,
Set parentResources) {
Set defaultIdentifierMutability = getDefaultIdentifierMutabilities(resource);
resource.getIdentifiers().forEach((name, shape) -> {
builder.putPropertyDefinition(name, CfnResourceProperty.builder()
.hasExplicitMutability(true)
.mutabilities(identifierIsInherited(name, parentResources)
? INHERITED_MUTABILITY : defaultIdentifierMutability)
.addShapeId(shape)
.build());
});
}
private Set getDefaultIdentifierMutabilities(ResourceShape resource) {
// If we have a put operation, the identifier will be specified
// on creation. Otherwise, it's read only.
if (resource.getPut().isPresent()) {
return SetUtils.of(Mutability.CREATE, Mutability.READ);
}
return SetUtils.of(Mutability.READ);
}
private List