embulk-input-dynamodb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }