embulk-input-dynamodb 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/master.yml +34 -0
  3. data/.github/workflows/test.yml +30 -0
  4. data/.scalafmt.conf +5 -0
  5. data/CHANGELOG.md +49 -0
  6. data/README.md +204 -54
  7. data/build.gradle +53 -44
  8. data/example/config-deprecated.yml +20 -0
  9. data/example/config-query-as-json.yml +18 -0
  10. data/example/config-query.yml +22 -0
  11. data/example/config-scan.yml +18 -0
  12. data/example/prepare_dynamodb_table.sh +67 -0
  13. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  14. data/gradle/wrapper/gradle-wrapper.properties +1 -2
  15. data/gradlew +67 -48
  16. data/gradlew.bat +20 -10
  17. data/{test/run_dynamodb_local.sh → run_dynamodb_local.sh} +2 -1
  18. data/settings.gradle +1 -0
  19. data/src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala +73 -0
  20. data/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala +76 -25
  21. data/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala +132 -32
  22. data/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala +44 -0
  23. data/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala +37 -0
  24. data/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala +240 -0
  25. data/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala +35 -0
  26. data/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala +79 -0
  27. data/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala +61 -0
  28. data/src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala +72 -0
  29. data/src/main/scala/org/embulk/input/dynamodb/{Filter.scala → deprecated/Filter.scala} +3 -3
  30. data/src/main/scala/org/embulk/input/dynamodb/{FilterConfig.scala → deprecated/FilterConfig.scala} +13 -13
  31. data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/AbstractOperation.scala +36 -18
  32. data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/QueryOperation.scala +21 -13
  33. data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/ScanOperation.scala +20 -13
  34. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala +154 -0
  35. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala +245 -0
  36. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueType.scala +33 -0
  37. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemColumnVisitor.scala +50 -0
  38. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemConsumer.scala +40 -0
  39. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemIterator.scala +19 -0
  40. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala +64 -0
  41. data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala +135 -0
  42. data/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala +169 -0
  43. data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala +59 -0
  44. data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala +72 -0
  45. data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala +93 -0
  46. data/src/main/scala/org/embulk/input/dynamodb/operation/EmbulkDynamodbOperation.scala +15 -0
  47. data/src/main/scala/org/embulk/input/dynamodb/package.scala +4 -9
  48. data/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala +245 -101
  49. data/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala +150 -97
  50. data/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationTest.scala +188 -0
  51. data/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationTest.scala +181 -0
  52. data/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala +85 -0
  53. metadata +73 -49
  54. data/circle.yml +0 -16
  55. data/config/checkstyle/checkstyle.xml +0 -128
  56. data/config/checkstyle/default.xml +0 -108
  57. data/src/main/scala/org/embulk/input/dynamodb/AttributeValueHelper.scala +0 -41
  58. data/src/main/scala/org/embulk/input/dynamodb/AwsCredentials.scala +0 -63
  59. data/src/main/scala/org/embulk/input/dynamodb/DynamoDBClient.scala +0 -23
  60. data/src/test/resources/yaml/authMethodBasic.yml +0 -21
  61. data/src/test/resources/yaml/authMethodBasic_Error.yml +0 -19
  62. data/src/test/resources/yaml/authMethodEnv.yml +0 -19
  63. data/src/test/resources/yaml/authMethodProfile.yml +0 -20
  64. data/src/test/resources/yaml/dynamodb-local-query.yml +0 -25
  65. data/src/test/resources/yaml/dynamodb-local-scan.yml +0 -23
  66. data/src/test/resources/yaml/notSetAuthMethod.yml +0 -20
  67. data/src/test/scala/org/embulk/input/dynamodb/ope/QueryOperationTest.scala +0 -83
  68. data/src/test/scala/org/embulk/input/dynamodb/ope/ScanOperationTest.scala +0 -83
  69. data/test/create_table.sh +0 -16
  70. data/test/put_items.sh +0 -25
@@ -2,46 +2,97 @@ package org.embulk.input.dynamodb
2
2
 
3
3
  import java.util.{List => JList}
4
4
 
5
- import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient
6
- import org.embulk.config._
7
- import org.embulk.input.dynamodb.ope.{QueryOperation, ScanOperation}
8
- import org.embulk.spi._
5
+ import org.embulk.config.{ConfigDiff, ConfigSource, TaskReport, TaskSource}
6
+ import org.embulk.input.dynamodb.aws.Aws
7
+ import org.embulk.input.dynamodb.item.DynamodbItemSchema
8
+ import org.embulk.input.dynamodb.operation.DynamodbOperationProxy
9
+ import org.embulk.spi.{Exec, InputPlugin, PageBuilder, PageOutput, Schema}
9
10
 
10
11
  class DynamodbInputPlugin extends InputPlugin {
11
- def transaction(config: ConfigSource, control: InputPlugin.Control): ConfigDiff = {
12
- val task: PluginTask = config.loadConfig(classOf[PluginTask])
13
12
 
14
- val schema: Schema = task.getColumns.toSchema
15
- val taskCount: Int = 1
13
+ override def transaction(
14
+ config: ConfigSource,
15
+ control: InputPlugin.Control
16
+ ): ConfigDiff = {
17
+ val task: PluginTask = PluginTask.load(config)
18
+ if (isDeprecatedOperationRequired(task))
19
+ return DeprecatedDynamodbInputPlugin.transaction(config, control)
16
20
 
17
- resume(task.dump(), schema, taskCount, control)
18
- }
21
+ val schema: Schema = DynamodbItemSchema(task).getEmbulkSchema
22
+ val taskCount: Int = DynamodbOperationProxy(task).getEmbulkTaskCount
19
23
 
20
- def resume(taskSource: TaskSource, schema: Schema, taskCount: Int, control: InputPlugin.Control): ConfigDiff = {
21
- control.run(taskSource, schema, taskCount)
24
+ control.run(task.dump(), schema, taskCount)
22
25
  Exec.newConfigDiff()
23
26
  }
24
27
 
25
- def run(taskSource: TaskSource, schema: Schema, taskIndex: Int, output: PageOutput): TaskReport = {
26
- val task: PluginTask = taskSource.loadTask(classOf[PluginTask])
28
+ override def resume(
29
+ taskSource: TaskSource,
30
+ schema: Schema,
31
+ taskCount: Int,
32
+ control: InputPlugin.Control
33
+ ): ConfigDiff = {
34
+ val task: PluginTask = PluginTask.load(taskSource)
35
+ if (isDeprecatedOperationRequired(task))
36
+ return DeprecatedDynamodbInputPlugin.resume(
37
+ taskSource,
38
+ schema,
39
+ taskCount,
40
+ control
41
+ )
42
+ throw new UnsupportedOperationException
43
+ }
27
44
 
28
- val client: AmazonDynamoDBClient = DynamoDBClient.create(task)
45
+ override def run(
46
+ taskSource: TaskSource,
47
+ schema: Schema,
48
+ taskIndex: Int,
49
+ output: PageOutput
50
+ ): TaskReport = {
51
+ val task: PluginTask = PluginTask.load(taskSource)
52
+ if (isDeprecatedOperationRequired(task))
53
+ return DeprecatedDynamodbInputPlugin.run(
54
+ taskSource,
55
+ schema,
56
+ taskIndex,
57
+ output
58
+ )
29
59
 
30
- val ope = task.getOperation.toLowerCase match {
31
- case "scan" => new ScanOperation(client)
32
- case "query" => new QueryOperation(client)
33
- }
34
- ope.execute(task, schema, output)
60
+ val pageBuilder = new PageBuilder(task.getBufferAllocator, schema, output)
35
61
 
62
+ Aws(task).withDynamodb { dynamodb =>
63
+ DynamodbOperationProxy(task).run(
64
+ dynamodb,
65
+ taskIndex,
66
+ DynamodbItemSchema(task).getItemsConsumer(pageBuilder)
67
+ )
68
+ }
69
+ pageBuilder.finish()
36
70
  Exec.newTaskReport()
37
71
  }
38
72
 
39
- def cleanup(taskSource: TaskSource, schema: Schema, taskCount: Int, successTaskReports: JList[TaskReport]): Unit = {
40
- // TODO
73
+ override def cleanup(
74
+ taskSource: TaskSource,
75
+ schema: Schema,
76
+ taskCount: Int,
77
+ successTaskReports: JList[TaskReport]
78
+ ): Unit = {
79
+ val task: PluginTask = PluginTask.load(taskSource)
80
+ if (isDeprecatedOperationRequired(task))
81
+ DeprecatedDynamodbInputPlugin.cleanup(
82
+ taskSource,
83
+ schema,
84
+ taskCount,
85
+ successTaskReports
86
+ )
41
87
  }
42
88
 
43
- def guess(config: ConfigSource): ConfigDiff = {
44
- // TODO
45
- null
89
+ override def guess(config: ConfigSource): ConfigDiff = {
90
+ val task: PluginTask = PluginTask.load(config)
91
+ if (isDeprecatedOperationRequired(task))
92
+ return DeprecatedDynamodbInputPlugin.guess(config)
93
+ throw new UnsupportedOperationException
46
94
  }
95
+
96
+ private def isDeprecatedOperationRequired(task: PluginTask): Boolean =
97
+ task.getOperation.isPresent
47
98
  }
@@ -1,55 +1,71 @@
1
1
  package org.embulk.input.dynamodb
2
2
 
3
- import com.google.common.base.Optional
4
- import org.embulk.config.{Config, ConfigDefault, ConfigInject, Task}
5
- import org.embulk.spi.{BufferAllocator, SchemaConfig}
3
+ import java.util.Optional
6
4
 
7
- trait PluginTask extends Task {
8
- @Config("auth_method")
9
- @ConfigDefault("null")
10
- def getAuthMethod: Optional[String]
11
-
12
- @Config("access_key")
13
- @ConfigDefault("null")
14
- def getAccessKey: Optional[String]
15
-
16
- @Config("secret_key")
17
- @ConfigDefault("null")
18
- def getSecretKey: Optional[String]
19
-
20
- @Config("profile_name")
21
- @ConfigDefault("null")
22
- def getProfileName: Optional[String]
5
+ import org.embulk.config.{
6
+ Config,
7
+ ConfigDefault,
8
+ ConfigException,
9
+ ConfigInject,
10
+ ConfigSource,
11
+ Task,
12
+ TaskSource
13
+ }
14
+ import org.embulk.input.dynamodb.aws.Aws
15
+ import org.embulk.input.dynamodb.deprecated.Filter
16
+ import org.embulk.input.dynamodb.item.DynamodbItemSchema
17
+ import org.embulk.input.dynamodb.operation.DynamodbOperationProxy
18
+ import org.embulk.spi.BufferAllocator
19
+ import zio.macros.annotation.delegate
23
20
 
24
- @Config("region")
25
- @ConfigDefault("null")
26
- def getRegion: Optional[String]
21
+ import scala.util.chaining._
27
22
 
28
- @Config("end_point")
29
- @ConfigDefault("null")
30
- def getEndPoint: Optional[String]
23
+ trait PluginTask
24
+ extends Task
25
+ with Aws.Task
26
+ with DynamodbItemSchema.Task
27
+ with DynamodbOperationProxy.Task {
31
28
 
29
+ @deprecated(
30
+ message = "Use #getScan() or #getQuery() instead.",
31
+ since = "0.3.0"
32
+ )
32
33
  @Config("operation")
33
- def getOperation: String
34
+ @ConfigDefault("null")
35
+ def getOperation: Optional[String]
34
36
 
37
+ @deprecated(
38
+ message =
39
+ "Use DynamodbQueryOperation.Task#getBatchSize() or DynamodbScanOperation.Task#getBatchSize() instead.",
40
+ since = "0.3.0"
41
+ )
35
42
  @Config("limit")
36
43
  @ConfigDefault("0")
37
44
  def getLimit: Long
38
45
 
46
+ @deprecated(
47
+ message =
48
+ "Use DynamodbQueryOperation.Task#getBatchSize() or DynamodbScanOperation.Task#getBatchSize() instead.",
49
+ since = "0.3.0"
50
+ )
39
51
  @Config("scan_limit")
40
52
  @ConfigDefault("0")
41
53
  def getScanLimit: Long
42
54
 
55
+ @deprecated(
56
+ message =
57
+ "Use DynamodbQueryOperation.Task#getLimit() or DynamodbScanOperation.Task#getLimit() instead.",
58
+ since = "0.3.0"
59
+ )
43
60
  @Config("record_limit")
44
61
  @ConfigDefault("0")
45
62
  def getRecordLimit: Long
46
63
 
47
- @Config("table")
48
- def getTable: String
49
-
50
- @Config("columns")
51
- def getColumns: SchemaConfig
52
-
64
+ @deprecated(
65
+ message =
66
+ "Use DynamodbQueryOperation.Task#getFilterExpression() or DynamodbScanOperation.Task#getFilterExpression() instead.",
67
+ since = "0.3.0"
68
+ )
53
69
  @Config("filters")
54
70
  @ConfigDefault("null")
55
71
  def getFilters: Optional[Filter]
@@ -57,3 +73,87 @@ trait PluginTask extends Task {
57
73
  @ConfigInject
58
74
  def getBufferAllocator: BufferAllocator
59
75
  }
76
+
77
+ case class PluginTaskCompat(@delegate task: PluginTask) extends PluginTask {
78
+
79
+ override def getOperation: Optional[String] = {
80
+ task.getOperation.ifPresent { op =>
81
+ logger.warn(
82
+ "[Deprecated] The option \"operation\" is deprecated. Use \"scan\" or \"query\" option instead."
83
+ )
84
+
85
+ op.toLowerCase match {
86
+ case "scan" | "query" => // do nothing
87
+ case x =>
88
+ throw new ConfigException(
89
+ s"Operation '$x' is unsupported. Available values are 'scan' or 'query'."
90
+ )
91
+ }
92
+
93
+ if (getScan.isPresent || getQuery.isPresent)
94
+ throw new ConfigException(
95
+ "The option \"operation\" must not be used together with either \"scan\" or \"query\" options."
96
+ )
97
+ }
98
+ task.getOperation
99
+ }
100
+
101
+ override def getLimit: Long = {
102
+ logger.warn(
103
+ "[Deprecated] The option \"limit\" is deprecated. Use \"query.batch_size\" or \"scan.batch_size\" instead."
104
+ )
105
+ task.getLimit
106
+ }
107
+
108
+ override def getScanLimit: Long = {
109
+ logger.warn(
110
+ "[Deprecated] The option \"scan_limit\" is deprecated. Use \"query.batch_size\" or \"scan.batch_size\" instead."
111
+ )
112
+ task.getScanLimit
113
+ }
114
+
115
+ override def getRecordLimit: Long = {
116
+ logger.warn(
117
+ "[Deprecated] The option \"record_limit\" is deprecated. Use \"query.limit\" or \"scan.limit\" instead."
118
+ )
119
+ task.getRecordLimit
120
+ }
121
+
122
+ override def getFilters: Optional[Filter] = {
123
+ logger.warn(
124
+ "[Deprecated] The option \"filters\" is deprecated. Use \"query.filter_expression\" or \"scan.filter_expression\" instead."
125
+ )
126
+ task.getFilters
127
+ }
128
+ }
129
+
130
+ object PluginTask {
131
+
132
+ def load(configSource: ConfigSource): PluginTask = {
133
+ configSource
134
+ .loadConfig(classOf[PluginTask])
135
+ .pipe(PluginTaskCompat)
136
+ .tap(configure)
137
+ }
138
+
139
+ def load(taskSource: TaskSource): PluginTask = {
140
+ taskSource
141
+ .loadTask(classOf[PluginTask])
142
+ .pipe(PluginTaskCompat)
143
+ .tap(configure)
144
+ }
145
+
146
+ private def configure(task: PluginTask): Unit = {
147
+ if (!task.getOperation.isPresent && !task.getScan.isPresent && !task.getQuery.isPresent) {
148
+ // NOTE: "operation" option is deprecated, so this is not shown the message.
149
+ throw new ConfigException(
150
+ "Either \"scan\" or \"query\" option is required."
151
+ )
152
+ }
153
+ if (task.getOperation.isPresent && (task.getScan.isPresent || task.getQuery.isPresent)) {
154
+ throw new ConfigException(
155
+ "[Deprecated] You must not use \"scan\" or \"query\" option with \"operation\" option."
156
+ )
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,44 @@
1
+ package org.embulk.input.dynamodb.aws
2
+
3
+ import com.amazonaws.client.builder.AwsClientBuilder
4
+ import com.amazonaws.services.dynamodbv2.{
5
+ AmazonDynamoDB,
6
+ AmazonDynamoDBClientBuilder
7
+ }
8
+
9
+ object Aws {
10
+
11
+ trait Task
12
+ extends AwsCredentials.Task
13
+ with AwsEndpointConfiguration.Task
14
+ with AwsClientConfiguration.Task
15
+ with AwsDynamodbConfiguration.Task
16
+
17
+ def apply(task: Task): Aws = {
18
+ new Aws(task)
19
+ }
20
+
21
+ }
22
+
23
+ class Aws(task: Aws.Task) {
24
+
25
+ def withDynamodb[A](f: AmazonDynamoDB => A): A = {
26
+ val builder: AmazonDynamoDBClientBuilder =
27
+ AmazonDynamoDBClientBuilder.standard()
28
+ AwsDynamodbConfiguration(task).configureAmazonDynamoDBClientBuilder(builder)
29
+ val svc = createService(builder)
30
+ try f(svc)
31
+ finally svc.shutdown()
32
+
33
+ }
34
+
35
+ def createService[S <: AwsClientBuilder[S, T], T](
36
+ builder: AwsClientBuilder[S, T]
37
+ ): T = {
38
+ AwsEndpointConfiguration(task).configureAwsClientBuilder(builder)
39
+ AwsClientConfiguration(task).configureAwsClientBuilder(builder)
40
+ builder.setCredentials(AwsCredentials(task).createAwsCredentialsProvider)
41
+
42
+ builder.build()
43
+ }
44
+ }
@@ -0,0 +1,37 @@
1
+ package org.embulk.input.dynamodb.aws
2
+
3
+ import java.util.Optional
4
+
5
+ import com.amazonaws.ClientConfiguration
6
+ import com.amazonaws.client.builder.AwsClientBuilder
7
+ import org.embulk.config.{Config, ConfigDefault}
8
+ import org.embulk.input.dynamodb.aws.AwsClientConfiguration.Task
9
+
10
+ object AwsClientConfiguration {
11
+
12
+ trait Task {
13
+
14
+ @Config("http_proxy")
15
+ @ConfigDefault("null")
16
+ def getHttpProxy: Optional[HttpProxy.Task]
17
+
18
+ }
19
+
20
+ def apply(task: Task): AwsClientConfiguration = {
21
+ new AwsClientConfiguration(task)
22
+ }
23
+ }
24
+
25
+ class AwsClientConfiguration(task: Task) {
26
+
27
+ def configureAwsClientBuilder[S <: AwsClientBuilder[S, T], T](
28
+ builder: AwsClientBuilder[S, T]
29
+ ): Unit = {
30
+ task.getHttpProxy.ifPresent { v =>
31
+ val cc = new ClientConfiguration
32
+ HttpProxy(v).configureClientConfiguration(cc)
33
+ builder.setClientConfiguration(cc)
34
+ }
35
+ }
36
+
37
+ }
@@ -0,0 +1,240 @@
1
+ package org.embulk.input.dynamodb.aws
2
+
3
+ import java.util.Optional
4
+
5
+ import com.amazonaws.auth.{
6
+ AnonymousAWSCredentials,
7
+ AWSCredentialsProvider,
8
+ AWSStaticCredentialsProvider,
9
+ BasicAWSCredentials,
10
+ BasicSessionCredentials,
11
+ DefaultAWSCredentialsProviderChain,
12
+ EC2ContainerCredentialsProviderWrapper,
13
+ EnvironmentVariableCredentialsProvider,
14
+ STSAssumeRoleSessionCredentialsProvider,
15
+ SystemPropertiesCredentialsProvider,
16
+ WebIdentityTokenCredentialsProvider
17
+ }
18
+ import com.amazonaws.auth.profile.{
19
+ ProfileCredentialsProvider,
20
+ ProfilesConfigFile
21
+ }
22
+ import org.embulk.config.{Config, ConfigDefault, ConfigException}
23
+ import org.embulk.input.dynamodb.aws.AwsCredentials.Task
24
+ import org.embulk.input.dynamodb.logger
25
+ import org.embulk.spi.unit.LocalFile
26
+ import zio.macros.annotation.delegate
27
+
28
+ object AwsCredentials {
29
+
30
+ trait Task {
31
+
32
+ @Config("auth_method")
33
+ @ConfigDefault("\"default\"")
34
+ def getAuthMethod: String
35
+
36
+ @deprecated(message = "Use #getAccessKeyId() instead.", since = "0.3.0")
37
+ @Config("access_key")
38
+ @ConfigDefault("null")
39
+ def getAccessKey: Optional[String]
40
+
41
+ @Config("access_key_id")
42
+ @ConfigDefault("null")
43
+ def getAccessKeyId: Optional[String]
44
+
45
+ @deprecated(message = "Use #getSecretAccessKey() instead.", since = "0.3.0")
46
+ @Config("secret_key")
47
+ @ConfigDefault("null")
48
+ def getSecretKey: Optional[String]
49
+
50
+ @Config("secret_access_key")
51
+ @ConfigDefault("null")
52
+ def getSecretAccessKey: Optional[String]
53
+
54
+ @Config("session_token")
55
+ @ConfigDefault("null")
56
+ def getSessionToken: Optional[String]
57
+
58
+ @Config("profile_file")
59
+ @ConfigDefault("null")
60
+ def getProfileFile: Optional[LocalFile]
61
+
62
+ @Config("profile_name")
63
+ @ConfigDefault("\"default\"")
64
+ def getProfileName: String
65
+
66
+ @Config("role_arn")
67
+ @ConfigDefault("null")
68
+ def getRoleArn: Optional[String]
69
+
70
+ @Config("role_session_name")
71
+ @ConfigDefault("null")
72
+ def getRoleSessionName: Optional[String]
73
+
74
+ @Config("role_external_id")
75
+ @ConfigDefault("null")
76
+ def getRoleExternalId: Optional[String]
77
+
78
+ @Config("role_session_duration_seconds")
79
+ @ConfigDefault("null")
80
+ def getRoleSessionDurationSeconds: Optional[Int]
81
+
82
+ @Config("scope_down_policy")
83
+ @ConfigDefault("null")
84
+ def getScopeDownPolicy: Optional[String]
85
+
86
+ @Config("web_identity_token_file")
87
+ @ConfigDefault("null")
88
+ def getWebIdentityTokenFile: Optional[String]
89
+ }
90
+
91
+ def apply(task: Task): AwsCredentials = {
92
+ new AwsCredentials(AwsCredentialsTaskCompat(task))
93
+ }
94
+ }
95
+
96
+ case class AwsCredentialsTaskCompat(@delegate task: Task) extends Task {
97
+
98
+ override def getAccessKey: Optional[String] = {
99
+ throw new NotImplementedError()
100
+ }
101
+
102
+ override def getSecretKey: Optional[String] = {
103
+ throw new NotImplementedError()
104
+ }
105
+
106
+ override def getAuthMethod: String = {
107
+ if (getAccessKeyId.isPresent && getSecretAccessKey.isPresent) {
108
+ if (task.getAuthMethod != "basic") {
109
+ logger.warn(
110
+ "[Deprecated] The default value of \"auth_method\" option is \"default\", " +
111
+ "but currently use \"basic\" auth_method for backward compatibility " +
112
+ "because you set \"access_key_id\" and \"secret_access_key\" options. " +
113
+ "Please set \"basic\" to \"auth_method\" option expressly."
114
+ )
115
+ return "basic"
116
+ }
117
+ }
118
+ task.getAuthMethod
119
+ }
120
+
121
+ override def getAccessKeyId: Optional[String] = {
122
+ if (task.getAccessKeyId.isPresent && task.getAccessKey.isPresent)
123
+ throw new ConfigException(
124
+ "You cannot use both \"access_key_id\" option and \"access_key\" option. Use \"access_key_id\" option."
125
+ )
126
+ if (task.getAccessKey.isPresent) {
127
+ logger.warn(
128
+ "[Deprecated] \"access_key\" option is deprecated. Use \"access_key_id\" option instead."
129
+ )
130
+ return task.getAccessKey
131
+ }
132
+ task.getAccessKeyId
133
+ }
134
+
135
+ override def getSecretAccessKey: Optional[String] = {
136
+ if (task.getSecretAccessKey.isPresent && task.getSecretKey.isPresent)
137
+ throw new ConfigException(
138
+ "You cannot use both \"secret_access_key\" option and \"secret_key\" option. Use \"secret_access_key\" option."
139
+ )
140
+ if (task.getSecretKey.isPresent) {
141
+ logger.warn(
142
+ "[Deprecated] \"secret_key\" option is deprecated. Use \"secret_access_key\" option instead."
143
+ )
144
+ return task.getSecretKey
145
+ }
146
+ task.getSecretAccessKey
147
+ }
148
+ }
149
+
150
+ class AwsCredentials(task: Task) {
151
+
152
+ def createAwsCredentialsProvider: AWSCredentialsProvider = {
153
+ task.getAuthMethod match {
154
+ case "basic" =>
155
+ new AWSStaticCredentialsProvider(
156
+ new BasicAWSCredentials(
157
+ getRequiredOption(task.getAccessKeyId, "access_key_id"),
158
+ getRequiredOption(task.getSecretAccessKey, "secret_access_key")
159
+ )
160
+ )
161
+
162
+ case "env" =>
163
+ new EnvironmentVariableCredentialsProvider
164
+
165
+ case "instance" =>
166
+ // NOTE: combination of InstanceProfileCredentialsProvider and ContainerCredentialsProvider
167
+ new EC2ContainerCredentialsProviderWrapper
168
+
169
+ case "profile" =>
170
+ if (task.getProfileFile.isPresent) {
171
+ val pf: ProfilesConfigFile = new ProfilesConfigFile(
172
+ task.getProfileFile.get().getFile
173
+ )
174
+ new ProfileCredentialsProvider(pf, task.getProfileName)
175
+ }
176
+ else new ProfileCredentialsProvider(task.getProfileName)
177
+
178
+ case "properties" =>
179
+ new SystemPropertiesCredentialsProvider
180
+
181
+ case "anonymous" =>
182
+ new AWSStaticCredentialsProvider(new AnonymousAWSCredentials)
183
+
184
+ case "session" =>
185
+ new AWSStaticCredentialsProvider(
186
+ new BasicSessionCredentials(
187
+ getRequiredOption(task.getAccessKeyId, "access_key_id"),
188
+ getRequiredOption(task.getSecretAccessKey, "secret_access_key"),
189
+ getRequiredOption(task.getSessionToken, "session_token")
190
+ )
191
+ )
192
+
193
+ case "assume_role" =>
194
+ // NOTE: Are http_proxy, endpoint, region required when assuming role?
195
+ val builder = new STSAssumeRoleSessionCredentialsProvider.Builder(
196
+ getRequiredOption(task.getRoleArn, "role_arn"),
197
+ getRequiredOption(task.getRoleSessionName, "role_session_name")
198
+ )
199
+ task.getRoleExternalId.ifPresent(v => builder.withExternalId(v))
200
+ task.getRoleSessionDurationSeconds.ifPresent(v =>
201
+ builder.withRoleSessionDurationSeconds(v)
202
+ )
203
+ task.getScopeDownPolicy.ifPresent(v => builder.withScopeDownPolicy(v))
204
+
205
+ builder.build()
206
+
207
+ case "web_identity_token" =>
208
+ WebIdentityTokenCredentialsProvider
209
+ .builder()
210
+ .roleArn(getRequiredOption(task.getRoleArn, "role_arn"))
211
+ .roleSessionName(
212
+ getRequiredOption(task.getRoleSessionName, "role_session_name")
213
+ )
214
+ .webIdentityTokenFile(
215
+ getRequiredOption(
216
+ task.getWebIdentityTokenFile,
217
+ "web_identity_token_file"
218
+ )
219
+ )
220
+ .build()
221
+
222
+ case "default" =>
223
+ new DefaultAWSCredentialsProviderChain
224
+
225
+ case am =>
226
+ throw new ConfigException(
227
+ s"'$am' is unsupported: `auth_method` must be one of ['basic', 'env', 'instance', 'profile', 'properties', 'anonymous', 'session', 'assume_role', 'default']."
228
+ )
229
+ }
230
+ }
231
+
232
+ private def getRequiredOption[A](o: Optional[A], name: String): A = {
233
+ o.orElseThrow(() =>
234
+ new ConfigException(
235
+ s"`$name` must be set when `auth_method` is ${task.getAuthMethod}."
236
+ )
237
+ )
238
+ }
239
+
240
+ }