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

Infer defined condition key service #2450

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/source-2.0/aws/aws-iam.rst
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,11 @@ Value type
``map`` of IAM identifiers to condition key ``structure``

The ``aws.iam#defineConditionKeys`` trait defines additional condition keys
that appear within a service. Keys in the map must be valid IAM identifiers,
meaning they must adhere to the following regular expression:
``"^([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:[^:]+)$"``.
Each condition key structure supports the following members:
that appear within a service. Keys in the map must be valid IAM identifiers
or names of condition keys, meaning they must adhere to the following regular
expression: ``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a
condition key name is specified, the service is inferred to be the
``arnNamespace``. Each condition key structure supports the following members:

.. list-table::
:header-rows: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.KnowledgeIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
Expand All @@ -47,34 +48,38 @@ public final class ConditionKeysIndex implements KnowledgeIndex {
private final Map<ShapeId, Map<ShapeId, Set<String>>> resourceConditionKeys = new HashMap<>();

public ConditionKeysIndex(Model model) {
model.shapes(ServiceShape.class).forEach(service -> {
service.getTrait(ServiceTrait.class).ifPresent(trait -> {
// Copy over the explicitly defined condition keys into the service map.
// This will be mutated when adding inferred resource condition keys.
serviceConditionKeys.put(service.getId(), new HashMap<>(
service.getTrait(DefineConditionKeysTrait.class)
.map(DefineConditionKeysTrait::getConditionKeys)
.orElse(MapUtils.of())));
resourceConditionKeys.put(service.getId(), new HashMap<>());
for (ServiceShape service : model.getServiceShapesWithTrait(ServiceTrait.class)) {
// Defines the scoping of any derived condition keys.
String arnNamespace = service.expectTrait(ServiceTrait.class).getArnNamespace();

// Defines the scoping of any derived condition keys.
String arnRoot = trait.getArnNamespace();
// Copy over the explicitly defined condition keys into the service map.
// This will be mutated when adding inferred resource condition keys.
Map<String, ConditionKeyDefinition> serviceKeys = new HashMap<>();
if (service.hasTrait(DefineConditionKeysTrait.ID)) {
DefineConditionKeysTrait trait = service.expectTrait(DefineConditionKeysTrait.class);
for (Map.Entry<String, ConditionKeyDefinition> entry : trait.getConditionKeys().entrySet()) {
// If no colon is present, we infer that this condition key is for the
// current service and apply its ARN namespace.
String key = entry.getKey();
if (!key.contains(":")) {
key = arnNamespace + ":" + key;
}
serviceKeys.put(key, entry.getValue());
}
}
serviceConditionKeys.put(service.getId(), serviceKeys);
resourceConditionKeys.put(service.getId(), new HashMap<>());

// Compute the keys of child resources.
service.getResources().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(resource -> {
compute(model, service, arnRoot, resource, null);
});
// Compute the keys of child resources.
for (ShapeId resourceId : service.getResources()) {
compute(model, service, arnNamespace, model.expectShape(resourceId, ResourceShape.class), null);
}

// Compute the keys of operations of the service.
service.getOperations().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(operation -> {
compute(model, service, arnRoot, operation, null);
});
});
});
// Compute the keys of operations of the service.
for (ShapeId operationId : service.getOperations()) {
compute(model, service, arnNamespace, model.expectShape(operationId, OperationShape.class), null);
}
}
}

public static ConditionKeysIndex of(Model model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ string conditionKeyValue
/// inferred and global condition keys.
@trait(selector: "service")
map defineConditionKeys {
key: IamIdentifier
key: ConditionKeyName
value: ConditionKeyDefinition
}

Expand Down Expand Up @@ -156,6 +156,10 @@ list ResourceNameList {
member: ResourceName
}

@private
@pattern("^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$")
string ConditionKeyName

/// The IAM policy type of the value that will supplied for this context key
@private
enum ConditionKeyType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public void successfullyLoadsConditionKeys() {

ConditionKeysIndex index = ConditionKeysIndex.of(model);
assertThat(index.getConditionKeyNames(service), containsInAnyOrder(
"aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2"));
"aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2", "myservice:bar"));
assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Operation1")),
containsInAnyOrder("aws:accountId", "foo:baz"));
containsInAnyOrder("aws:accountId", "myservice:bar"));
assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
containsInAnyOrder("aws:accountId", "foo:baz", "myservice:Resource1Id1"));
// Note that ID1 is not duplicated but rather reused on the child operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void loadsFromModel() {
DefineConditionKeysTrait trait = shape.expectTrait(DefineConditionKeysTrait.class);
assertEquals(3,trait.getConditionKeys().size());
assertFalse(trait.getConditionKey("myservice:Bar").get().isRequired());
assertFalse(trait.getConditionKey("myservice:Foo").get().isRequired());
assertFalse(trait.getConditionKey("Foo").get().isRequired());
assertTrue(trait.getConditionKey("myservice:Baz").get().isRequired());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use aws.iam#serviceResolvedConditionKeys
externalDocumentation: "http://baz.com"
required: true
}
"myservice:Foo": {
"Foo": {
type: "String"
documentation: "The Foo string"
externalDocumentation: "http://foo.com"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$version: "1.0"
$version: "2.0"
namespace smithy.example

use aws.api#arnReference
Expand All @@ -8,39 +8,44 @@ use aws.iam#defineConditionKeys
use aws.iam#disableConditionKeyInference
use aws.iam#iamResource

@service(sdkId: "My")
@service(sdkId: "My", arnNamespace: "myservice")
@defineConditionKeys(
"foo:baz": {
type: "String",
documentation: "Foo baz",
type: "String"
documentation: "Foo baz"
relativeDocumentation: "condition-keys.html"
}
"bar": {
type: "String"
documentation: "Foo bar"
relativeDocumentation: "condition-keys.html"
}
)
service MyService {
version: "2019-02-20",
operations: [Operation1],
version: "2019-02-20"
operations: [Operation1]
resources: [Resource1]
}

@conditionKeys(["aws:accountId", "foo:baz"])
@conditionKeys(["aws:accountId", "myservice:bar"])
operation Operation1 {}

@conditionKeys(["aws:accountId", "foo:baz"])
resource Resource1 {
identifiers: {
id1: ArnString,
},
id1: ArnString
}
resources: [Resource2, Resource3, Resource4]
}

@iamResource(name: "ResourceTwo")
resource Resource2 {
identifiers: {
id1: ArnString,
id2: FooString,
},
read: GetResource2,
list: ListResource2,
id1: ArnString
id2: FooString
}
read: GetResource2
list: ListResource2
}

@disableConditionKeyInference
Expand Down Expand Up @@ -71,7 +76,7 @@ operation GetResource2 {

structure GetResource2Input {
@required
id1: ArnString,
id1: ArnString

@required
id2: FooString
Expand All @@ -82,13 +87,13 @@ string FooString

@readonly
operation ListResource2 {
input: ListResource2Input,
input: ListResource2Input
output: ListResource2Output
}

structure ListResource2Input {
@required
id1: ArnString,
id1: ArnString
}

structure ListResource2Output {}
Expand Down
Loading