embulk-input-dynamodb 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/README.md +13 -4
- data/build.gradle +1 -1
- data/circle.yml +10 -2
- data/src/main/scala/org/embulk/input/dynamodb/AwsCredentials.scala +0 -8
- data/src/main/scala/org/embulk/input/dynamodb/DynamoDBUtil.scala +18 -11
- data/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala +6 -2
- data/src/main/scala/org/embulk/input/dynamodb/package.scala +14 -0
- data/src/test/resources/yaml/dynamodb-local.yml +23 -0
- data/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala +12 -6
- data/src/test/scala/org/embulk/input/dynamodb/DynamoDBUtilTest.scala +81 -0
- data/test/create_table.sh +16 -0
- data/test/put_items.sh +25 -0
- data/test/run_dynamodb_local.sh +7 -0
- metadata +19 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 603d3c6513d86bf773182a8214d70cb668fcf2a6
|
4
|
+
data.tar.gz: 60d9256bb5fac99e56f5424997037323e98f3868
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55d4bdb5960bdf069804360dd0ca9b627bb7cfab0be6bb92c1012b14335019f3c1fd9dd31548fcf9b11b3e22d207efc35b8faf1172b7a5be531d62d39966d61e
|
7
|
+
data.tar.gz: 9ae148915f273eca767e740b0cd523f7d355b6f793644106320b592695124682769c47df41b643139d91111d9e541b65e692a32d31b9072a04ea1721b1fd5b35
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
10
|
|
11
11
|
## Configuration
|
12
|
-
- **auth_method**: AWS Credential Type.
|
12
|
+
- **auth_method**: AWS Credential Type.
|
13
13
|
Available values options are: `basic`, `env`, `instance`, `profile`, `properties`
|
14
14
|
- **basic**: AWS access key and secret access key
|
15
15
|
- **env**: Environment variables
|
@@ -21,13 +21,15 @@ Available values options are: `basic`, `env`, `instance`, `profile`, `properties
|
|
21
21
|
- **secret_key**: AWS secret key (string, required)
|
22
22
|
- If **auth_method** is set `profile`
|
23
23
|
- **profile_name**: The name of a local configuration profile (string, optional)
|
24
|
-
- **region**: Region Name (string,
|
24
|
+
- **region**: Region Name (string, optional)
|
25
|
+
- **end_point**: EndPoint URL (string, optional)
|
26
|
+
`end_point` has priority when `region` and `end_point` are specified.
|
25
27
|
- **table**: Table Name (string, required)
|
26
28
|
- **scan_limit**: DynamoDB 1time Scan Query size limit (Int, optional)
|
27
29
|
- **record_limit**: Max Record Search limit (Long, optional)
|
28
30
|
- **columns**: a key-value pairs where key is a column name and value is options for the column (required)
|
29
31
|
- **name**: Column name.
|
30
|
-
- **type**: Column values are converted to this embulk type.
|
32
|
+
- **type**: Column values are converted to this embulk type.
|
31
33
|
Available values options are: `boolean`, `long`, `double`, `string`, `json`
|
32
34
|
- **filters**: query filter (optional)
|
33
35
|
|
@@ -46,7 +48,7 @@ in:
|
|
46
48
|
- {name: ColumnB, type: double}
|
47
49
|
- {name: ColumnC, type: string}
|
48
50
|
- {name: ColumnD, type: boolean}
|
49
|
-
- {name: ColumnE, type: json}
|
51
|
+
- {name: ColumnE, type: json} # DynamoDB Map, List and Set Column Type are json.
|
50
52
|
filters:
|
51
53
|
- {name: ColumnA, type: long, condition: BETWEEN, value: 10000, value2: 20000}
|
52
54
|
- {name: ColumnC, type: string, condition: EQ, value: foobar}
|
@@ -55,6 +57,13 @@ out:
|
|
55
57
|
type: stdout
|
56
58
|
```
|
57
59
|
|
60
|
+
## Try
|
61
|
+
|
62
|
+
```
|
63
|
+
$ ./gradlew classpath
|
64
|
+
$ embulk preview -I lib your-sample.yml
|
65
|
+
```
|
66
|
+
|
58
67
|
## Build
|
59
68
|
|
60
69
|
```
|
data/build.gradle
CHANGED
data/circle.yml
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
machine:
|
2
|
+
services:
|
3
|
+
- docker
|
4
|
+
|
1
5
|
general:
|
2
6
|
artifacts:
|
3
7
|
- "~/embulk-input-dynamodb/build/reports/tests"
|
4
8
|
|
5
9
|
test:
|
10
|
+
pre:
|
11
|
+
- sh ./test/run_dynamodb_local.sh; sleep 2
|
12
|
+
- sh ./test/create_table.sh
|
13
|
+
- sh ./test/put_items.sh
|
6
14
|
post:
|
7
|
-
|
8
|
-
|
15
|
+
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
|
16
|
+
- find . -type f -regex ".*/test-results/*/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
|
@@ -6,20 +6,27 @@ import com.amazonaws.ClientConfiguration
|
|
6
6
|
import com.amazonaws.regions.Regions
|
7
7
|
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient
|
8
8
|
import com.amazonaws.services.dynamodbv2.model.{AttributeValue, Condition, ScanRequest, ScanResult}
|
9
|
+
import org.embulk.config.ConfigException
|
9
10
|
import org.embulk.spi._
|
10
11
|
import org.embulk.spi.`type`.Types
|
11
|
-
import org.msgpack.value.{
|
12
|
+
import org.msgpack.value.{Value, ValueFactory}
|
12
13
|
|
13
|
-
import scala.collection.JavaConversions._
|
14
14
|
import scala.collection.JavaConverters._
|
15
15
|
|
16
16
|
object DynamoDBUtil {
|
17
17
|
def createClient(task: PluginTask): AmazonDynamoDBClient = {
|
18
|
-
new AmazonDynamoDBClient(
|
18
|
+
val client = new AmazonDynamoDBClient(
|
19
19
|
AwsCredentials.getCredentialsProvider(task),
|
20
20
|
new ClientConfiguration()
|
21
21
|
.withMaxConnections(50)) // SDK Default Value
|
22
|
-
|
22
|
+
|
23
|
+
if (task.getEndPoint.isPresent) {
|
24
|
+
client.withEndpoint(task.getEndPoint.get())
|
25
|
+
} else if (task.getRegion.isPresent) {
|
26
|
+
client.withRegion(Regions.fromName(task.getRegion.get()))
|
27
|
+
} else {
|
28
|
+
throw new ConfigException("At least one of EndPoint or Region must be set")
|
29
|
+
}
|
23
30
|
}
|
24
31
|
|
25
32
|
|
@@ -34,10 +41,10 @@ object DynamoDBUtil {
|
|
34
41
|
|
35
42
|
val attributes: JList[String] = new JArrayList[String]()
|
36
43
|
|
37
|
-
schema.getColumns.foreach { column =>
|
44
|
+
schema.getColumns.asScala.foreach { column =>
|
38
45
|
attributes.add(column.getName)
|
39
46
|
}
|
40
|
-
val scanFilter: JMap[String, Condition] = createScanFilter(task)
|
47
|
+
val scanFilter: JMap[String, Condition] = createScanFilter(task).asJava
|
41
48
|
var evaluateKey: JMap[String, AttributeValue] = null
|
42
49
|
|
43
50
|
val scanLimit: Long = task.getScanLimit
|
@@ -60,8 +67,8 @@ object DynamoDBUtil {
|
|
60
67
|
val result: ScanResult = client.scan(request)
|
61
68
|
evaluateKey = result.getLastEvaluatedKey
|
62
69
|
|
63
|
-
result.getItems.foreach { item =>
|
64
|
-
schema.getColumns.foreach { column =>
|
70
|
+
result.getItems.asScala.foreach { item =>
|
71
|
+
schema.getColumns.asScala.foreach { column =>
|
65
72
|
val value = item.asScala.get(column.getName)
|
66
73
|
column.getType match {
|
67
74
|
case Types.STRING =>
|
@@ -97,7 +104,7 @@ object DynamoDBUtil {
|
|
97
104
|
val filterMap = collection.mutable.HashMap[String, Condition]()
|
98
105
|
|
99
106
|
Option(task.getFilters.orNull).map { filters =>
|
100
|
-
filters.getFilters.map { filter =>
|
107
|
+
filters.getFilters.asScala.map { filter =>
|
101
108
|
val attributeValueList = collection.mutable.ArrayBuffer[AttributeValue]()
|
102
109
|
attributeValueList += createAttributeValue(filter.getType, filter.getValue)
|
103
110
|
Option(filter.getValue2).map { value2 =>
|
@@ -105,7 +112,7 @@ object DynamoDBUtil {
|
|
105
112
|
|
106
113
|
filterMap += filter.getName -> new Condition()
|
107
114
|
.withComparisonOperator(filter.getCondition)
|
108
|
-
.withAttributeValueList(attributeValueList)
|
115
|
+
.withAttributeValueList(attributeValueList.asJava)
|
109
116
|
}
|
110
117
|
}
|
111
118
|
|
@@ -138,7 +145,7 @@ object DynamoDBUtil {
|
|
138
145
|
value.map(_.getN.toDouble).getOrElse(0D)
|
139
146
|
|
140
147
|
implicit private def BooleanConvert(value: Option[AttributeValue]): Boolean =
|
141
|
-
value.exists(_.
|
148
|
+
value.exists(_.getBOOL)
|
142
149
|
|
143
150
|
implicit private def JsonConvert(value: Option[AttributeValue]): Value = {
|
144
151
|
value.map { attr =>
|
@@ -22,8 +22,12 @@ trait PluginTask extends Task {
|
|
22
22
|
def getProfileName: Optional[String]
|
23
23
|
|
24
24
|
@Config("region")
|
25
|
-
@ConfigDefault("
|
26
|
-
def getRegion: String
|
25
|
+
@ConfigDefault("null")
|
26
|
+
def getRegion: Optional[String]
|
27
|
+
|
28
|
+
@Config("end_point")
|
29
|
+
@ConfigDefault("null")
|
30
|
+
def getEndPoint: Optional[String]
|
27
31
|
|
28
32
|
@Config("scan_limit")
|
29
33
|
@ConfigDefault("0")
|
@@ -0,0 +1,14 @@
|
|
1
|
+
package org.embulk.input
|
2
|
+
|
3
|
+
import com.google.common.base.Optional
|
4
|
+
import org.embulk.config.ConfigException
|
5
|
+
|
6
|
+
package object dynamodb {
|
7
|
+
def require[A](value: Optional[A], message: String): A = {
|
8
|
+
if (value.isPresent) {
|
9
|
+
value.get()
|
10
|
+
} else {
|
11
|
+
throw new ConfigException("Required option is not set: " + message)
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
in:
|
2
|
+
type: dynamodb
|
3
|
+
end_point: http://localhost:8000/
|
4
|
+
table: ENV_VAR
|
5
|
+
auth_method: basic
|
6
|
+
access_key: dummy
|
7
|
+
secret_key: dummy
|
8
|
+
columns:
|
9
|
+
- {name: pri-key, type: string}
|
10
|
+
- {name: sort-key, type: long}
|
11
|
+
- {name: doubleValue, type: double}
|
12
|
+
- {name: boolValue, type: boolean}
|
13
|
+
- {name: listValue, type: json}
|
14
|
+
- {name: mapValue, type: json}
|
15
|
+
|
16
|
+
out:
|
17
|
+
type: file
|
18
|
+
path_prefix: dynamodb-local-result
|
19
|
+
file_ext: tsv
|
20
|
+
formatter:
|
21
|
+
type: csv
|
22
|
+
delimiter: "\t"
|
23
|
+
header_line: false
|
@@ -138,12 +138,18 @@ class AttributeValueHelperTest {
|
|
138
138
|
}
|
139
139
|
|
140
140
|
def attr[A](value: A)(implicit f: A=> AttributeValue): AttributeValue = f(value)
|
141
|
-
implicit def StringAttributeValue(value: String): AttributeValue
|
142
|
-
|
143
|
-
implicit def
|
144
|
-
|
145
|
-
implicit def
|
146
|
-
|
141
|
+
implicit def StringAttributeValue(value: String): AttributeValue
|
142
|
+
= new AttributeValue().withS(value)
|
143
|
+
implicit def IntegerAttributeValue(value: Int): AttributeValue
|
144
|
+
= new AttributeValue().withN(value.toString)
|
145
|
+
implicit def LongAttributeValue(value: Long): AttributeValue
|
146
|
+
= new AttributeValue().withN(value.toString)
|
147
|
+
implicit def FloatAttributeValue(value: Float): AttributeValue
|
148
|
+
= new AttributeValue().withN(value.toString)
|
149
|
+
implicit def DoubleAttributeValue(value: Double): AttributeValue
|
150
|
+
= new AttributeValue().withN(value.toString)
|
151
|
+
implicit def BooleanAttributeValue(value: Boolean): AttributeValue
|
152
|
+
= new AttributeValue().withBOOL(value)
|
147
153
|
implicit def MapAttributeValue(value: Map[String, AttributeValue]): AttributeValue
|
148
154
|
= new AttributeValue().withM(value.asJava)
|
149
155
|
implicit def ListAttributeValue(value: List[AttributeValue]): AttributeValue
|
@@ -0,0 +1,81 @@
|
|
1
|
+
package org.embulk.input.dynamodb
|
2
|
+
|
3
|
+
import java.io.File
|
4
|
+
import java.nio.charset.Charset
|
5
|
+
import java.nio.file.{FileSystems, Files}
|
6
|
+
|
7
|
+
import com.fasterxml.jackson.databind.ObjectMapper
|
8
|
+
import com.google.inject.{Binder, Module}
|
9
|
+
import org.embulk.EmbulkEmbed
|
10
|
+
import org.embulk.config.ConfigSource
|
11
|
+
import org.embulk.plugin.InjectedPluginSource
|
12
|
+
import org.embulk.spi.InputPlugin
|
13
|
+
import org.hamcrest.CoreMatchers._
|
14
|
+
import org.junit.Assert._
|
15
|
+
import org.junit.{Before, Test}
|
16
|
+
|
17
|
+
class DynamoDBUtilTest {
|
18
|
+
private var embulk: EmbulkEmbed = null
|
19
|
+
|
20
|
+
private var EMBULK_DYNAMODB_TEST_TABLE: String = null
|
21
|
+
private var mapper: ObjectMapper = null
|
22
|
+
|
23
|
+
@Before
|
24
|
+
def createResources() {
|
25
|
+
// Get Environments
|
26
|
+
EMBULK_DYNAMODB_TEST_TABLE = System.getenv("EMBULK_DYNAMODB_TEST_TABLE")
|
27
|
+
|
28
|
+
val bootstrap = new EmbulkEmbed.Bootstrap()
|
29
|
+
bootstrap.addModules(new Module {
|
30
|
+
def configure(binder: Binder): Unit = {
|
31
|
+
InjectedPluginSource.registerPluginTo(binder,
|
32
|
+
classOf[InputPlugin],
|
33
|
+
"dynamodb",
|
34
|
+
classOf[DynamodbInputPlugin])
|
35
|
+
}
|
36
|
+
})
|
37
|
+
|
38
|
+
embulk = bootstrap.initializeCloseable()
|
39
|
+
|
40
|
+
mapper = new ObjectMapper()
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
def doTest(config: ConfigSource) {
|
45
|
+
embulk.run(config)
|
46
|
+
|
47
|
+
val fs = FileSystems.getDefault
|
48
|
+
val lines = Files.readAllLines(fs.getPath("dynamodb-local-result000.00.tsv"), Charset.forName("UTF-8"))
|
49
|
+
assertEquals(lines.size, 1)
|
50
|
+
|
51
|
+
val head = lines.get(0)
|
52
|
+
val values = head.split("\t")
|
53
|
+
|
54
|
+
assertThat(values(0), is("key-1"))
|
55
|
+
assertThat(values(1), is("0"))
|
56
|
+
assertThat(values(2), is("42.195"))
|
57
|
+
assertThat(values(3), is("true"))
|
58
|
+
|
59
|
+
val listValue = mapper.readValue(values(4).replaceAll("\"(?!\")", ""), classOf[java.util.List[Object]])
|
60
|
+
assertThat(listValue.size(), is(2))
|
61
|
+
assertThat(listValue.get(0).asInstanceOf[String], is("list-value"))
|
62
|
+
assertThat(listValue.get(1).asInstanceOf[Int], is(123))
|
63
|
+
|
64
|
+
val mapValue = mapper.readValue(values(5).replaceAll("\"(?!\")", ""), classOf[java.util.Map[String, Object]])
|
65
|
+
assert(mapValue.containsKey("map-key-1"))
|
66
|
+
assertThat(mapValue.get("map-key-1").asInstanceOf[String], is("map-value-1"))
|
67
|
+
assert(mapValue.containsKey("map-key-2"))
|
68
|
+
assertThat(mapValue.get("map-key-2").asInstanceOf[Int], is(456))
|
69
|
+
}
|
70
|
+
|
71
|
+
@Test
|
72
|
+
def scanTest() {
|
73
|
+
val config = embulk.newConfigLoader().fromYamlFile(
|
74
|
+
new File("src/test/resources/yaml/dynamodb-local.yml"))
|
75
|
+
|
76
|
+
config.getNested("in")
|
77
|
+
.set("table", EMBULK_DYNAMODB_TEST_TABLE)
|
78
|
+
|
79
|
+
doTest(config)
|
80
|
+
}
|
81
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
aws dynamodb create-table \
|
4
|
+
--table-name='EMBULK_DYNAMODB_TEST_TABLE' \
|
5
|
+
--attribute-definitions='[
|
6
|
+
{"AttributeName":"pri-key","AttributeType":"S"},
|
7
|
+
{"AttributeName":"sort-key","AttributeType":"N"}
|
8
|
+
]' \
|
9
|
+
--key-schema='[
|
10
|
+
{"AttributeName":"pri-key","KeyType":"HASH"},
|
11
|
+
{"AttributeName":"sort-key","KeyType":"RANGE"}
|
12
|
+
]' \
|
13
|
+
--provisioned-throughput='{"ReadCapacityUnits":5, "WriteCapacityUnits":5}' \
|
14
|
+
--endpoint-url http://localhost:8000 \
|
15
|
+
--region us-east-1
|
16
|
+
|
data/test/put_items.sh
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
aws dynamodb put-item \
|
4
|
+
--table-name='EMBULK_DYNAMODB_TEST_TABLE' \
|
5
|
+
--item='{
|
6
|
+
"pri-key" : { "S" : "key-1" },
|
7
|
+
"sort-key" : { "N" : "0" },
|
8
|
+
"doubleValue" : { "N" : "42.195" },
|
9
|
+
"boolValue" : { "BOOL" : true },
|
10
|
+
"listValue" : { "L":
|
11
|
+
[
|
12
|
+
{ "S" : "list-value"},
|
13
|
+
{ "N" : "123"}
|
14
|
+
]
|
15
|
+
},
|
16
|
+
"mapValue" : { "M":
|
17
|
+
{
|
18
|
+
"map-key-1" : { "S" : "map-value-1" },
|
19
|
+
"map-key-2" : { "N" : "456" }
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}' \
|
23
|
+
--endpoint-url http://localhost:8000 \
|
24
|
+
--region us-east-1
|
25
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-dynamodb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daisuke Higashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,25 +64,31 @@ files:
|
|
64
64
|
- src/main/scala/org/embulk/input/dynamodb/Filter.scala
|
65
65
|
- src/main/scala/org/embulk/input/dynamodb/FilterConfig.scala
|
66
66
|
- src/main/scala/org/embulk/input/dynamodb/PluginTask.scala
|
67
|
+
- src/main/scala/org/embulk/input/dynamodb/package.scala
|
67
68
|
- src/test/resources/json/test.json
|
68
69
|
- src/test/resources/json/test.template
|
69
70
|
- src/test/resources/yaml/authMethodBasic.yml
|
70
71
|
- src/test/resources/yaml/authMethodBasic_Error.yml
|
71
72
|
- src/test/resources/yaml/authMethodEnv.yml
|
72
73
|
- src/test/resources/yaml/authMethodProfile.yml
|
74
|
+
- src/test/resources/yaml/dynamodb-local.yml
|
73
75
|
- src/test/resources/yaml/notSetAuthMethod.yml
|
74
76
|
- src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala
|
75
77
|
- src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala
|
76
|
-
-
|
78
|
+
- src/test/scala/org/embulk/input/dynamodb/DynamoDBUtilTest.scala
|
79
|
+
- test/create_table.sh
|
80
|
+
- test/put_items.sh
|
81
|
+
- test/run_dynamodb_local.sh
|
82
|
+
- classpath/commons-logging-1.1.3.jar
|
83
|
+
- classpath/commons-codec-1.6.jar
|
84
|
+
- classpath/scala-library-2.11.7.jar
|
77
85
|
- classpath/aws-java-sdk-dynamodb-1.10.43.jar
|
86
|
+
- classpath/embulk-input-dynamodb-0.1.1.jar
|
78
87
|
- classpath/aws-java-sdk-kms-1.10.43.jar
|
79
|
-
- classpath/aws-java-sdk-s3-1.10.43.jar
|
80
|
-
- classpath/commons-codec-1.6.jar
|
81
|
-
- classpath/commons-logging-1.1.3.jar
|
82
|
-
- classpath/embulk-input-dynamodb-0.1.0.jar
|
83
|
-
- classpath/httpclient-4.3.6.jar
|
84
88
|
- classpath/httpcore-4.3.3.jar
|
85
|
-
- classpath/
|
89
|
+
- classpath/httpclient-4.3.6.jar
|
90
|
+
- classpath/aws-java-sdk-core-1.10.43.jar
|
91
|
+
- classpath/aws-java-sdk-s3-1.10.43.jar
|
86
92
|
homepage: https://github.com/lulichn/embulk-input-dynamodb
|
87
93
|
licenses:
|
88
94
|
- MIT
|
@@ -107,4 +113,7 @@ rubygems_version: 2.1.9
|
|
107
113
|
signing_key:
|
108
114
|
specification_version: 4
|
109
115
|
summary: Dynamodb input plugin for Embulk
|
110
|
-
test_files:
|
116
|
+
test_files:
|
117
|
+
- test/create_table.sh
|
118
|
+
- test/put_items.sh
|
119
|
+
- test/run_dynamodb_local.sh
|