Skip to content

Commit 960b5c9

Browse files
🐛 Source Dynamodb: Fix missing scan permissions (#27045)
Co-authored-by: Marcos Marx <[email protected]> Co-authored-by: marcosmarxm <[email protected]>
1 parent ccfb775 commit 960b5c9

File tree

6 files changed

+48
-16
lines changed

6 files changed

+48
-16
lines changed

airbyte-integrations/connectors/source-dynamodb/metadata.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ data:
55
connectorSubtype: api
66
connectorType: source
77
definitionId: 50401137-8871-4c5a-abb7-1f5fda35545a
8-
dockerImageTag: 0.3.1
8+
dockerImageTag: 0.3.2
99
dockerRepository: airbyte/source-dynamodb
1010
documentationUrl: https://docs.airbyte.com/integrations/sources/dynamodb
1111
githubIssueLabel: source-dynamodb

airbyte-integrations/connectors/source-dynamodb/src/main/java/io/airbyte/integrations/source/dynamodb/DynamodbConfig.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public record DynamodbConfig(
2020

2121
String secretKey,
2222

23-
List<String> reservedAttributeNames
23+
List<String> reservedAttributeNames,
24+
25+
boolean ignoreMissingPermissions
2426

2527
) {
2628

@@ -32,12 +34,14 @@ public static DynamodbConfig createDynamodbConfig(JsonNode jsonNode) {
3234
JsonNode endpoint = jsonNode.get("endpoint");
3335
JsonNode region = jsonNode.get("region");
3436
JsonNode attributeNames = jsonNode.get("reserved_attribute_names");
37+
JsonNode missingPermissions = jsonNode.get("ignore_missing_read_permissions_tables");
3538
return new DynamodbConfig(
3639
endpoint != null && !endpoint.asText().isBlank() ? URI.create(endpoint.asText()) : null,
3740
region != null && !region.asText().isBlank() ? Region.of(region.asText()) : null,
3841
accessKeyId != null && !accessKeyId.asText().isBlank() ? accessKeyId.asText() : null,
3942
secretAccessKey != null && !secretAccessKey.asText().isBlank() ? secretAccessKey.asText() : null,
40-
attributeNames != null ? Arrays.asList(attributeNames.asText().split("\\s*,\\s*")) : List.of());
43+
attributeNames != null ? Arrays.asList(attributeNames.asText().split("\\s*,\\s*")) : List.of(),
44+
missingPermissions != null ? missingPermissions.asBoolean() : false);
4145
}
4246

4347
}

airbyte-integrations/connectors/source-dynamodb/src/main/java/io/airbyte/integrations/source/dynamodb/DynamodbSource.java

+30-12
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
import io.airbyte.protocol.models.v0.AirbyteStreamNameNamespacePair;
2929
import io.airbyte.protocol.models.v0.ConfiguredAirbyteCatalog;
3030
import io.airbyte.protocol.models.v0.SyncMode;
31+
import java.util.ArrayList;
3132
import java.util.Collections;
3233
import java.util.List;
3334
import java.util.Map;
3435
import java.util.Optional;
3536
import java.util.Set;
3637
import org.slf4j.Logger;
3738
import org.slf4j.LoggerFactory;
39+
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
3840

3941
public class DynamodbSource extends BaseConnector implements Source {
4042

@@ -70,23 +72,39 @@ public AirbyteConnectionStatus check(final JsonNode config) {
7072
public AirbyteCatalog discover(final JsonNode config) {
7173

7274
final var dynamodbConfig = DynamodbConfig.createDynamodbConfig(config);
75+
List<AirbyteStream> airbyteStreams = new ArrayList<>();
7376

7477
try (final var dynamodbOperations = new DynamodbOperations(dynamodbConfig)) {
7578

76-
final var airbyteStreams = dynamodbOperations.listTables().stream()
77-
.map(tb -> new AirbyteStream()
78-
.withName(tb)
79-
.withJsonSchema(Jsons.jsonNode(ImmutableMap.builder()
80-
.put("type", "object")
81-
.put("properties", dynamodbOperations.inferSchema(tb, 1000))
82-
.build()))
83-
.withSourceDefinedPrimaryKey(Collections.singletonList(dynamodbOperations.primaryKey(tb)))
84-
.withSupportedSyncModes(List.of(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL)))
85-
.toList();
86-
87-
return new AirbyteCatalog().withStreams(airbyteStreams);
79+
dynamodbOperations.listTables().forEach(table -> {
80+
try {
81+
airbyteStreams.add(
82+
new AirbyteStream()
83+
.withName(table)
84+
.withJsonSchema(Jsons.jsonNode(ImmutableMap.builder()
85+
.put("type", "object")
86+
// will throw DynamoDbException if it can't scan the table from missing read permissions
87+
.put("properties", dynamodbOperations.inferSchema(table, 1000))
88+
.build()))
89+
.withSourceDefinedPrimaryKey(Collections.singletonList(dynamodbOperations.primaryKey(table)))
90+
.withSupportedSyncModes(List.of(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL)));
91+
} catch (DynamoDbException e) {
92+
if (dynamodbConfig.ignoreMissingPermissions()) {
93+
// fragile way to check for missing read access but there is no dedicated exception for missing
94+
// permissions.
95+
if (e.getMessage().contains("not authorized")) {
96+
LOGGER.warn("Connector doesn't have READ access for the table {}", table);
97+
} else {
98+
throw e;
99+
}
100+
} else {
101+
throw e;
102+
}
103+
}
104+
});
88105
}
89106

107+
return new AirbyteCatalog().withStreams(airbyteStreams);
90108
}
91109

92110
@Override

airbyte-integrations/connectors/source-dynamodb/src/main/resources/spec.json

+6
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@
110110
"description": "Comma separated reserved attribute names present in your tables",
111111
"airbyte_secret": true,
112112
"examples": ["name, field_name, field-name"]
113+
},
114+
"ignore_missing_read_permissions_tables": {
115+
"title": "Ignore missing read permissions tables",
116+
"type": "boolean",
117+
"description": "Ignore tables with missing scan/read permissions",
118+
"default": false
113119
}
114120
}
115121
}

airbyte-integrations/connectors/source-dynamodb/src/test/java/io/airbyte/integrations/source/dynamodb/DynamodbConfigTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import io.airbyte.commons.json.Jsons;
1010
import java.net.URI;
11+
import java.util.Collections;
1112
import java.util.Map;
1213
import org.junit.jupiter.api.Test;
1314
import software.amazon.awssdk.regions.Region;
@@ -29,7 +30,9 @@ void testUserBasedDynamodbConfig() {
2930
.hasFieldOrPropertyWithValue("endpoint", URI.create("http://localhost:8080"))
3031
.hasFieldOrPropertyWithValue("region", Region.of("us-east-1"))
3132
.hasFieldOrPropertyWithValue("accessKey", "A012345678910EXAMPLE")
32-
.hasFieldOrPropertyWithValue("secretKey", "a012345678910ABCDEFGH/AbCdEfGhLEKEY");
33+
.hasFieldOrPropertyWithValue("secretKey", "a012345678910ABCDEFGH/AbCdEfGhLEKEY")
34+
.hasFieldOrPropertyWithValue("reservedAttributeNames", Collections.emptyList())
35+
.hasFieldOrPropertyWithValue("ignoreMissingPermissions", false);
3336

3437
}
3538

docs/integrations/sources/dynamodb.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ the underlying role executing the container workload in AWS.
7575

7676
| Version | Date | Pull Request | Subject |
7777
|:--------| :--------- | :-------------------------------------------------------- |:-----------------------------------------------------------------------|
78+
| 0.3.2 | 2024-05-01 | [27045](https://github.com/airbytehq/airbyte/pull/27045) | Fix missing scan permissions |
7879
| 0.3.1 | 2024-05-01 | [31935](https://github.com/airbytehq/airbyte/pull/31935) | Fix list more than 100 tables |
7980
| 0.3.0 | 2024-04-24 | [37530](https://github.com/airbytehq/airbyte/pull/37530) | Allow role based access |
8081
| 0.2.3 | 2024-02-13 | [35232](https://github.com/airbytehq/airbyte/pull/35232) | Adopt CDK 0.20.4 |

0 commit comments

Comments
 (0)